DCCL v5
Loading...
Searching...
No Matches
codec.h
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// Nathan Knotts <nknotts@gmail.com>
8// philboske <philboske@gmail.com>
9// Chris Murphy <cmurphy@aphysci.com>
10//
11//
12// This file is part of the Dynamic Compact Control Language Library
13// ("DCCL").
14//
15// DCCL is free software: you can redistribute it and/or modify
16// it under the terms of the GNU Lesser General Public License as published by
17// the Free Software Foundation, either version 2.1 of the License, or
18// (at your option) any later version.
19//
20// DCCL is distributed in the hope that it will be useful,
21// but WITHOUT ANY WARRANTY; without even the implied warranty of
22// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23// GNU Lesser General Public License for more details.
24//
25// You should have received a copy of the GNU Lesser General Public License
26// along with DCCL. If not, see <http://www.gnu.org/licenses/>.
27#ifndef DCCL20091211H
28#define DCCL20091211H
29
30#include <map>
31#include <ostream>
32#include <set>
33#include <stdexcept>
34#include <string>
35#include <type_traits>
36#include <vector>
37
38#include <google/protobuf/descriptor.h>
39
40#include <memory>
41
42#include "binary.h"
43#include "dynamic_protobuf_manager.h"
44#include "exception.h"
45#include "field_codec.h"
46#include "field_codec_fixed.h"
47#include "logger.h"
48
49#include "dccl/def.h"
50#include "dccl/version.h"
51#include "field_codec_manager.h"
52
54namespace dccl
55{
56class FieldCodec;
57
60class Codec
61{
62 public:
68 Codec(std::string dccl_id_codec_name = default_id_codec_name(),
69 const std::string& library_path = "");
70
77 template <class IDFieldCodec,
78 typename std::enable_if<std::is_base_of<FieldCodecBase, IDFieldCodec>::value,
79 int>::type = 0>
80 Codec(const std::string& dccl_id_codec_name, const IDFieldCodec& dccl_id_codec) // NOLINT
81 : id_codec_(dccl_id_codec_name)
82 {
83 set_default_codecs();
84 manager_.add<IDFieldCodec>(dccl_id_codec_name);
85 }
86
88 virtual ~Codec();
89
90 Codec(const Codec&) = delete;
91 Codec& operator=(const Codec&) = delete;
92
98 void load_library(void* dl_handle);
99
106 void unload_library(void* dl_handle);
107
112 void load_library(const std::string& library_path);
113
119 template <typename ProtobufMessage> std::size_t load()
120 {
121 return load(ProtobufMessage::descriptor());
122 }
123
127 template <typename ProtobufMessage> void unload() { unload(ProtobufMessage::descriptor()); }
128
129 void unload_all() { id2desc_.clear(); }
130
138 std::size_t load(const google::protobuf::Descriptor* desc, int user_id = -1);
139
144 void unload(const google::protobuf::Descriptor* desc);
145
150 void unload(size_t dccl_id);
151
153 void set_id_codec(const std::string& id_codec_name);
154 std::string get_id_codec() { return id_codec_; }
155
161 void set_crypto_passphrase(const std::string& passphrase,
162 const std::set<int32>& do_not_encrypt_ids = std::set<int32>());
163
164 void set_crypto_passphrase(const std::string& passphrase,
165 const std::set<unsigned>& do_not_encrypt_ids)
166 {
167 std::set<int32> s_ids{do_not_encrypt_ids.begin(), do_not_encrypt_ids.end()};
168 set_crypto_passphrase(passphrase, s_ids);
169 }
170
174 void set_strict(bool mode) { strict_ = mode; }
175
179 void set_console_width(unsigned num_chars) { console_width_ = num_chars; }
180
182
186
187
194 template <typename ProtobufMessage>
195 void info(std::ostream* os = nullptr, int user_id = -1) const
196 {
197 info(ProtobufMessage::descriptor(), os, user_id);
198 }
199
205 void info(const google::protobuf::Descriptor* desc, std::ostream* os = nullptr,
206 int user_id = -1) const;
207
211 void info_all(std::ostream* os = nullptr) const;
212
216 template <typename ProtobufMessage> int32 id() const
217 {
218 return id(ProtobufMessage::descriptor());
219 }
220
240 int32 id(const std::string& bytes) const;
241
243 template <typename CharIterator> int32 id(CharIterator begin, CharIterator end) const;
244
246 int32 id(const google::protobuf::Descriptor* desc) const
247 {
248 if (desc->options().GetExtension(dccl::msg).omit_id())
249 throw(Exception("Cannot call id(...) on message with omit_id == true"));
250 Bitset id_bits;
251 dccl::uint32 hardcoded_id = desc->options().GetExtension(dccl::msg).id();
252 // pass the hard coded id, that is, (dccl.msg).id,
253 // through encode/decode to allow a custom ID codec (if in use)
254 // to always take effect.
255 id_codec()->field_encode(&id_bits, hardcoded_id, nullptr);
256 std::string id_bytes(id_bits.to_byte_string());
257 return id(id_bytes);
258 }
259
261 const std::map<int32, const google::protobuf::Descriptor*>& loaded() const { return id2desc_; }
262
264
268
269
277 void encode(std::string* bytes, const google::protobuf::Message& msg, bool header_only = false,
278 int user_id = -1);
279
290 size_t encode(char* bytes, size_t max_len, const google::protobuf::Message& msg,
291 bool header_only = false, int user_id = -1);
292
301 template <typename CharIterator, typename ProtobufMessage>
302 CharIterator decode(CharIterator begin, CharIterator end, ProtobufMessage* msg,
303 bool header_only = false);
304
311 template <typename ProtobufMessage>
312 void decode(const std::string& bytes, ProtobufMessage* msg, bool header_only = false)
313 {
314 decode(bytes.begin(), bytes.end(), msg, header_only);
315 }
316
322 template <typename ProtobufMessage> void decode(std::string* bytes, ProtobufMessage* msg)
323 {
324 decode(*bytes, msg);
325 unsigned last_size = size(*msg);
326 bytes->erase(0, last_size);
327 }
328
336 template <typename GoogleProtobufMessagePointer>
337 GoogleProtobufMessagePointer decode(const std::string& bytes, bool header_only = false);
338
345 template <typename GoogleProtobufMessagePointer>
346 GoogleProtobufMessagePointer decode(std::string* bytes);
347
354 unsigned size(const google::protobuf::Message& msg, int user_id = -1);
355
360 template <typename ProtobufMessage> unsigned max_size()
361 {
362 return max_size(ProtobufMessage::descriptor());
363 }
364
366 unsigned max_size(const google::protobuf::Descriptor* desc, int user_id = -1) const;
367
372 template <typename ProtobufMessage> unsigned min_size()
373 {
374 return min_size(ProtobufMessage::descriptor());
375 }
376
378 unsigned min_size(const google::protobuf::Descriptor* desc, int user_id = -1) const;
379
381
382 static std::string default_id_codec_name() { return "dccl.default.id"; }
383
384 static std::string default_codec_name(int version = 2)
385 {
386 return "dccl.default" + std::to_string(version);
387 }
388
389 FieldCodecManagerLocal& manager() { return manager_; }
390
391 private:
392 void encode_internal(const google::protobuf::Message& msg, bool header_only,
393 Bitset& header_bits, Bitset& body_bits, int user_id);
394 std::string get_all_error_fields_in_message(const google::protobuf::Message& msg,
395 uint8_t depth = 1);
396
397 void encrypt(std::string* s, const std::string& nonce);
398 void decrypt(std::string* s, const std::string& nonce);
399
400 void set_default_codecs();
401
402 std::shared_ptr<FieldCodecBase> id_codec() const
403 {
404 return manager_.find(google::protobuf::FieldDescriptor::TYPE_UINT32, DCCL_VERSION_MAJOR,
405 id_codec_);
406 }
407
408 int32 id_internal(const google::protobuf::Descriptor* desc, int user_id)
409 {
410 // if we have omit_id, check for or assign an autogenerate negative internal placeholder ID
411 if (desc->options().GetExtension(dccl::msg).omit_id() && !desc2placeholder_id_.count(desc))
412 desc2placeholder_id_.emplace(desc, omit_id_placeholder_id_--);
413
414 return id_internal_const(desc, user_id);
415 }
416
417 int32 id_internal_const(const google::protobuf::Descriptor* desc, int user_id) const
418 {
419 if (desc->options().GetExtension(dccl::msg).omit_id())
420 {
421 if (desc2placeholder_id_.count(desc))
422 return desc2placeholder_id_.find(desc)->second;
423 else
424 throw(Exception("Message " + std::string(desc->full_name()) +
425 " has omit_id == true but has not been loaded, so id_internal() "
426 "const cannot be called"));
427 }
428 else
429 {
430 return (user_id < 0) ? id(desc) : user_id;
431 }
432 }
433
434 private:
435 // SHA256 hash of the crypto passphrase
436 std::string crypto_key_;
437
438 // strict mode setting
439 bool strict_{false};
440
441 // console outputting format width
442 unsigned console_width_{60};
443
444 // set of DCCL IDs *not* to encrypt
445 std::set<int32> skip_crypto_ids_;
446
447 // maps `dccl.id`s onto Message Descriptors
448 std::map<int32, const google::protobuf::Descriptor*> id2desc_;
449 std::string id_codec_;
450
451 std::vector<void*> dl_handles_;
452
453 std::string build_guard_for_console_output(std::string& base, char guard_char) const;
454
455 FieldCodecManagerLocal manager_;
456
457 // current omit_id placeholder DCCL Id (starts at -1 and decrements)
458 int32 omit_id_placeholder_id_{-1};
459 // maps message descriptor onto placeholder ID for omit_id messages
460 std::map<const google::protobuf::Descriptor*, int32> desc2placeholder_id_;
461};
462
463inline std::ostream& operator<<(std::ostream& os, const Codec& codec)
464{
465 codec.info_all(&os);
466 return os;
467}
468} // namespace dccl
469
470template <typename GoogleProtobufMessagePointer>
471GoogleProtobufMessagePointer dccl::Codec::decode(const std::string& bytes,
472 bool header_only /* = false */)
473{
474 int32 this_id = id(bytes);
475
476 if (!id2desc_.count(this_id))
477 throw(Exception("Message id " + std::to_string(this_id) +
478 " has not been loaded. Call load() before decoding this type."));
479
480 // ownership of this object goes to the caller of decode()
481 auto msg = dccl::DynamicProtobufManager::new_protobuf_message<GoogleProtobufMessagePointer>(
482 id2desc_.find(this_id)->second);
483 decode(bytes, &(*msg), header_only);
484 return msg;
485}
486
487template <typename GoogleProtobufMessagePointer>
488GoogleProtobufMessagePointer dccl::Codec::decode(std::string* bytes)
489{
490 int32 this_id = id(*bytes);
491
492 if (!id2desc_.count(this_id))
493 throw(Exception("Message id " + std::to_string(this_id) +
494 " has not been loaded. Call load() before decoding this type."));
495
496 GoogleProtobufMessagePointer msg =
497 dccl::DynamicProtobufManager::new_protobuf_message<GoogleProtobufMessagePointer>(
498 id2desc_.find(this_id)->second);
499 std::string::iterator new_begin = decode(bytes->begin(), bytes->end(), &(*msg));
500 bytes->erase(bytes->begin(), new_begin);
501 return msg;
502}
503
504template <typename CharIterator>
505dccl::int32 dccl::Codec::id(CharIterator begin, CharIterator end) const
506{
507 try
508 {
509 unsigned id_min_size = 0, id_max_size = 0;
510 id_codec()->field_min_size(&id_min_size, nullptr);
511 id_codec()->field_max_size(&id_max_size, nullptr);
512 Bitset fixed_header_bits;
513
514 // ensure we don't go past-the-end if fewer bytes are passed in than id_max_size
515 int incr = std::min<size_t>(
516 static_cast<size_t>(std::distance(begin, end)),
517 static_cast<size_t>(std::ceil(static_cast<double>(id_max_size) / BITS_IN_BYTE)));
518 fixed_header_bits.from_byte_stream(begin, begin + incr);
519
520 Bitset these_bits(&fixed_header_bits);
521 these_bits.get_more_bits(id_min_size);
522
523 dccl::any return_value;
524 id_codec()->field_decode(&these_bits, &return_value, nullptr);
525 return dccl::any_cast<uint32>(return_value);
526 }
527 catch (const dccl::Exception& e)
528 {
529 throw(Exception("Failed to decoded id from bytes passed (hex: " + hex_encode(begin, end) +
530 ")"));
531 }
532}
533
534template <typename CharIterator, typename ProtobufMessage>
535CharIterator dccl::Codec::decode(CharIterator begin, CharIterator end, ProtobufMessage* msg,
536 bool header_only /*= false*/)
537{
538 try
539 {
540 const google::protobuf::Descriptor* desc = msg->GetDescriptor();
541 int32 expected_id = id_internal(desc, -1);
542 int32 received_id =
543 expected_id; // if omit_id, we have to assume we have the correct type. Otherwise, overwrite if not omit_id and check
544 if (!desc->options().GetExtension(dccl::msg).omit_id())
545 {
546 received_id = id(begin, end);
547
548 if (!id2desc_.count(received_id))
549 throw(Exception("Message id " + std::to_string(received_id) +
550 " has not been loaded. Call load() before decoding this type."));
551
552 if (expected_id != received_id)
553 throw(Exception("Received message with id " + std::to_string(received_id) + " (" +
554 std::string(id2desc_.at(received_id)->full_name()) +
555 ") but decode was called with message of id " +
556 std::to_string(expected_id) + " (" +
557 std::string(desc->full_name()) +
558 "). Ensure dccl::Codec::decode is called with the correct Protobuf "
559 "message or use the dynamic overloads of decode."));
560 }
561
562 dlog.is(logger::DEBUG1, logger::DECODE) &&
563 dlog << "Began decoding message of id: " << received_id << std::endl;
564
565 dlog.is(logger::DEBUG1, logger::DECODE) && dlog << "Type name: " << desc->full_name()
566 << std::endl;
567
568 std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
569 std::shared_ptr<internal::FromProtoCppTypeBase> helper = manager_.type_helper().find(desc);
570
571 CharIterator actual_end = end;
572 if (codec)
573 {
574 unsigned head_size_bits;
575 unsigned body_size_bits;
576 codec->base_max_size(&head_size_bits, desc, HEAD);
577 codec->base_max_size(&body_size_bits, desc, BODY);
578 unsigned id_size = 0;
579 if (!desc->options().GetExtension(dccl::msg).omit_id())
580 id_codec()->field_size(&id_size, static_cast<uint32>(received_id), nullptr);
581 head_size_bits += id_size;
582
583 unsigned head_size_bytes = ceil_bits2bytes(head_size_bits);
584 unsigned body_size_bytes = ceil_bits2bytes(body_size_bits);
585
586 dlog.is(logger::DEBUG2, logger::DECODE) &&
587 dlog << "Head bytes (bits): " << head_size_bytes << "(" << head_size_bits
588 << "), max body bytes (bits): " << body_size_bytes << "(" << body_size_bits
589 << ")" << std::endl;
590
591 CharIterator head_bytes_end = begin + head_size_bytes;
592 dlog.is(logger::DEBUG3, logger::DECODE) &&
593 dlog << "Unencrypted Head (hex): " << hex_encode(begin, head_bytes_end)
594 << std::endl;
595
596 Bitset head_bits;
597 head_bits.from_byte_stream(begin, head_bytes_end);
598 dlog.is(logger::DEBUG3, logger::DECODE) &&
599 dlog << "Unencrypted Head (bin): " << head_bits << std::endl;
600
601 // shift off ID bits
602 head_bits >>= id_size;
603
604 dlog.is(logger::DEBUG3, logger::DECODE) &&
605 dlog << "Unencrypted Head after ID bits removal (bin): " << head_bits << std::endl;
606
607 internal::MessageStack msg_stack(manager_.codec_data().root_message_,
608 manager_.codec_data().message_data_);
609 msg_stack.push(msg->GetDescriptor());
610
611 codec->base_decode(&head_bits, msg, HEAD);
612 dlog.is(logger::DEBUG2, logger::DECODE) &&
613 dlog << "after header decode, message is: " << *msg << std::endl;
614
615 if (header_only)
616 {
617 dlog.is(logger::DEBUG2, logger::DECODE) &&
618 dlog << "as requested, skipping decrypting and decoding body." << std::endl;
619 actual_end = head_bytes_end;
620 }
621 else
622 {
623 dlog.is(logger::DEBUG3, logger::DECODE) &&
624 dlog << "Encrypted Body (hex): " << hex_encode(head_bytes_end, end)
625 << std::endl;
626
627 Bitset body_bits;
628 if (!crypto_key_.empty() && !skip_crypto_ids_.count(received_id))
629 {
630 std::string head_bytes(begin, head_bytes_end);
631 std::string body_bytes(head_bytes_end, end);
632 decrypt(&body_bytes, head_bytes);
633 dlog.is(logger::DEBUG3, logger::DECODE) &&
634 dlog << "Unencrypted Body (hex): " << hex_encode(body_bytes) << std::endl;
635 body_bits.from_byte_stream(body_bytes.begin(), body_bytes.end());
636 }
637 else
638 {
639 dlog.is(logger::DEBUG3, logger::DECODE) &&
640 dlog << "Unencrypted Body (hex): " << hex_encode(head_bytes_end, end)
641 << std::endl;
642 body_bits.from_byte_stream(head_bytes_end, end);
643 }
644
645 dlog.is(logger::DEBUG3, logger::DECODE) &&
646 dlog << "Unencrypted Body (bin): " << body_bits << std::endl;
647
648 codec->base_decode(&body_bits, msg, BODY);
649 dlog.is(logger::DEBUG2, logger::DECODE) &&
650 dlog << "after header & body decode, message is: " << *msg << std::endl;
651
652 actual_end = end - body_bits.size() / BITS_IN_BYTE;
653 }
654 }
655 else
656 {
657 throw(Exception("Failed to find (dccl.msg).codec `" +
658 desc->options().GetExtension(dccl::msg).codec() + "`"),
659 desc);
660 }
661
662 dlog.is(logger::DEBUG1, logger::DECODE) &&
663 dlog << "Successfully decoded message of type: " << desc->full_name() << std::endl;
664 return actual_end;
665 }
666 catch (std::exception& e)
667 {
668 std::stringstream ss;
669
670 ss << "Message " << hex_encode(begin, end) << " failed to decode. Reason: " << e.what()
671 << std::endl;
672
673 dlog.is(logger::DEBUG1, logger::DECODE) && dlog << ss.str() << std::endl;
674 throw(Exception(ss.str()));
675 }
676}
677
678#endif
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
void from_byte_stream(CharIterator begin, CharIterator end)
Sets the value of the Bitset to the contents of a byte string, where each character represents 8 bits...
Definition bitset.h:341
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
Definition codec.h:61
int32 id(const google::protobuf::Descriptor *desc) const
Provides the DCCL ID given a DCCL type.
Definition codec.h:246
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 set_strict(bool mode)
Set "strict" mode where a dccl::OutOfRangeException will be thrown for encode if the value(s) provide...
Definition codec.h:174
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
const std::map< int32, const google::protobuf::Descriptor * > & loaded() const
Provides a map of all loaded DCCL IDs to the equivalent Protobuf descriptor.
Definition codec.h:261
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
void decode(std::string *bytes, ProtobufMessage *msg)
Decode a DCCL message when the type is known at compile time.
Definition codec.h:322
int32 id(CharIterator begin, CharIterator end) const
Get the DCCL ID of an unknown encoded DCCL message (Iterator overload).
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 set_console_width(unsigned num_chars)
Set the number of characters used in programmatic generation of console outputs.
Definition codec.h:179
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
CharIterator decode(CharIterator begin, CharIterator end, ProtobufMessage *msg, bool header_only=false)
Decode a DCCL message when the type is known at compile time.
Definition codec.h:535
Codec(const std::string &dccl_id_codec_name, const IDFieldCodec &dccl_id_codec)
Instantiate a Codec with a non-default identifier field codec (loaded directly).
Definition codec.h:80
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
void decode(const std::string &bytes, ProtobufMessage *msg, bool header_only=false)
Decode a DCCL message when the type is known at compile time.
Definition codec.h:312
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,...
std::shared_ptr< FieldCodecBase > find(const google::protobuf::FieldDescriptor *field, int codec_version, bool has_codec_group, const std::string &codec_group) const
Find the codec for a given field. For embedded messages, prefers (dccl.field).codec (inside field) ov...
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
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