38#include <google/protobuf/descriptor.h>
43#include "dynamic_protobuf_manager.h"
45#include "field_codec.h"
46#include "field_codec_fixed.h"
50#include "dccl/version.h"
51#include "field_codec_manager.h"
68 Codec(std::string dccl_id_codec_name = default_id_codec_name(),
69 const std::string& library_path =
"");
77 template <
class IDFieldCodec,
78 typename std::enable_if<std::is_base_of<FieldCodecBase, IDFieldCodec>::value,
80 Codec(
const std::string& dccl_id_codec_name,
const IDFieldCodec& dccl_id_codec)
81 : id_codec_(dccl_id_codec_name)
84 manager_.
add<IDFieldCodec>(dccl_id_codec_name);
119 template <
typename ProtobufMessage> std::size_t
load()
121 return load(ProtobufMessage::descriptor());
127 template <
typename ProtobufMessage>
void unload() {
unload(ProtobufMessage::descriptor()); }
129 void unload_all() { id2desc_.clear(); }
138 std::size_t
load(
const google::protobuf::Descriptor* desc,
int user_id = -1);
144 void unload(
const google::protobuf::Descriptor* desc);
150 void unload(
size_t dccl_id);
154 std::string get_id_codec() {
return id_codec_; }
162 const std::set<int32>& do_not_encrypt_ids = std::set<int32>());
165 const std::set<unsigned>& do_not_encrypt_ids)
167 std::set<int32> s_ids{do_not_encrypt_ids.begin(), do_not_encrypt_ids.end()};
194 template <
typename ProtobufMessage>
195 void info(std::ostream* os =
nullptr,
int user_id = -1)
const
197 info(ProtobufMessage::descriptor(), os, user_id);
205 void info(
const google::protobuf::Descriptor* desc, std::ostream* os =
nullptr,
206 int user_id = -1)
const;
211 void info_all(std::ostream* os =
nullptr)
const;
216 template <
typename ProtobufMessage>
int32 id()
const
218 return id(ProtobufMessage::descriptor());
240 int32 id(
const std::string& bytes)
const;
243 template <
typename CharIterator>
int32 id(CharIterator begin, CharIterator end)
const;
246 int32 id(
const google::protobuf::Descriptor* desc)
const
248 if (desc->options().GetExtension(dccl::msg).omit_id())
249 throw(
Exception(
"Cannot call id(...) on message with omit_id == true"));
251 dccl::uint32 hardcoded_id = desc->options().GetExtension(dccl::msg).id();
255 id_codec()->field_encode(&id_bits, hardcoded_id,
nullptr);
261 const std::map<int32, const google::protobuf::Descriptor*>&
loaded()
const {
return id2desc_; }
277 void encode(std::string* bytes,
const google::protobuf::Message& msg,
bool header_only =
false,
290 size_t encode(
char* bytes,
size_t max_len,
const google::protobuf::Message& msg,
291 bool header_only =
false,
int user_id = -1);
301 template <
typename CharIterator,
typename ProtobufMessage>
302 CharIterator
decode(CharIterator begin, CharIterator end, ProtobufMessage* msg,
303 bool header_only =
false);
311 template <
typename ProtobufMessage>
312 void decode(
const std::string& bytes, ProtobufMessage* msg,
bool header_only =
false)
314 decode(bytes.begin(), bytes.end(), msg, header_only);
322 template <
typename ProtobufMessage>
void decode(std::string* bytes, ProtobufMessage* msg)
325 unsigned last_size =
size(*msg);
326 bytes->erase(0, last_size);
336 template <
typename GoogleProtobufMessagePo
inter>
337 GoogleProtobufMessagePointer
decode(
const std::string& bytes,
bool header_only =
false);
345 template <
typename GoogleProtobufMessagePo
inter>
346 GoogleProtobufMessagePointer
decode(std::string* bytes);
354 unsigned size(
const google::protobuf::Message& msg,
int user_id = -1);
360 template <
typename ProtobufMessage>
unsigned max_size()
362 return max_size(ProtobufMessage::descriptor());
366 unsigned max_size(
const google::protobuf::Descriptor* desc,
int user_id = -1)
const;
372 template <
typename ProtobufMessage>
unsigned min_size()
374 return min_size(ProtobufMessage::descriptor());
378 unsigned min_size(
const google::protobuf::Descriptor* desc,
int user_id = -1)
const;
382 static std::string default_id_codec_name() {
return "dccl.default.id"; }
384 static std::string default_codec_name(
int version = 2)
386 return "dccl.default" + std::to_string(version);
389 FieldCodecManagerLocal& manager() {
return manager_; }
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,
397 void encrypt(std::string* s,
const std::string& nonce);
398 void decrypt(std::string* s,
const std::string& nonce);
400 void set_default_codecs();
402 std::shared_ptr<FieldCodecBase> id_codec()
const
404 return manager_.
find(google::protobuf::FieldDescriptor::TYPE_UINT32, DCCL_VERSION_MAJOR,
408 int32 id_internal(
const google::protobuf::Descriptor* desc,
int user_id)
411 if (desc->options().GetExtension(dccl::msg).omit_id() && !desc2placeholder_id_.count(desc))
412 desc2placeholder_id_.emplace(desc, omit_id_placeholder_id_--);
414 return id_internal_const(desc, user_id);
417 int32 id_internal_const(
const google::protobuf::Descriptor* desc,
int user_id)
const
419 if (desc->options().GetExtension(dccl::msg).omit_id())
421 if (desc2placeholder_id_.count(desc))
422 return desc2placeholder_id_.find(desc)->second;
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"));
430 return (user_id < 0) ?
id(desc) : user_id;
436 std::string crypto_key_;
442 unsigned console_width_{60};
445 std::set<int32> skip_crypto_ids_;
448 std::map<int32, const google::protobuf::Descriptor*> id2desc_;
449 std::string id_codec_;
451 std::vector<void*> dl_handles_;
453 std::string build_guard_for_console_output(std::string& base,
char guard_char)
const;
455 FieldCodecManagerLocal manager_;
458 int32 omit_id_placeholder_id_{-1};
460 std::map<const google::protobuf::Descriptor*, int32> desc2placeholder_id_;
463inline std::ostream& operator<<(std::ostream& os,
const Codec& codec)
470template <
typename GoogleProtobufMessagePo
inter>
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."));
481 auto msg = dccl::DynamicProtobufManager::new_protobuf_message<GoogleProtobufMessagePointer>(
482 id2desc_.find(this_id)->second);
483 decode(bytes, &(*msg), header_only);
487template <
typename GoogleProtobufMessagePo
inter>
490 int32 this_id = id(*bytes);
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."));
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);
504template <
typename CharIterator>
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);
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)));
520 Bitset these_bits(&fixed_header_bits);
521 these_bits.get_more_bits(id_min_size);
523 dccl::any return_value;
524 id_codec()->field_decode(&these_bits, &return_value,
nullptr);
525 return dccl::any_cast<uint32>(return_value);
529 throw(Exception(
"Failed to decoded id from bytes passed (hex: " + hex_encode(begin, end) +
534template <
typename CharIterator,
typename ProtobufMessage>
540 const google::protobuf::Descriptor* desc = msg->GetDescriptor();
541 int32 expected_id = id_internal(desc, -1);
544 if (!desc->options().GetExtension(dccl::msg).omit_id())
546 received_id = id(begin, end);
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."));
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."));
562 dlog.
is(logger::DEBUG1, logger::DECODE) &&
563 dlog <<
"Began decoding message of id: " << received_id << std::endl;
565 dlog.
is(logger::DEBUG1, logger::DECODE) && dlog <<
"Type name: " << desc->full_name()
568 std::shared_ptr<FieldCodecBase> codec = manager_.find(desc);
569 std::shared_ptr<internal::FromProtoCppTypeBase> helper = manager_.type_helper().find(desc);
571 CharIterator actual_end = end;
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;
583 unsigned head_size_bytes = ceil_bits2bytes(head_size_bits);
584 unsigned body_size_bytes = ceil_bits2bytes(body_size_bits);
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
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)
598 dlog.
is(logger::DEBUG3, logger::DECODE) &&
599 dlog <<
"Unencrypted Head (bin): " << head_bits << std::endl;
602 head_bits >>= id_size;
604 dlog.
is(logger::DEBUG3, logger::DECODE) &&
605 dlog <<
"Unencrypted Head after ID bits removal (bin): " << head_bits << std::endl;
608 manager_.codec_data().message_data_);
609 msg_stack.push(msg->GetDescriptor());
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;
617 dlog.
is(logger::DEBUG2, logger::DECODE) &&
618 dlog <<
"as requested, skipping decrypting and decoding body." << std::endl;
619 actual_end = head_bytes_end;
623 dlog.
is(logger::DEBUG3, logger::DECODE) &&
624 dlog <<
"Encrypted Body (hex): " <<
hex_encode(head_bytes_end, end)
628 if (!crypto_key_.empty() && !skip_crypto_ids_.count(received_id))
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;
639 dlog.
is(logger::DEBUG3, logger::DECODE) &&
640 dlog <<
"Unencrypted Body (hex): " <<
hex_encode(head_bytes_end, end)
645 dlog.
is(logger::DEBUG3, logger::DECODE) &&
646 dlog <<
"Unencrypted Body (bin): " << body_bits << std::endl;
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;
652 actual_end = end - body_bits.size() / BITS_IN_BYTE;
657 throw(
Exception(
"Failed to find (dccl.msg).codec `" +
658 desc->options().GetExtension(dccl::msg).codec() +
"`"),
662 dlog.
is(logger::DEBUG1, logger::DECODE) &&
663 dlog <<
"Successfully decoded message of type: " << desc->full_name() << std::endl;
666 catch (std::exception& e)
668 std::stringstream ss;
670 ss <<
"Message " <<
hex_encode(begin, end) <<
" failed to decode. Reason: " << e.what()
673 dlog.
is(logger::DEBUG1, logger::DECODE) && dlog << ss.str() << std::endl;
A variable size container of bits (subclassed from std::deque<bool>) with an optional hierarchy....
std::string to_byte_string()
Returns the value of the Bitset to a byte string, where each character represents 8 bits of the Bitse...
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...
The Dynamic CCL enCODer/DECoder. This is the main class you will use to load, encode and decode DCCL ...
int32 id(const google::protobuf::Descriptor *desc) const
Provides the DCCL ID given a DCCL type.
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...
void set_strict(bool mode)
Set "strict" mode where a dccl::OutOfRangeException will be thrown for encode if the value(s) provide...
void info_all(std::ostream *os=nullptr) const
Writes a human readable summary (including field sizes) of all the loaded (validated) DCCL types.
const std::map< int32, const google::protobuf::Descriptor * > & loaded() const
Provides a map of all loaded DCCL IDs to the equivalent Protobuf descriptor.
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)
unsigned min_size()
Provides the encoded minimum size (in bytes) of msg.
void decode(std::string *bytes, ProtobufMessage *msg)
Decode a DCCL message when the type is known at compile time.
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 ....
void unload_library(void *dl_handle)
Remove codecs and/or unload messages present in the given shared library handle.
unsigned max_size()
Provides the encoded maximum size (in bytes) of msg.
void encode(std::string *bytes, const google::protobuf::Message &msg, bool header_only=false, int user_id=-1)
Encodes a DCCL message.
void set_console_width(unsigned num_chars)
Set the number of characters used in programmatic generation of console outputs.
void unload()
Unload a given message.
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...
virtual ~Codec()
Destructor.
CharIterator decode(CharIterator begin, CharIterator end, ProtobufMessage *msg, bool header_only=false)
Decode a DCCL message when the type is known at compile time.
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).
void load_library(void *dl_handle)
Add codecs and/or load messages present in the given shared library handle.
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...
std::size_t load()
All messages must be explicited loaded and validated (size checks, option extensions checks,...
void decode(const std::string &bytes, ProtobufMessage *msg, bool header_only=false)
Decode a DCCL message when the type is known at compile time.
Exception class for DCCL.
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...
Dynamic Compact Control Language namespace.
google::protobuf::int32 int32
a signed 32 bit integer
google::protobuf::uint32 uint32
an unsigned 32 bit integer
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...