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