DCCL v3
dccl_tool.cpp
1 // Copyright 2009-2017 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (for 2013-)
3 // Massachusetts Institute of Technology (for 2007-2014)
4 // Community contributors (see AUTHORS file)
5 //
6 //
7 // This file is part of the Dynamic Compact Control Language Library
8 // ("DCCL").
9 //
10 // DCCL is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 2.1 of the License, or
13 // (at your option) any later version.
14 //
15 // DCCL is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with DCCL. If not, see <http://www.gnu.org/licenses/>.
22 //
23 // For the 'dccl' tool: loading non-GPL shared libraries for the purpose of
24 // using this tool does *not* violate the GPL license terms of DCCL.
25 //
26 
27 
28 
29 #include <sstream>
30 #include <fstream>
31 
32 
33 #include <google/protobuf/descriptor.h>
34 #include <google/protobuf/text_format.h>
35 #include <google/protobuf/descriptor.pb.h>
36 
37 #include <boost/algorithm/string.hpp>
38 
39 #include "dccl/codec.h"
40 #include "dccl/cli_option.h"
41 #include "dccl/binary.h"
42 
43 #include "dccl_tool.pb.h"
44 #include "dccl/version.h"
45 
46 // for realpath
47 #include <limits.h>
48 #include <stdlib.h>
49 
50 
51 enum Action { NO_ACTION, ENCODE, DECODE, ANALYZE, DISP_PROTO };
52 enum Format { BINARY, TEXTFORMAT, HEX, BASE64 };
53 
54 namespace dccl
55 {
57  namespace tool
58  {
59  struct Config
60  {
61  Config()
62  : action(NO_ACTION),
63  format(BINARY),
64  id_codec(dccl::Codec::default_id_codec_name()),
65  verbose(false),
66  omit_prefix(false)
67  { }
68 
69  Action action;
70  std::set<std::string> include;
71  std::vector<std::string> dlopen;
72  std::set<std::string> message;
73  std::set<std::string> proto_file;
74  Format format;
75  std::string id_codec;
76  bool verbose;
77  bool omit_prefix;
78  };
79  }
80 }
81 
82 
83 void analyze(dccl::Codec& dccl, const dccl::tool::Config& cfg);
84 void encode(dccl::Codec& dccl, dccl::tool::Config& cfg);
85 void decode(dccl::Codec& dccl, const dccl::tool::Config& cfg);
86 void disp_proto(dccl::Codec& dccl, const dccl::tool::Config& cfg);
87 
88 
89 void load_desc(dccl::Codec* dccl, const google::protobuf::Descriptor* desc, const std::string& name);
90 void parse_options(int argc, char* argv[], dccl::tool::Config* cfg);
91 
92 
93 int main(int argc, char* argv[])
94 {
95  {
97  parse_options(argc, argv, &cfg);
98 
99  if(!cfg.verbose)
100  dccl::dlog.connect(dccl::logger::WARN_PLUS, &std::cerr);
101  else
102  dccl::dlog.connect(dccl::logger::DEBUG1_PLUS, &std::cerr);
103 
105 
106  for(std::set<std::string>::const_iterator it = cfg.include.begin(),
107  end = cfg.include.end(); it != end; ++it)
109 
110 
111 
112  std::string first_dl;
113  if(cfg.dlopen.size())
114  first_dl = cfg.dlopen[0];
115 
116  dccl::Codec dccl(cfg.id_codec, first_dl);
117 
118  if(cfg.dlopen.size() > 1)
119  {
120  for(std::vector<std::string>::iterator it = cfg.dlopen.begin()+1,
121  n = cfg.dlopen.end(); it != n; ++it)
122  dccl.load_library(*it);
123  }
124  bool no_messages_specified = cfg.message.empty();
125  for(std::set<std::string>::const_iterator it = cfg.proto_file.begin(),
126  end = cfg.proto_file.end(); it != end; ++it)
127  {
128  const google::protobuf::FileDescriptor* file_desc =
130 
131  if(!file_desc)
132  {
133  std::cerr << "failed to read in: " << *it << std::endl;
134  exit(EXIT_FAILURE);
135  }
136 
137  // if no messages explicitly specified, load them all.
138  if(no_messages_specified)
139  {
140  for(int i = 0, n = file_desc->message_type_count(); i < n; ++i)
141  {
142  cfg.message.insert(file_desc->message_type(i)->full_name());
143  if(i == 0 && cfg.action == ENCODE)
144  {
145  std::cerr << "Encoding assuming message: " << file_desc->message_type(i)->full_name() << std::endl;
146  break;
147  }
148  }
149  }
150  }
151 
152  // Load up all the messages
153  for(std::set<std::string>::const_iterator it = cfg.message.begin(),
154  end = cfg.message.end(); it != end; ++it)
155  {
156  const google::protobuf::Descriptor* desc =
158  load_desc(&dccl, desc, *it);
159  }
160 
161  switch(cfg.action)
162  {
163  case ENCODE: encode(dccl, cfg); break;
164  case DECODE: decode(dccl, cfg); break;
165  case ANALYZE: analyze(dccl, cfg); break;
166  case DISP_PROTO: disp_proto(dccl, cfg); break;
167  default:
168  std::cerr << "No action specified (e.g. analyze, decode, encode). Try --help." << std::endl;
169  exit(EXIT_SUCCESS);
170 
171  }
172  }
173 }
174 
175 
176 
177 void analyze(dccl::Codec& dccl, const dccl::tool::Config& cfg)
178 {
179  dccl.info_all(&std::cout);
180 }
181 
182 void encode(dccl::Codec& dccl, dccl::tool::Config& cfg)
183 {
184  if(cfg.message.size() > 1)
185  {
186  std::cerr << "No more than one DCCL message can be specified with -m or --message for encoding." << std::endl;
187  exit(EXIT_FAILURE);
188  }
189  else if(cfg.message.size() == 0)
190  {
191  std::cerr << "You must specify a DCCL message to encode with -m" << std::endl;
192  exit(EXIT_FAILURE);
193  }
194 
195  std::string command_line_name = *cfg.message.begin();
196 
197  while(!std::cin.eof())
198  {
199  std::string input;
200  std::getline (std::cin, input);
201 
202  boost::trim(input);
203  if(input.empty())
204  continue;
205 
206  std::string name;
207  if(input[0] == '|')
208  {
209  std::string::size_type close_bracket_pos = input.find('|', 1);
210  if(close_bracket_pos == std::string::npos)
211  {
212  std::cerr << "Incorrectly formatted input: expected '|'" << std::endl;
213  exit(EXIT_FAILURE);
214  }
215 
216  name = input.substr(1, close_bracket_pos-1);
217  if(cfg.message.find(name) == cfg.message.end())
218  {
219  const google::protobuf::Descriptor* desc =
221  load_desc(&dccl, desc, name);
222  cfg.message.insert(name);
223  }
224 
225 
226  if(input.size() > close_bracket_pos+1)
227  input = input.substr(close_bracket_pos+1);
228  else
229  input.clear();
230  }
231  else
232  {
233  if(cfg.message.size() == 0)
234  {
235  std::cerr << "Message name not given with -m or in the input (i.e. '[Name] field1: value field2: value')." << std::endl;
236  exit(EXIT_FAILURE);
237  }
238 
239  name = command_line_name;
240  }
241 
242  const google::protobuf::Descriptor* desc = dccl::DynamicProtobufManager::find_descriptor(name);
243  if(desc == 0)
244  {
245  std::cerr << "No descriptor with name " << name << " found! Make sure you have loaded all the necessary .proto files and/or shared libraries. Also make sure you specified the fully qualified name including the package, if any (e.g. 'goby.acomms.protobuf.NetworkAck', not just 'NetworkAck')." << std::endl;
246  exit(EXIT_FAILURE);
247  }
248 
249 
250  boost::shared_ptr<google::protobuf::Message> msg = dccl::DynamicProtobufManager::new_protobuf_message(desc);
251  google::protobuf::TextFormat::ParseFromString(input, msg.get());
252 
253  if(msg->IsInitialized())
254  {
255  std::string encoded;
256  dccl.encode(&encoded, *msg);
257  switch(cfg.format)
258  {
259  default:
260  case BINARY:
261  {
262  std::ofstream fout("/dev/stdout", std::ios::binary | std::ios::app);
263  fout.write(encoded.data(), encoded.size());
264  break;
265  }
266 
267  case TEXTFORMAT:
268  {
269 
270  dccl::tool::protobuf::ByteString s;
271  s.set_b(encoded);
272  std::string output;
273  google::protobuf::TextFormat::PrintFieldValueToString(s, s.GetDescriptor()->FindFieldByNumber(1), -1, &output);
274 
275  std::cout << output << std::endl;
276  break;
277  }
278 
279  case HEX:
280  std::cout << dccl::hex_encode(encoded) << std::endl;
281  break;
282  case BASE64:
283 #if DCCL_HAS_B64
284  std::stringstream instream(encoded);
285  std::stringstream outstream;
286  ::base64::encoder D;
287  D.encode(instream, outstream);
288  std::cout << outstream.str();
289 #else
290  std::cerr << "dccl was not compiled with libb64-dev, so no Base64 functionality is available." << std::endl;
291  exit(EXIT_FAILURE);
292 #endif
293  break;
294  }
295 
296  }
297  }
298 }
299 
300 void decode(dccl::Codec& dccl, const dccl::tool::Config& cfg)
301 {
302  std::string input;
303  if(cfg.format == BINARY)
304  {
305  std::ifstream fin("/dev/stdin", std::ios::binary);
306  std::ostringstream ostrm;
307  ostrm << fin.rdbuf();
308  input = ostrm.str();
309  }
310  else
311  {
312  while(!std::cin.eof())
313  {
314  std::string line;
315  std::getline (std::cin, line);
316 
317  if(boost::trim_copy(line).empty())
318  continue;
319 
320  switch(cfg.format)
321  {
322  default:
323  case BINARY:
324  break;
325 
326  case TEXTFORMAT:
327  {
328  boost::trim_if(line, boost::is_any_of("\""));
329 
330 
331  dccl::tool::protobuf::ByteString s;
332  google::protobuf::TextFormat::ParseFieldValueFromString("\"" + line + "\"", s.GetDescriptor()->FindFieldByNumber(1), &s);
333  input += s.b();
334  break;
335  }
336  case HEX:
337  input += dccl::hex_decode(line);
338  break;
339  case BASE64:
340 #if DCCL_HAS_B64
341  std::stringstream instream(line);
342  std::stringstream outstream;
343  ::base64::decoder D;
344  D.decode(instream, outstream);
345  input += outstream.str();
346  break;
347 #else
348  std::cerr << "dccl was not compiled with libb64-dev, so no Base64 functionality is available." << std::endl;
349  exit(EXIT_FAILURE);
350 #endif
351  }
352  }
353  }
354 
355  while(!input.empty())
356  {
357  boost::shared_ptr<google::protobuf::Message> msg = dccl.decode<boost::shared_ptr<google::protobuf::Message> >(&input);
358  if(!cfg.omit_prefix)
359  std::cout << "|" << msg->GetDescriptor()->full_name() << "| ";
360  std::cout << msg->ShortDebugString() << std::endl;
361  }
362 }
363 
364 void disp_proto(dccl::Codec& dccl, const dccl::tool::Config& cfg)
365 {
366  std::cout << "Please note that for Google Protobuf versions < 2.5.0, the dccl extensions will not be show below, so you'll need to refer to the original .proto file." << std::endl;
367  for(std::set<std::string>::const_iterator it = cfg.message.begin(),
368  end = cfg.message.end(); it != end; ++it)
369  {
370  const google::protobuf::Descriptor* desc =
372 
373  std::cout << desc->DebugString();
374 
375  }
376 }
377 
378 
379 void load_desc(dccl::Codec* dccl, const google::protobuf::Descriptor* desc, const std::string& name)
380 {
381  if(desc)
382  {
383  try { dccl->load(desc); }
384  catch(std::exception& e)
385  {
386  std::cerr << "Not a valid DCCL message: " << desc->full_name() << "\nWhy: " << e.what() << std::endl;
387  }
388  }
389  else
390  {
391  std::cerr << "No descriptor with name " << name << " found! Make sure you have loaded all the necessary .proto files and/or shared libraries. Try --help." << std::endl;
392  exit(EXIT_FAILURE);
393  }
394 }
395 
396 void parse_options(int argc, char* argv[], dccl::tool::Config* cfg)
397 {
398  std::vector<dccl::Option> options;
399  options.push_back(dccl::Option('e', "encode", no_argument, "Encode a DCCL message to STDOUT from STDIN"));
400  options.push_back(dccl::Option('d', "decode", no_argument, "Decode a DCCL message to STDOUT from STDIN"));
401  options.push_back(dccl::Option('a', "analyze", no_argument, "Provides information on a given DCCL message definition (e.g. field sizes)"));
402  options.push_back(dccl::Option('p', "display_proto", no_argument, "Display the .proto definition of this message."));
403  options.push_back(dccl::Option('h', "help", no_argument, "Gives help on the usage of 'dccl'"));
404  options.push_back(dccl::Option('I', "proto_path", required_argument, "Add another search directory for .proto files"));
405  options.push_back(dccl::Option('l', "dlopen", required_argument, "Open this shared library containing compiled DCCL messages."));
406  options.push_back(dccl::Option('m', "message", required_argument, "Message name to encode, decode or analyze."));
407  options.push_back(dccl::Option('f', "proto_file", required_argument, ".proto file to load."));
408  options.push_back(dccl::Option(0, "format", required_argument, "Format for encode output or decode input: 'bin' (default) is raw binary, 'hex' is ascii-encoded hexadecimal, 'textformat' is a Google Protobuf TextFormat byte string, 'base64' is ascii-encoded base 64."));
409  options.push_back(dccl::Option('v', "verbose", no_argument, "Display extra debugging information."));
410  options.push_back(dccl::Option('o', "omit_prefix", no_argument, "Omit the DCCL type name prefix from the output of decode."));
411  options.push_back(dccl::Option('i', "id_codec", required_argument, "(Advanced) name for a nonstandard DCCL ID codec to use"));
412  options.push_back(dccl::Option('V', "version", no_argument, "DCCL Version"));
413 
414  std::vector<option> long_options;
415  std::string opt_string;
416  dccl::Option::convert_vector(options, &long_options, &opt_string);
417 
418  while (1) {
419  int option_index = 0;
420 
421  int c = getopt_long(argc, argv, opt_string.c_str(),
422  &long_options[0], &option_index);
423  if (c == -1)
424  break;
425 
426  switch (c) {
427  case 0:
428  // If this option set a flag, do nothing else now.
429  if (long_options[option_index].flag != 0)
430  break;
431 
432  if(!strcmp(long_options[option_index].name, "format"))
433  {
434  if(!strcmp(optarg, "textformat"))
435  cfg->format = TEXTFORMAT;
436  else if(!strcmp(optarg, "hex"))
437  cfg->format = HEX;
438  else if(!strcmp(optarg, "base64"))
439  cfg->format = BASE64;
440  else if(!strcmp(optarg, "bin"))
441  cfg->format = BINARY;
442  else
443  {
444  std::cerr << "Invalid format '" << optarg << "'" << std::endl;
445  exit(EXIT_FAILURE);
446  }
447  }
448  else
449  {
450  std::cerr << "Try --help for valid options." << std::endl;
451  exit(EXIT_FAILURE);
452  }
453 
454  break;
455 
456  case 'e': cfg->action = ENCODE; break;
457  case 'd': cfg->action = DECODE; break;
458  case 'a': cfg->action = ANALYZE; break;
459  case 'p': cfg->action = DISP_PROTO; break;
460  case 'I': cfg->include.insert(optarg); break;
461  case 'l': cfg->dlopen.push_back(optarg); break;
462  case 'm': cfg->message.insert(optarg); break;
463  case 'f':
464  {
465  char* proto_file_canonical_path = realpath(optarg, 0);
466  if(proto_file_canonical_path)
467  {
468  cfg->proto_file.insert(proto_file_canonical_path);
469  free(proto_file_canonical_path);
470  }
471  else
472  {
473  std::cerr << "Invalid proto file path: '" << optarg << "'" << std::endl;
474  exit(EXIT_FAILURE);
475  }
476  break;
477  }
478  case 'i': cfg->id_codec = optarg; break;
479  case 'v': cfg->verbose = true; break;
480  case 'o': cfg->omit_prefix = true; break;
481 
482  case 'h':
483  std::cout << "Usage of the Dynamic Compact Control Language (DCCL) tool ('dccl'): " << std::endl;
484  for(int i = 0, n = options.size(); i < n; ++i)
485  std::cout << " " << options[i].usage() << std::endl;
486  exit(EXIT_SUCCESS);
487  break;
488 
489  case 'V':
490  std::cout << dccl::VERSION_STRING << std::endl;
491  exit(EXIT_SUCCESS);
492  break;
493 
494 
495  case '?':
496  std::cerr << "Try --help for valid options." << std::endl;
497  exit(EXIT_FAILURE);
498  default: exit(EXIT_FAILURE);
499  }
500  }
501 
502  /* Print any remaining command line arguments (not options). */
503  if (optind < argc)
504  {
505  std::cerr << "Unknown arguments: \n";
506  while (optind < argc)
507  std::cerr << argv[optind++];
508  std::cerr << std::endl;
509  std::cerr << "Try --help for valid options." << std::endl;
510  exit(EXIT_FAILURE);
511  }
512 }
513 
dccl
Dynamic Compact Control Language namespace.
Definition: gen_units_class_plugin.h:49
dccl::DynamicProtobufManager::add_include_path
static void add_include_path(const std::string &path)
Add a path for searching for import messages when loading .proto files using load_from_proto_file()
Definition: dynamic_protobuf_manager.h:143
dccl::Option
Represents a command line option.
Definition: cli_option.h:33
dccl::Codec
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
Definition: codec.h:56
dccl::DynamicProtobufManager::find_descriptor
static const google::protobuf::Descriptor * find_descriptor(const std::string &protobuf_type_name)
Finds the Google Protobuf Descriptor (essentially a meta-class for a given Message) from a given Mess...
Definition: dynamic_protobuf_manager.h:54
dccl::DynamicProtobufManager::load_from_proto_file
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.
Definition: dynamic_protobuf_manager.cpp:60
dccl::Logger::connect
void connect(int verbosity_mask, Slot slot)
Connect the output of one or more given verbosities to a slot (function pointer or similar)
Definition: logger.h:161
dccl::hex_encode
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...
Definition: binary.h:100
dccl::DynamicProtobufManager::enable_compilation
static void enable_compilation()
Enable on the fly compilation of .proto files on the local disk. Must be called before load_from_prot...
Definition: dynamic_protobuf_manager.h:121
dccl::Option::convert_vector
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,...
Definition: cli_option.h:89
dccl::tool::Config
Definition: dccl_tool.cpp:59
dccl::hex_decode
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...
Definition: binary.h:49
dccl::DynamicProtobufManager::new_protobuf_message
static GoogleProtobufMessagePointer new_protobuf_message(const std::string &protobuf_type_name)
Create a new (empty) Google Protobuf message of a given type by name.
Definition: dynamic_protobuf_manager.h:76