DCCL v4
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_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
61
62using namespace dccl;
63using namespace dccl::logger;
64
65using google::protobuf::Descriptor;
66using google::protobuf::FieldDescriptor;
67using google::protobuf::Reflection;
68
69//
70// Codec
71//
72
73dccl::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
94void 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
129void 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 }
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
212size_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
272void 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
312int32 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
316std::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
411void 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
433void 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
447unsigned 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
470unsigned 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
490unsigned 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
510void 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
610void 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
630void 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
651void 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
659void 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
671void 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
707void 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
732void 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
742std::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
750std::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}
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:63
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
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
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
unsigned min_size()
Provides the encoded minimum size (in bytes) of msg.
Definition codec.h:375
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
int32 id() const
Gives the DCCL id (defined by the custom message option extension "(dccl.msg).id" in the ....
Definition codec.h:218
void unload_library(void *dl_handle)
Remove codecs and/or unload messages present in the given shared library handle.
Definition codec.cpp:671
unsigned max_size()
Provides the encoded maximum size (in bytes) of msg.
Definition codec.h:363
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
void unload()
Unload a given message.
Definition codec.h:129
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
virtual ~Codec()
Destructor.
Definition codec.cpp:85
void load_library(void *dl_handle)
Add codecs and/or load messages present in the given shared library handle.
Definition codec.cpp:659
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
std::size_t load()
All messages must be explicited loaded and validated (size checks, option extensions checks,...
Definition codec.h:121
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:192
bool check(logger::Verbosity verbosity)
Same as is() but doesn't set the verbosity or lock the mutex.
Definition logger.h:181
Dynamic Compact Control Language namespace.
Definition any.h:47
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:100