DCCL v4
codec.cpp
1 // Copyright 2009-2023:
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 // Community contributors (see AUTHORS file)
5 // File authors:
6 // Toby Schneider <toby@gobysoft.org>
7 // philboske <philboske@gmail.com>
8 // Denis <dbiryukov@slb.com>
9 // Nathan Knotts <nknotts@gmail.com>
10 // Chris Murphy <cmurphy@aphysci.com>
11 // Davide Fenucci <davfen@noc.ac.uk>
12 // Kyle Guilbert <kguilbert@aphysci.com>
13 //
14 //
15 // This file is part of the Dynamic Compact Control Language Library
16 // ("DCCL").
17 //
18 // DCCL is free software: you can redistribute it and/or modify
19 // it under the terms of the GNU Lesser General Public License as published by
20 // the Free Software Foundation, either version 2.1 of the License, or
21 // (at your option) any later version.
22 //
23 // DCCL is distributed in the hope that it will be useful,
24 // but WITHOUT ANY WARRANTY; without even the implied warranty of
25 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 // GNU Lesser General Public License for more details.
27 //
28 // You should have received a copy of the GNU Lesser General Public License
29 // along with DCCL. If not, see <http://www.gnu.org/licenses/>.
30 #include <algorithm>
31 #include <utility>
32 
33 #include <dlfcn.h> // for shared library loading
34 
35 #include "codec.h"
36 
37 #if DCCL_HAS_CRYPTOPP
38 #if CRYPTOPP_PATH_USES_PLUS_SIGN
39 #include <crypto++/aes.h>
40 #include <crypto++/filters.h>
41 #include <crypto++/modes.h>
42 #include <crypto++/sha.h>
43 #else
44 #include <cryptopp/aes.h>
45 #include <cryptopp/filters.h>
46 #include <cryptopp/modes.h>
47 #include <cryptopp/sha.h>
48 #endif // CRYPTOPP_PATH_USES_PLUS_SIGN
49 #endif // HAS_CRYPTOPP
50 
51 #include "codecs3/field_codec_var_bytes.h"
52 #include "codecs4/field_codec_hash.h"
53 #include "codecs4/field_codec_var_bytes.h"
54 #include "field_codec_id.h"
55 
56 #include "codecs2/default_field_codec_impl.h"
57 #include "codecs3/default_field_codec_impl.h"
58 #include "codecs4/default_field_codec_impl.h"
59 
60 #include "option_extensions.pb.h"
61 
62 using dccl::hex_encode;
63 
64 using namespace dccl;
65 using namespace dccl::logger;
66 
67 using google::protobuf::Descriptor;
68 using google::protobuf::FieldDescriptor;
69 using google::protobuf::Reflection;
70 
71 //
72 // Codec
73 //
74 
75 dccl::Codec::Codec(std::string dccl_id_codec_name, const std::string& library_path)
76  : id_codec_(std::move(dccl_id_codec_name))
77 {
78  set_default_codecs();
79  manager_.add<DefaultIdentifierCodec>(default_id_codec_name());
80 
81  if (!library_path.empty())
82  load_library(library_path);
83  // make sure the id codec exists
84  id_codec();
85 }
86 
88 {
89  for (auto& dl_handle : dl_handles_)
90  {
91  unload_library(dl_handle);
92  dlclose(dl_handle);
93  }
94 }
95 
96 void dccl::Codec::set_default_codecs()
97 {
98  using google::protobuf::FieldDescriptor;
99 
100  //
101  // version 2
102  //
106 
107  // backwards compatibility names (deprecated)
108  internal::TimeCodecLoader<2>::add(manager_, "_time");
109  internal::StaticCodecLoader<2>::add(manager_, "_static");
110 
111  //
112  // version 3
113  //
119 
120  //
121  // version 4
122  //
128  internal::HashCodecLoader<4>::add(manager_, {2, 3, 4}); // backport for older versions 2 and 3
129 }
130 
131 void dccl::Codec::encode_internal(const google::protobuf::Message& msg, bool header_only,
132  Bitset& head_bits, Bitset& body_bits, int user_id)
133 {
134  const Descriptor* desc = msg.GetDescriptor();
135 
136  dlog.is(DEBUG1, ENCODE) && dlog << "Began encoding message of type: " << desc->full_name()
137  << std::endl;
138 
139  try
140  {
141  int32 dccl_id = id_internal(desc, user_id);
142  size_t head_byte_size = 0;
143 
144  if (!msg.IsInitialized() && !header_only)
145  {
146  std::stringstream ss;
147 
148  ss << "Message is not properly initialized. All `required` fields must be set. Fields "
149  "with errors: \n"
150  << get_all_error_fields_in_message(msg);
151 
152  throw(Exception(ss.str(), desc));
153  }
154 
155  if (!id2desc_.count(dccl_id))
156  throw(Exception("Message id " + std::to_string(dccl_id) +
157  " has not been loaded. Call load() before encoding this type."),
158  desc);
159 
160  std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
161  std::shared_ptr<internal::FromProtoCppTypeBase> helper = manager_.type_helper().find(desc);
162 
163  if (codec)
164  {
165  //fixed header
166  if (!desc->options().GetExtension(dccl::msg).omit_id())
167  id_codec()->field_encode(&head_bits, static_cast<uint32>(dccl_id), nullptr);
168 
169  internal::MessageStack msg_stack(manager_.codec_data().root_message_,
170  manager_.codec_data().message_data_);
171  msg_stack.push(msg.GetDescriptor());
172  codec->base_encode(&head_bits, msg, HEAD, strict_);
173 
174  // given header of not even byte size (e.g. 01011), make even byte size (e.g. 00001011)
175  head_byte_size = ceil_bits2bytes(head_bits.size());
176  head_bits.resize(head_byte_size * BITS_IN_BYTE);
177 
178  if (header_only)
179  {
180  dlog.is(DEBUG2, ENCODE) &&
181  dlog << "as requested, skipping encoding and encrypting body." << std::endl;
182  }
183  else
184  {
185  codec->base_encode(&body_bits, msg, BODY, strict_);
186  }
187  }
188  else
189  {
190  throw(Exception("Failed to find (dccl.msg).codec `" +
191  desc->options().GetExtension(dccl::msg).codec() + "`"),
192  desc);
193  }
194  }
195  catch (dccl::OutOfRangeException& e)
196  {
197  dlog.is(DEBUG1, ENCODE) &&
198  dlog << "Message " << desc->full_name()
199  << " failed to encode because a field was out of bounds and strict == true: "
200  << e.what() << std::endl;
201  throw;
202  }
203  catch (std::exception& e)
204  {
205  std::stringstream ss;
206 
207  ss << "Message " << desc->full_name() << " failed to encode. Reason: " << e.what();
208 
209  dlog.is(DEBUG1, ENCODE) && dlog << ss.str() << std::endl;
210  throw(Exception(ss.str(), desc));
211  }
212 }
213 
214 size_t dccl::Codec::encode(char* bytes, size_t max_len, const google::protobuf::Message& msg,
215  bool header_only /* = false */, int user_id /* = -1 */)
216 {
217  const Descriptor* desc = msg.GetDescriptor();
218  Bitset head_bits;
219  Bitset body_bits;
220  encode_internal(msg, header_only, head_bits, body_bits, user_id);
221 
222  size_t head_byte_size = ceil_bits2bytes(head_bits.size());
223  if (max_len < head_byte_size)
224  {
225  throw std::length_error("max_len must be >= head_byte_size");
226  }
227  head_bits.to_byte_string(bytes, head_byte_size);
228 
229  dlog.is(DEBUG2, ENCODE) && dlog << "Head bytes (bits): " << head_byte_size << "("
230  << head_bits.size() << ")" << std::endl;
231  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Head (bin): " << head_bits << std::endl;
232  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Head (hex): "
233  << hex_encode(bytes, bytes + head_byte_size) << std::endl;
234 
235  size_t body_byte_size = 0;
236  if (!header_only)
237  {
238  body_byte_size = ceil_bits2bytes(body_bits.size());
239  if (max_len < (head_byte_size + body_byte_size))
240  {
241  throw std::length_error("max_len must be >= (head_byte_size + body_byte_size)");
242  }
243  body_bits.to_byte_string(bytes + head_byte_size, max_len - head_byte_size);
244 
245  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Body (bin): " << body_bits << std::endl;
246  dlog.is(DEBUG3, ENCODE) &&
247  dlog << "Unencrypted Body (hex): "
248  << hex_encode(bytes + head_byte_size, bytes + head_byte_size + body_byte_size)
249  << std::endl;
250  dlog.is(DEBUG2, ENCODE) && dlog << "Body bytes (bits): " << body_byte_size << "("
251  << body_bits.size() << ")" << std::endl;
252 
253  int32 dccl_id = id_internal(desc, user_id);
254  if (!crypto_key_.empty() && !skip_crypto_ids_.count(dccl_id))
255  {
256  std::string head_bytes(bytes, bytes + head_byte_size);
257  std::string body_bytes(bytes + head_byte_size, bytes + head_byte_size + body_byte_size);
258  encrypt(&body_bytes, head_bytes);
259  std::memcpy(bytes + head_byte_size, body_bytes.data(), body_bytes.size());
260  }
261 
262  dlog.is(logger::DEBUG3, logger::ENCODE) &&
263  dlog << "Encrypted Body (hex): "
264  << hex_encode(bytes + head_byte_size, bytes + head_byte_size + body_byte_size)
265  << std::endl;
266  }
267 
268  dlog.is(DEBUG1, ENCODE) && dlog << "Successfully encoded message of type: " << desc->full_name()
269  << std::endl;
270 
271  return head_byte_size + body_byte_size;
272 }
273 
274 void dccl::Codec::encode(std::string* bytes, const google::protobuf::Message& msg,
275  bool header_only /* = false */, int user_id /* = -1 */)
276 {
277  const Descriptor* desc = msg.GetDescriptor();
278  Bitset head_bits;
279  Bitset body_bits;
280  encode_internal(msg, header_only, head_bits, body_bits, user_id);
281 
282  std::string head_bytes = head_bits.to_byte_string();
283 
284  dlog.is(DEBUG2, ENCODE) && dlog << "Head bytes (bits): " << head_bytes.size() << "("
285  << head_bits.size() << ")" << std::endl;
286  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Head (bin): " << head_bits << std::endl;
287  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Head (hex): " << hex_encode(head_bytes)
288  << std::endl;
289 
290  std::string body_bytes;
291  if (!header_only)
292  {
293  body_bytes = body_bits.to_byte_string();
294 
295  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Body (bin): " << body_bits << std::endl;
296  dlog.is(DEBUG3, ENCODE) && dlog << "Unencrypted Body (hex): " << hex_encode(body_bytes)
297  << std::endl;
298  dlog.is(DEBUG2, ENCODE) && dlog << "Body bytes (bits): " << body_bytes.size() << "("
299  << body_bits.size() << ")" << std::endl;
300 
301  int32 dccl_id = id_internal(desc, user_id);
302  if (!crypto_key_.empty() && !skip_crypto_ids_.count(dccl_id))
303  encrypt(&body_bytes, head_bytes);
304 
305  dlog.is(logger::DEBUG3, logger::ENCODE) &&
306  dlog << "Encrypted Body (hex): " << hex_encode(body_bytes) << std::endl;
307  }
308 
309  dlog.is(DEBUG1, ENCODE) && dlog << "Successfully encoded message of type: " << desc->full_name()
310  << std::endl;
311  *bytes += head_bytes + body_bytes;
312 }
313 
314 int32 dccl::Codec::id(const std::string& bytes) const { return id(bytes.begin(), bytes.end()); }
315 
316 // makes sure we can actual encode / decode a message of this descriptor given the loaded FieldCodecs
317 // checks all bounds on the message
318 std::size_t dccl::Codec::load(const google::protobuf::Descriptor* desc, int user_id /* = -1 */)
319 {
320  try
321  {
322  const auto& msg_opt = desc->options().GetExtension(dccl::msg);
323  if (user_id < 0 && !msg_opt.has_id() && !msg_opt.omit_id())
324  throw(
325  Exception("Missing message option `(dccl.msg).id`. Specify a unique id (e.g. 3) in "
326  "the body of your .proto message using \"option (dccl.msg).id = 3\"",
327  desc));
328  if (!msg_opt.has_max_bytes())
329  throw(Exception("Missing message option `(dccl.msg).max_bytes`. Specify a maximum "
330  "(encoded) message size in bytes (e.g. 32) in the body of your .proto "
331  "message using \"option (dccl.msg).max_bytes = 32\"",
332  desc));
333 
334  if (!msg_opt.has_codec_version())
335  throw(Exception(
336  "No (dccl.msg).codec_version set for DCCL Message '" + desc->full_name() +
337  "'. For new messages, set 'option (dccl.msg).codec_version = 4' in the "
338  "message definition for " +
339  desc->full_name() + " to use the default DCCL4 codecs.",
340  desc));
341 
342  std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
343 
344  int32 dccl_id = id_internal(desc, user_id);
345 
346  unsigned head_size_bits, body_size_bits;
347  codec->base_max_size(&head_size_bits, desc, HEAD);
348  codec->base_max_size(&body_size_bits, desc, BODY);
349 
350  unsigned id_bits = 0;
351  if (!msg_opt.omit_id())
352  id_codec()->field_size(&id_bits, static_cast<uint32>(dccl_id), nullptr);
353  head_size_bits += id_bits;
354 
355  const unsigned byte_size =
356  ceil_bits2bytes(head_size_bits) + ceil_bits2bytes(body_size_bits);
357 
358  auto actual_max = byte_size;
359  auto allowed_max = msg_opt.max_bytes();
360  if (actual_max > allowed_max)
361  throw(Exception("Actual maximum size of message (" + std::to_string(actual_max) +
362  "B) exceeds allowed maximum (" + std::to_string(allowed_max) +
363  "B). Tighten "
364  "bounds, remove fields, improve codecs, or increase the allowed "
365  "value in (dccl.msg).max_bytes",
366  desc));
367 
368  codec->base_validate(desc, HEAD);
369  codec->base_validate(desc, BODY);
370 
371  if (id2desc_.count(dccl_id) && desc != id2desc_.find(dccl_id)->second)
372  {
373  std::stringstream ss;
374  ss << "`dccl id` " << dccl_id << " is already in use by Message "
375  << id2desc_.find(dccl_id)->second->full_name() << ": "
376  << id2desc_.find(dccl_id)->second;
377 
378  throw(Exception(ss.str(), desc));
379  }
380  else
381  {
382  id2desc_.insert(std::make_pair(dccl_id, desc));
383  }
384 
385  dlog.is(DEBUG1) && dlog << "Successfully validated message of type: " << desc->full_name()
386  << std::endl;
387 
388  std::size_t hash_value = 0;
389  codec->base_hash(&hash_value, desc, HEAD);
390  codec->base_hash(&hash_value, desc, BODY);
391  manager_.set_hash(desc, hash_value);
392  return hash_value;
393  }
394  catch (Exception& e)
395  {
396  try
397  {
398  info(desc, &dlog);
399  }
400  catch (Exception& e)
401  {
402  }
403 
404  dlog.is(DEBUG1) && dlog << "Message " << desc->full_name() << ": " << desc
405  << " failed validation. Reason: " << e.what() << "\n"
406  << "If possible, information about the Message are printed above. "
407  << std::endl;
408 
409  throw;
410  }
411 }
412 
413 void dccl::Codec::unload(const google::protobuf::Descriptor* desc)
414 {
415  unsigned int erased = 0;
416  for (auto it = id2desc_.begin(); it != id2desc_.end();)
417  {
418  if (it->second == desc)
419  {
420  erased++;
421  id2desc_.erase(it++);
422  }
423  else
424  {
425  it++;
426  }
427  }
428  if (erased == 0)
429  {
430  dlog.is(DEBUG1) && dlog << "Message " << desc->full_name()
431  << ": is not loaded. Ignoring unload request." << std::endl;
432  }
433 }
434 
435 void dccl::Codec::unload(size_t dccl_id)
436 {
437  if (id2desc_.count(dccl_id))
438  {
439  id2desc_.erase(dccl_id);
440  }
441  else
442  {
443  dlog.is(DEBUG1) && dlog << "Message with id " << dccl_id
444  << ": is not loaded. Ignoring unload request." << std::endl;
445  return;
446  }
447 }
448 
449 unsigned dccl::Codec::size(const google::protobuf::Message& msg, int user_id /* = -1 */)
450 {
451  const Descriptor* desc = msg.GetDescriptor();
452 
453  std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
454 
455  int32 dccl_id = id_internal(desc, user_id);
456  unsigned head_size_bits;
457  codec->base_size(&head_size_bits, msg, HEAD);
458 
459  unsigned id_bits = 0;
460  if (!desc->options().GetExtension(dccl::msg).omit_id())
461  id_codec()->field_size(&id_bits, static_cast<uint32>(dccl_id), nullptr);
462  head_size_bits += id_bits;
463 
464  unsigned body_size_bits;
465  codec->base_size(&body_size_bits, msg, BODY);
466 
467  const unsigned head_size_bytes = ceil_bits2bytes(head_size_bits);
468  const unsigned body_size_bytes = ceil_bits2bytes(body_size_bits);
469  return head_size_bytes + body_size_bytes;
470 }
471 
472 unsigned dccl::Codec::max_size(const google::protobuf::Descriptor* desc) const
473 {
474  std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
475 
476  unsigned head_size_bits;
477  codec->base_max_size(&head_size_bits, desc, HEAD);
478 
479  unsigned id_bits = 0;
480  if (!desc->options().GetExtension(dccl::msg).omit_id())
481  id_codec()->field_max_size(&id_bits, nullptr);
482  head_size_bits += id_bits;
483 
484  unsigned body_size_bits;
485  codec->base_max_size(&body_size_bits, desc, BODY);
486 
487  const unsigned head_size_bytes = ceil_bits2bytes(head_size_bits);
488  const unsigned body_size_bytes = ceil_bits2bytes(body_size_bits);
489  return head_size_bytes + body_size_bytes;
490 }
491 
492 unsigned dccl::Codec::min_size(const google::protobuf::Descriptor* desc) const
493 {
494  std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
495 
496  unsigned head_size_bits;
497  codec->base_min_size(&head_size_bits, desc, HEAD);
498 
499  unsigned id_bits = 0;
500  if (!desc->options().GetExtension(dccl::msg).omit_id())
501  id_codec()->field_min_size(&id_bits, nullptr);
502  head_size_bits += id_bits;
503 
504  unsigned body_size_bits;
505  codec->base_min_size(&body_size_bits, desc, BODY);
506 
507  const unsigned head_size_bytes = ceil_bits2bytes(head_size_bits);
508  const unsigned body_size_bytes = ceil_bits2bytes(body_size_bits);
509  return head_size_bytes + body_size_bytes;
510 }
511 
512 void dccl::Codec::info(const google::protobuf::Descriptor* desc, std::ostream* param_os /*= 0 */,
513  int user_id /* = -1 */) const
514 {
515  bool is_dlog = false;
516  if (!param_os || param_os == &dlog)
517  is_dlog = true;
518  std::stringstream ss;
519  std::ostream* os = (is_dlog) ? &ss : param_os;
520 
521  if (!is_dlog || dlog.check(INFO))
522  {
523  try
524  {
525  bool omit_id = desc->options().GetExtension(dccl::msg).omit_id();
526 
527  std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
528 
529  unsigned config_head_bit_size, body_bit_size;
530  codec->base_max_size(&config_head_bit_size, desc, HEAD);
531  codec->base_max_size(&body_bit_size, desc, BODY);
532 
533  int32 dccl_id = id_internal_const(desc, user_id);
534  unsigned id_bit_size = 0;
535  if (!omit_id)
536  id_codec()->field_size(&id_bit_size, static_cast<uint32>(dccl_id), nullptr);
537 
538  const unsigned bit_size = id_bit_size + config_head_bit_size + body_bit_size;
539 
540  const unsigned byte_size = ceil_bits2bytes(config_head_bit_size + id_bit_size) +
541  ceil_bits2bytes(body_bit_size);
542 
543  const unsigned allowed_byte_size = desc->options().GetExtension(dccl::msg).max_bytes();
544  const unsigned allowed_bit_size = allowed_byte_size * BITS_IN_BYTE;
545 
546  std::string hash;
547  if (manager_.has_hash(desc))
548  hash = hash_as_string(manager_.hash(desc));
549 
550  std::string message_name;
551  if (!omit_id)
552  message_name += std::to_string(dccl_id) + ": ";
553  message_name += desc->full_name() + " {" + hash + "}";
554  std::string guard = build_guard_for_console_output(message_name, '=');
555  std::string bits_dccl_head_str = "dccl.id head";
556  std::string bits_user_head_str = "user head";
557  std::string bits_body_str = "body";
558  std::string bits_padding_str = "padding to full byte";
559 
560  const int bits_width = 40;
561  const int spaces = 8;
562  std::string indent = std::string(spaces, ' ');
563 
564  *os << guard << " " << message_name << " " << guard << "\n"
565  << "Actual maximum size of message: " << byte_size << " bytes / "
566  << byte_size * BITS_IN_BYTE << " bits\n"
567  << indent << bits_dccl_head_str << std::setfill('.')
568  << std::setw(bits_width - bits_dccl_head_str.size()) << id_bit_size << "\n"
569  << indent << bits_user_head_str << std::setfill('.')
570  << std::setw(bits_width - bits_user_head_str.size()) << config_head_bit_size << "\n"
571  << indent << bits_body_str << std::setfill('.')
572  << std::setw(bits_width - bits_body_str.size()) << body_bit_size << "\n"
573  << indent << bits_padding_str << std::setfill('.')
574  << std::setw(bits_width - bits_padding_str.size())
575  << byte_size * BITS_IN_BYTE - bit_size << "\n"
576  << "Allowed maximum size of message: " << allowed_byte_size << " bytes / "
577  << allowed_bit_size << " bits\n";
578 
579  std::string header_str = "Header";
580  std::string header_guard = build_guard_for_console_output(header_str, '-');
581 
582  *os << header_guard << " " << header_str << " " << header_guard << "\n";
583  *os << bits_dccl_head_str << std::setfill('.')
584  << std::setw(bits_width - bits_dccl_head_str.size() + spaces) << id_bit_size << " {"
585  << (omit_id ? std::string("omit_id: true") : id_codec()->name()) << "}\n";
586  codec->base_info(os, desc, HEAD);
587  // *os << std::string(header_str.size() + 2 + 2*header_guard.size(), '-') << "\n";
588 
589  std::string body_str = "Body";
590  std::string body_guard = build_guard_for_console_output(body_str, '-');
591 
592  *os << body_guard << " " << body_str << " " << body_guard << "\n";
593  codec->base_info(os, desc, BODY);
594  // *os << std::string(body_str.size() + 2 + 2*body_guard.size(), '-') << "\n";
595 
596  // *os << std::string(desc->full_name().size() + 2 + 2*guard.size(), '=') << "\n";
597  os->flush();
598 
599  if (is_dlog)
600  dlog.is(INFO) && dlog << ss.str() << std::endl;
601  }
602  catch (Exception& e)
603  {
604  dlog.is(DEBUG1) &&
605  dlog << "Message " << desc->full_name()
606  << " cannot provide information due to invalid configuration. Reason: "
607  << e.what() << std::endl;
608  }
609  }
610 }
611 
612 void dccl::Codec::encrypt(std::string* s, const std::string& nonce /* message head */)
613 {
614 #if DCCL_HAS_CRYPTOPP
615  using namespace CryptoPP;
616 
617  std::string iv;
618  SHA256 hash;
619  StringSource unused(nonce, true, new HashFilter(hash, new StringSink(iv)));
620 
621  CTR_Mode<AES>::Encryption encryptor;
622  encryptor.SetKeyWithIV((byte*)crypto_key_.c_str(), crypto_key_.size(), (byte*)iv.c_str());
623 
624  std::string cipher;
625  StreamTransformationFilter in(encryptor, new StringSink(cipher));
626  in.Put((byte*)s->c_str(), s->size());
627  in.MessageEnd();
628  *s = cipher;
629 #endif
630 }
631 
632 void dccl::Codec::decrypt(std::string* s, const std::string& nonce)
633 {
634 #if DCCL_HAS_CRYPTOPP
635  using namespace CryptoPP;
636 
637  std::string iv;
638  SHA256 hash;
639  StringSource unused(nonce, true, new HashFilter(hash, new StringSink(iv)));
640 
641  CTR_Mode<AES>::Decryption decryptor;
642  decryptor.SetKeyWithIV((byte*)crypto_key_.c_str(), crypto_key_.size(), (byte*)iv.c_str());
643 
644  std::string recovered;
645 
646  StreamTransformationFilter out(decryptor, new StringSink(recovered));
647  out.Put((byte*)s->c_str(), s->size());
648  out.MessageEnd();
649  *s = recovered;
650 #endif
651 }
652 
653 void dccl::Codec::load_library(const std::string& library_path)
654 {
655  void* handle = dlopen(library_path.c_str(), RTLD_LAZY);
656  if (handle)
657  dl_handles_.push_back(handle);
658  load_library(handle);
659 }
660 
661 void dccl::Codec::load_library(void* dl_handle)
662 {
663  if (!dl_handle)
664  throw(Exception("Null shared library handle passed to load_library"));
665 
666  // load any shared library codecs
667  void (*dccl_load_ptr)(dccl::Codec*);
668  dccl_load_ptr = (void (*)(dccl::Codec*))dlsym(dl_handle, "dccl3_load");
669  if (dccl_load_ptr)
670  (*dccl_load_ptr)(this);
671 }
672 
673 void dccl::Codec::unload_library(void* dl_handle)
674 {
675  if (!dl_handle)
676  throw(Exception("Null shared library handle passed to unload_library"));
677 
678  // unload any shared library codecs
679  void (*dccl_unload_ptr)(dccl::Codec*);
680  dccl_unload_ptr = (void (*)(dccl::Codec*))dlsym(dl_handle, "dccl3_unload");
681  if (dccl_unload_ptr)
682  (*dccl_unload_ptr)(this);
683 }
684 
686  const std::string& passphrase,
687  const std::set<int32>& do_not_encrypt_ids /*= std::set<int32>()*/)
688 {
689  if (!crypto_key_.empty())
690  crypto_key_.clear();
691  skip_crypto_ids_.clear();
692 
693 #if DCCL_HAS_CRYPTOPP
694  using namespace CryptoPP;
695 
696  SHA256 hash;
697  StringSource unused(passphrase, true, new HashFilter(hash, new StringSink(crypto_key_)));
698 
699  dlog.is(DEBUG1) && dlog << "Cryptography enabled with given passphrase" << std::endl;
700 #else
701  dlog.is(DEBUG1) && dlog << "Cryptography disabled because DCCL was compiled without support of "
702  "Crypto++. Install Crypto++ and recompile to enable cryptography."
703  << std::endl;
704 #endif
705 
706  skip_crypto_ids_ = do_not_encrypt_ids;
707 }
708 
709 void dccl::Codec::info_all(std::ostream* param_os /*= 0 */) const
710 {
711  bool is_dlog = false;
712  if (!param_os || param_os == &dlog)
713  is_dlog = true;
714  std::stringstream ss;
715  std::ostream* os = (is_dlog) ? &ss : param_os;
716  if (!is_dlog || dlog.check(INFO))
717  {
718  std::string codec_str = "Dynamic Compact Control Language (DCCL) Codec";
719  std::string codec_guard = build_guard_for_console_output(codec_str, '|');
720 
721  *os << codec_guard << " " << codec_str << " " << codec_guard << "\n";
722  *os << id2desc_.size() << " messages loaded.\n";
723  *os << "Field sizes are in bits unless otherwise noted."
724  << "\n";
725 
726  for (auto it : id2desc_) info(it.second, os, it.first);
727  os->flush();
728 
729  if (is_dlog)
730  dlog.is(INFO) && dlog << ss.str() << std::endl;
731  }
732 }
733 
734 void dccl::Codec::set_id_codec(const std::string& id_codec_name)
735 {
736  // we must reload messages after setting the id_codec
737  unload_all();
738 
739  id_codec_ = id_codec_name;
740  // make sure the id codec exists
741  id_codec();
742 }
743 
744 std::string dccl::Codec::build_guard_for_console_output(std::string& base, char guard_char) const
745 {
746  // Only guard if possible, otherwise return an empty string rather than throwing a std::length_error.
747  return (base.size() < console_width_)
748  ? std::string((console_width_ - base.size()) / 2, guard_char)
749  : std::string();
750 }
751 
752 std::string dccl::Codec::get_all_error_fields_in_message(const google::protobuf::Message& message,
753  uint8_t depth /*= 1 */)
754 {
755  // This is largely taken from google::protobuf::ReflectionOps::FindInitializationErrors(), which is called by
756  // google::protobuf::Message::FindInitializationErrors(). The Message implementation is not used as they force a
757  // prefix of parent message onto fields, which would require a split function to break by delimiter '.' should we
758  // want to reflect upon sub-messages and get field numbers.
759 
760  std::stringstream output_stream;
761 
762  const google::protobuf::Descriptor* descriptor = message.GetDescriptor();
763  const google::protobuf::Reflection* reflection = message.GetReflection();
764  const uint8_t depth_spacing = 4;
765 
766  // Check required fields of this message.
767  const int32_t field_count = descriptor->field_count();
768 
769  for (int32_t index = 0; index < field_count; ++index)
770  {
771  if (descriptor->field(index)->is_required())
772  {
773  if (!reflection->HasField(message, descriptor->field(index)))
774  {
775  output_stream << std::string(depth * depth_spacing, ' ')
776  << descriptor->field(index)->number() << ": "
777  << descriptor->field(index)->name() << "\n";
778  }
779  }
780  }
781 
782  // Check sub-messages.
783  std::vector<const google::protobuf::FieldDescriptor*> fields;
784  reflection->ListFields(message, &fields);
785 
786  for (const google::protobuf::FieldDescriptor* field : fields)
787  {
788  if (field->cpp_type() == google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE)
789  {
790  output_stream << std::string(depth * depth_spacing, ' ') << field->number() << ": "
791  << field->name() << "\n";
792 
793  if (field->is_repeated())
794  {
795  int32_t size = reflection->FieldSize(message, field);
796 
797  for (int32_t index = 0; index < size; ++index)
798  {
799  const google::protobuf::Message& sub_message =
800  reflection->GetRepeatedMessage(message, field, index);
801 
802  output_stream << std::string((depth + 1) * depth_spacing, ' ') << "[" << index
803  << "]: "
804  << "\n";
805 
806  output_stream << get_all_error_fields_in_message(sub_message, depth + 2);
807  }
808  }
809  else
810  {
811  const google::protobuf::Message& sub_message =
812  reflection->GetMessage(message, field);
813  output_stream << get_all_error_fields_in_message(sub_message, depth + 1);
814  }
815  }
816  }
817 
818  return output_stream.str();
819 }
dccl
Dynamic Compact Control Language namespace.
Definition: any.h:49
dccl::internal::DefaultFieldCodecLoader
Definition: default_field_codec.h:45
dccl::Logger::is
bool is(logger::Verbosity verbosity, logger::Group group=logger::GENERAL)
Indicates the verbosity of the Logger until the next std::flush or std::endl. The boolean return is u...
Definition: logger.h:192
dccl::Exception
Exception class for DCCL.
Definition: exception.h:47
dccl::Codec::load
std::size_t load()
All messages must be explicited loaded and validated (size checks, option extensions checks,...
Definition: codec.h:121
dccl::internal::StaticCodecLoader
Definition: default_field_codec.h:56
dccl::Codec::unload
void unload()
Unload a given message.
Definition: codec.h:129
dccl::Codec::set_crypto_passphrase
void set_crypto_passphrase(const std::string &passphrase, const std::set< int32 > &do_not_encrypt_ids=std::set< int32 >())
Set a passphrase to be used when encoded messages to encrypt them and to decrypt messages after decod...
Definition: codec.cpp:685
dccl::Codec::set_id_codec
void set_id_codec(const std::string &id_codec_name)
Set a different ID codec name (note that is calls unload_all() so all messages must be reloaded)
Definition: codec.cpp:734
dccl::Codec::min_size
unsigned min_size()
Provides the encoded minimum size (in bytes) of msg.
Definition: codec.h:375
dccl::Codec::max_size
unsigned max_size()
Provides the encoded maximum size (in bytes) of msg.
Definition: codec.h:363
dccl::uint32
google::protobuf::uint32 uint32
an unsigned 32 bit integer
Definition: common.h:56
dccl::internal::MessageStack
Definition: field_codec_message_stack.h:72
dccl::Codec
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
Definition: codec.h:62
dccl::Codec::Codec
Codec(std::string dccl_id_codec_name=default_id_codec_name(), const std::string &library_path="")
Instantiate a Codec, optionally with a non-default identifier field codec (loaded via a shared librar...
Definition: codec.cpp:75
dccl::Codec::size
unsigned size(const google::protobuf::Message &msg, int user_id=-1)
Provides the encoded size (in bytes) of msg. This is useful if you need to know the size of a message...
Definition: codec.cpp:449
dccl::Codec::unload_library
void unload_library(void *dl_handle)
Remove codecs and/or unload messages present in the given shared library handle.
Definition: codec.cpp:673
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::Bitset
A variable size container of bits (subclassed from std::deque<bool>) with an optional hierarchy....
Definition: bitset.h:41
dccl::OutOfRangeException
Definition: exception.h:68
dccl::internal::HashCodecLoader
Definition: default_field_codec.h:73
dccl::Codec::load_library
void load_library(void *dl_handle)
Add codecs and/or load messages present in the given shared library handle.
Definition: codec.cpp:661
dccl::Codec::id
int32 id() const
Gives the DCCL id (defined by the custom message option extension "(dccl.msg).id" in the ....
Definition: codec.h:218
dccl::DefaultIdentifierCodec
Provides the default 1 byte or 2 byte DCCL ID codec.
Definition: field_codec_id.h:29
dccl::Logger::check
bool check(logger::Verbosity verbosity)
Same as is() but doesn't set the verbosity or lock the mutex.
Definition: logger.h:181
dccl::FieldCodecManagerLocal::add
std::enable_if< std::is_base_of< google::protobuf::Message, typename Codec::wire_type >::value &&!std::is_same< google::protobuf::Message, typename Codec::wire_type >::value, void >::type add(const std::string &name)
Add a new field codec (used for codecs operating on statically generated Protobuf messages,...
Definition: field_codec_manager.h:313
dccl::internal::PresenceCodecLoader
Definition: default_field_codec.h:61
dccl::Codec::encode
void encode(std::string *bytes, const google::protobuf::Message &msg, bool header_only=false, int user_id=-1)
Encodes a DCCL message.
Definition: codec.cpp:274
Message
dccl::internal::VarBytesCodecLoader
Definition: default_field_codec.h:67
dccl::Codec::info_all
void info_all(std::ostream *os=nullptr) const
Writes a human readable summary (including field sizes) of all the loaded (validated) DCCL types.
Definition: codec.cpp:709
dccl::Codec::info
void info(std::ostream *os=nullptr, int user_id=-1) const
Writes a human readable summary (including field sizes) of the provided DCCL type to the stream provi...
Definition: codec.h:197
dccl::int32
google::protobuf::int32 int32
a signed 32 bit integer
Definition: common.h:58
dccl::internal::TimeCodecLoader
Definition: default_field_codec.h:51
dccl::Codec::~Codec
virtual ~Codec()
Destructor.
Definition: codec.cpp:87
dccl::Bitset::to_byte_string
std::string to_byte_string()
Returns the value of the Bitset to a byte string, where each character represents 8 bits of the Bitse...
Definition: bitset.h:296