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