DCCL v4
field_codec_default.h
1 // Copyright 2014-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 // Davide Fenucci <davfen@noc.ac.uk>
8 // Kyle Guilbert <kguilbert@aphysci.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 // implements FieldCodecBase for all the basic DCCL types
29 
30 #ifndef DCCLFIELDCODECDEFAULT20110322H
31 #define DCCLFIELDCODECDEFAULT20110322H
32 
33 #include <chrono>
34 
35 #include <google/protobuf/descriptor.h>
36 
37 #include "dccl/option_extensions.pb.h"
38 
39 #include "../binary.h"
40 #include "../field_codec.h"
41 #include "../field_codec_fixed.h"
42 #include "../thread_safety.h"
43 #include "field_codec_default_message.h"
44 
45 namespace dccl
46 {
48 namespace v2
49 {
53 template <typename WireType, typename FieldType = WireType>
54 class DefaultNumericFieldCodec : public TypedFixedFieldCodec<WireType, FieldType>
55 {
56  public:
57  virtual double max()
58  {
59  DynamicConditions& dc = this->dynamic_conditions(this->this_field());
60 
61  double static_max = this->dccl_field_options().max();
62  if (dc.has_max())
63  {
64  dc.regenerate(this->this_message(), this->root_message());
65  // don't let dynamic conditions breach static bounds
66  return std::max(this->dccl_field_options().min(), std::min(dc.max(), static_max));
67  }
68  else
69  {
70  return static_max;
71  }
72  }
73 
74  virtual double min()
75  {
76  DynamicConditions& dc = this->dynamic_conditions(this->this_field());
77  double static_min = this->dccl_field_options().min();
78  if (dc.has_min())
79  {
80  dc.regenerate(this->this_message(), this->root_message());
81 
82  // don't let dynamic conditions breach static bounds
83  return std::min(this->dccl_field_options().max(), std::max(dc.min(), static_min));
84  }
85  else
86  {
87  return static_min;
88  }
89  }
90 
91  virtual double precision() { return FieldCodecBase::dccl_field_options().precision(); }
92 
93  virtual double resolution()
94  {
95  if (FieldCodecBase::dccl_field_options().has_precision())
96  return std::pow(10.0, -precision());
97  // If none is set returns the default resolution (=1)
98  return FieldCodecBase::dccl_field_options().resolution();
99  }
100 
101  void validate() override
102  {
104  "missing (dccl.field).min");
106  "missing (dccl.field).max");
107 
109  "(dccl.field).resolution must be greater than 0");
111  !(FieldCodecBase::dccl_field_options().has_precision() &&
112  FieldCodecBase::dccl_field_options().has_resolution()),
113  "at most one of either (dccl.field).precision or (dccl.field).resolution can be set");
114 
115  validate_numeric_bounds();
116  }
117 
118  void validate_numeric_bounds()
119  {
120  // ensure given max and min fit within WireType ranges
121  FieldCodecBase::require(static_cast<WireType>(min()) >=
122  std::numeric_limits<WireType>::lowest(),
123  "(dccl.field).min must be >= minimum of this field type.");
124  FieldCodecBase::require(static_cast<WireType>(max()) <=
125  std::numeric_limits<WireType>::max(),
126  "(dccl.field).max must be <= maximum of this field type.");
127 
128  // allowable epsilon for min / max to diverge from nearest quantile
129  const double min_max_eps = 1e-10;
130  bool min_multiple_of_res = std::abs(quantize(min(), resolution()) - min()) < min_max_eps;
131  bool max_multiple_of_res = std::abs(quantize(max(), resolution()) - max()) < min_max_eps;
132  if (FieldCodecBase::dccl_field_options().has_resolution())
133  {
134  // ensure that max and min are multiples of the resolution chosen
136  min_multiple_of_res,
137  "(dccl.field).min must be an exact multiple of (dccl.field).resolution");
139  max_multiple_of_res,
140  "(dccl.field).max must be an exact multiple of (dccl.field).resolution");
141  }
142  else
143  {
144  auto res = resolution();
145  // this was previously allowed so we will only give a warning not throw an exception
146  if (!min_multiple_of_res)
147  dccl::dlog.is(dccl::logger::WARN, dccl::logger::GENERAL) &&
148  dccl::dlog << "Warning: (dccl.field).min should be an exact multiple of "
149  "10^(-(dccl.field).precision), i.e. "
150  << res << ": " << this->this_field()->DebugString() << std::endl;
151  if (!max_multiple_of_res)
152  dccl::dlog.is(dccl::logger::WARN, dccl::logger::GENERAL) &&
153  dccl::dlog << "Warning: (dccl.field).max should be an exact multiple of "
154  "10^(-(dccl.field).precision), i.e. "
155  << res << ": " << this->this_field()->DebugString() << std::endl;
156  }
157 
158  // ensure value fits into double
159  FieldCodecBase::require(std::log2(max() - min()) - std::log2(resolution()) <=
160  std::numeric_limits<double>::digits,
161  "[(dccl.field).max-(dccl.field).min]/(dccl.field).resolution must "
162  "fit in a double-precision floating point value. Please increase "
163  "min, decrease max, or decrease precision.");
164  }
165 
166  Bitset encode() override { return Bitset(size()); }
167 
168  Bitset encode(const WireType& value) override
169  {
170  dccl::dlog.is(dccl::logger::DEBUG2, dccl::logger::ENCODE) &&
171  dlog << "Encode " << value << " with bounds: [" << min() << "," << max() << "]"
172  << std::endl;
173 
174  // round first, before checking bounds
175  double res = resolution();
176  WireType wire_value = dccl::quantize(value, res);
177 
178  // check bounds
179  if (wire_value < min() || wire_value > max())
180  {
181  // strict mode
182  if (this->strict())
184  std::string("Value exceeds min/max bounds for field: ") +
185  FieldCodecBase::this_field()->DebugString(),
186  this->this_field(), this->this_descriptor()));
187  // non-strict (default): if out-of-bounds, send as zeros
188  else
189  return Bitset(size());
190  }
191 
192  // calculate the encoded value: remove the minimum, scale for the resolution, cast to int.
193  wire_value -= dccl::quantize(static_cast<WireType>(min()), res);
194  if (res >= 1)
195  wire_value /= res;
196  else
197  wire_value *= (1.0 / res);
198  auto uint_value = static_cast<dccl::uint64>(dccl::round(wire_value, 0));
199 
200  // "presence" value (0)
202  uint_value += 1;
203 
204  Bitset encoded;
205  encoded.from(uint_value, size());
206  return encoded;
207  }
208 
209  WireType decode(Bitset* bits) override
210  {
211  dccl::dlog.is(dccl::logger::DEBUG2, dccl::logger::DECODE) &&
212  dlog << "Decode with bounds: [" << min() << "," << max() << "]" << std::endl;
213 
214  // The line below SHOULD BE:
215  // dccl::uint64 t = bits->to<dccl::uint64>();
216  // But GCC3.3 requires an explicit template modifier on the method.
217  // See, e.g., http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10959
218  dccl::uint64 uint_value = (bits->template to<dccl::uint64>)();
219 
221  {
222  if (!uint_value)
223  throw NullValueException();
224  --uint_value;
225  }
226 
227  auto wire_value = (WireType)uint_value;
228  double res = resolution();
229  if (res >= 1)
230  wire_value *= res;
231  else
232  wire_value /= (1.0 / res);
233 
234  // round values again to properly handle cases where double precision
235  // leads to slightly off values (e.g. 2.099999999 instead of 2.1)
236  wire_value =
237  dccl::quantize(wire_value + dccl::quantize(static_cast<WireType>(min()), res), res);
238  return wire_value;
239  }
240 
241  // bring size(const WireType&) into scope so callers can access it
243 
244  unsigned size() override
245  {
246  // if not required field, leave one value for unspecified (always encoded as 0)
247  unsigned NULL_VALUE = FieldCodecBase::use_required() ? 0 : 1;
248 
249  return dccl::ceil_log2((max() - min()) / resolution() + 1 + NULL_VALUE);
250  }
251 };
252 
257 {
258  public:
259  Bitset encode(const bool& wire_value) override;
260  Bitset encode() override;
261  bool decode(Bitset* bits) override;
262  unsigned size() override;
263  unsigned size(const bool& wire_value) override { return size(); }
264  void validate() override;
265 };
266 
270 class DefaultStringCodec : public TypedFieldCodec<std::string>
271 {
272  private:
273  Bitset encode() override;
274  Bitset encode(const std::string& wire_value) override;
275  std::string decode(Bitset* bits) override;
276  unsigned size() override;
277  unsigned size(const std::string& wire_value) override;
278  unsigned max_size() override;
279  unsigned min_size() override;
280  void validate() override;
281 
282  private:
283  enum
284  {
285  MAX_STRING_LENGTH = 255
286  };
287 };
288 
290 class DefaultBytesCodec : public TypedFieldCodec<std::string>
291 {
292  public:
293  Bitset encode() override;
294  Bitset encode(const std::string& wire_value) override;
295  std::string decode(Bitset* bits) override;
296  unsigned size() override;
297  unsigned size(const std::string& wire_value) override;
298  unsigned max_size() override;
299  unsigned min_size() override;
300  void validate() override;
301 };
302 
305  : public DefaultNumericFieldCodec<int32, const google::protobuf::EnumValueDescriptor*>
306 {
307  public:
308  int32 pre_encode(const google::protobuf::EnumValueDescriptor* const& field_value) override;
309  const google::protobuf::EnumValueDescriptor* post_decode(const int32& wire_value) override;
310 
311  private:
312  void validate() override {}
313  std::size_t hash() override
314  {
315  return std::hash<std::string>{}(this_field()->enum_type()->DebugString());
316  }
317 
318  double max() override
319  {
320  const google::protobuf::EnumDescriptor* e = this_field()->enum_type();
321  return e->value_count() - 1;
322  }
323  double min() override { return 0; }
324 };
325 
327 {
328  public:
330  {
331  // if no other clock, set std::chrono::system_clock as clock
332  if (!this->has_clock())
333  this->set_clock<std::chrono::system_clock>();
334  }
335 
336  template <typename Clock> static void set_clock()
337  {
338 #if DCCL_THREAD_SUPPORT
339  const std::lock_guard<std::mutex> lock(clock_mutex_);
340 #endif
341 
342  epoch_sec_func_ = []() -> int64
343  {
344  typename Clock::time_point now = Clock::now();
345  std::chrono::seconds sec =
346  std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
347  return sec.count();
348  };
349  }
350  static bool has_clock()
351  {
352 #if DCCL_THREAD_SUPPORT
353  const std::lock_guard<std::mutex> lock(clock_mutex_);
354 #endif
355  return epoch_sec_func_ ? true : false;
356  }
357 
358  protected:
359  static int64 epoch_sec()
360  {
361 #if DCCL_THREAD_SUPPORT
362  const std::lock_guard<std::mutex> lock(clock_mutex_);
363 #endif
364  return epoch_sec_func_();
365  }
366 
367  private:
368 #if DCCL_THREAD_SUPPORT
369  static std::mutex clock_mutex_;
370 #endif
371  static std::function<int64()> epoch_sec_func_;
372 };
373 
374 typedef double time_wire_type;
378 template <typename TimeType, int conversion_factor>
379 class TimeCodecBase : public DefaultNumericFieldCodec<time_wire_type, TimeType>,
380  public TimeCodecClock
381 {
382  public:
383  TimeCodecBase() {}
384 
385  time_wire_type pre_encode(const TimeType& time_of_day) override
386  {
387  time_wire_type max_secs = max();
388  return std::fmod(time_of_day / static_cast<time_wire_type>(conversion_factor), max_secs);
389  }
390 
391  TimeType post_decode(const time_wire_type& encoded_time) override
392  {
393  auto max_secs = static_cast<int64>(max());
394  int64_t now_secs = this->epoch_sec();
395  int64 daystart = now_secs - (now_secs % max_secs);
396  int64 today_time = now_secs - daystart;
397 
398  // If time is more than 12 hours ahead of now, assume it's yesterday.
399  if ((encoded_time - today_time) > (max_secs / 2))
400  daystart -= max_secs;
401  else if ((today_time - encoded_time) > (max_secs / 2))
402  daystart += max_secs;
403 
404  return dccl::round((TimeType)(conversion_factor * (daystart + encoded_time)),
405  precision() - std::log10((double)conversion_factor));
406  }
407 
408  private:
409  void validate() override
410  {
411  DefaultNumericFieldCodec<time_wire_type, TimeType>::validate_numeric_bounds();
412  }
413 
414  double max() override
415  {
416  return FieldCodecBase::dccl_field_options().num_days() * SECONDS_IN_DAY;
417  }
418 
419  double min() override { return 0; }
420  double precision() override
421  {
422  if (!FieldCodecBase::dccl_field_options().has_precision())
423  return 0; // default to second precision
424  else
425  {
426  return FieldCodecBase::dccl_field_options().precision() +
427  (double)std::log10((double)conversion_factor);
428  }
429  }
430 
431  private:
432  enum
433  {
434  SECONDS_IN_DAY = 86400
435  };
436 };
437 
438 template <typename TimeType> class TimeCodec : public TimeCodecBase<TimeType, 0>
439 {
440  static_assert(sizeof(TimeCodec) == 0, "Must use specialization of TimeCodec");
441 };
442 
443 template <> class TimeCodec<uint64> : public TimeCodecBase<uint64, 1000000>
444 {
445 };
446 template <> class TimeCodec<int64> : public TimeCodecBase<int64, 1000000>
447 {
448 };
449 template <> class TimeCodec<double> : public TimeCodecBase<double, 1>
450 {
451 };
452 
454 template <typename T> class StaticCodec : public TypedFixedFieldCodec<T>
455 {
456  Bitset encode(const T&) override { return Bitset(size()); }
457 
458  Bitset encode() override { return Bitset(size()); }
459 
460  T decode(Bitset* /*bits*/) override
461  {
462  std::istringstream iss(FieldCodecBase::dccl_field_options().static_value());
463  T value;
464  iss >> value;
465  return value;
466  }
467 
468  unsigned size() override { return 0; }
469 
470  void validate() override
471  {
473  "missing (dccl.field).static_value");
474 
475  std::string t = FieldCodecBase::dccl_field_options().static_value();
476  std::istringstream iss(t);
477  T value;
478 
479  if (!(iss >> value))
480  {
481  FieldCodecBase::require(false, "invalid static_value for this type.");
482  }
483  }
484 };
485 } // namespace v2
486 } // namespace dccl
487 
488 #endif
dccl::v2::DefaultBoolCodec::validate
void validate() override
Validate a field. Use require() inside your overloaded validate() to assert requirements or throw Exc...
Definition: field_codec_default.cpp:78
dccl::v2::StaticCodec
Placeholder codec that takes no space on the wire (0 bits).
Definition: field_codec_default.h:454
dccl::v2::DefaultBytesCodec::min_size
unsigned min_size() override
Calculate minimum size of the field in bits.
Definition: field_codec_default.cpp:236
dccl::v2::DefaultNumericFieldCodec::encode
Bitset encode() override
Encode an empty field.
Definition: field_codec_default.h:166
dccl::FieldCodecBase::this_descriptor
const google::protobuf::Descriptor * this_descriptor() const
Returns the Descriptor (message schema meta-data) for the immediate parent Message.
Definition: field_codec.cpp:658
dccl::v2::DefaultBytesCodec::max_size
unsigned max_size() override
Calculate maximum size of the field in bits.
Definition: field_codec_default.cpp:230
dccl::uint64
google::protobuf::uint64 uint64
an unsigned 64 bit integer
Definition: common.h:60
dccl::v2::DefaultBytesCodec::validate
void validate() override
Validate a field. Use require() inside your overloaded validate() to assert requirements or throw Exc...
Definition: field_codec_default.cpp:244
dccl::v2::DefaultEnumCodec::post_decode
const google::protobuf::EnumValueDescriptor * post_decode(const int32 &wire_value) override
Convert from the WireType representation (used with encode() and decode(), i.e. "on the wire") to the...
Definition: field_codec_default.cpp:259
dccl::DynamicConditions
Definition: dynamic_conditions.h:39
dccl::v2::DefaultNumericFieldCodec::encode
Bitset encode(const WireType &value) override
Encode a non-empty field.
Definition: field_codec_default.h:168
dccl::v2::DefaultBoolCodec::size
unsigned size(const bool &wire_value) override
Calculate the size (in bits) of a non-empty field.
Definition: field_codec_default.h:263
dccl
Dynamic Compact Control Language namespace.
Definition: any.h:49
dccl::FieldCodecBase::this_field
const google::protobuf::FieldDescriptor * this_field() const
Returns the FieldDescriptor (field schema meta-data) for this field.
Definition: field_codec.cpp:653
dccl::Logger::is
bool is(logger::Verbosity verbosity, logger::Group group=logger::GENERAL)
Indicates the verbosity of the Logger until the next std::flush or std::endl. The boolean return is u...
Definition: logger.h:192
dccl::v2::TimeCodecBase::pre_encode
time_wire_type pre_encode(const TimeType &time_of_day) override
Convert from the FieldType representation (used in the Google Protobuf message) to the WireType repre...
Definition: field_codec_default.h:385
dccl::v2::TimeCodec
Definition: field_codec_default.h:438
dccl::TypedFixedFieldCodec
Base class for static-typed field encoders/decoders that use a fixed number of bits on the wire regar...
Definition: field_codec_fixed.h:37
dccl::v2::DefaultEnumCodec::pre_encode
int32 pre_encode(const google::protobuf::EnumValueDescriptor *const &field_value) override
Convert from the FieldType representation (used in the Google Protobuf message) to the WireType repre...
Definition: field_codec_default.cpp:252
dccl::v2::DefaultNumericFieldCodec::validate
void validate() override
Validate a field. Use require() inside your overloaded validate() to assert requirements or throw Exc...
Definition: field_codec_default.h:101
dccl::FieldCodecBase::require
void require(bool b, const std::string &description)
Essentially an assertion to be used in the validate() virtual method.
Definition: field_codec.h:349
dccl::v2::TimeCodecBase
Encodes time of day (default: second precision, but can be set with (dccl.field).precision extension)
Definition: field_codec_default.h:379
dccl::Bitset::from
void from(IntType value, size_type num_bits=std::numeric_limits< IntType >::digits)
Sets value of the Bitset to the contents of an integer.
Definition: bitset.h:235
dccl::ceil_log2
unsigned ceil_log2(dccl::uint64 v)
Definition: binary.h:178
dccl::int64
google::protobuf::int64 int64
a signed 64 bit integer
Definition: common.h:62
dccl::v2::DefaultBoolCodec::decode
bool decode(Bitset *bits) override
Decode a field. If the field is empty (i.e. was encoded using the zero-argument encode()),...
Definition: field_codec_default.cpp:50
dccl::v2::DefaultStringCodec
Provides an variable length ASCII string encoder. Can encode strings up to 255 bytes by using a lengt...
Definition: field_codec_default.h:270
dccl::v2::DefaultBytesCodec::size
unsigned size() override
Calculate the size (in bits) of an empty field.
Definition: field_codec_default.cpp:200
dccl::NullValueException
Exception used to signal null (non-existent) value within field codecs during decode.
Definition: exception.h:62
dccl::Bitset
A variable size container of bits (subclassed from std::deque<bool>) with an optional hierarchy....
Definition: bitset.h:41
dccl::v2::DefaultBoolCodec
Provides a bool encoder. Uses 1 bit if field is required, 2 bits if optional
Definition: field_codec_default.h:256
dccl::v2::TimeCodecClock
Definition: field_codec_default.h:326
dccl::OutOfRangeException
Definition: exception.h:68
dccl::v2::DefaultEnumCodec
Provides an enum encoder. This converts the enumeration to an integer (based on the enumeration index...
Definition: field_codec_default.h:304
dccl::TypedFieldCodec
Base class for static-typed (no dccl::any) field encoders/decoders. Most single-valued user defined v...
Definition: field_codec_typed.h:73
dccl::v2::DefaultBytesCodec
Provides an fixed length byte string encoder.
Definition: field_codec_default.h:290
dccl::v2::DefaultBoolCodec::size
unsigned size() override
The size of the encoded message in bits. Use TypedFieldCodec if the size depends on the data.
Definition: field_codec_default.cpp:68
dccl::FieldCodecBase::use_required
bool use_required()
Whether to use the required or optional encoding.
Definition: field_codec.h:386
dccl::v2::DefaultBytesCodec::decode
std::string decode(Bitset *bits) override
Decode a field. If the field is empty (i.e. was encoded using the zero-argument encode()),...
Definition: field_codec_default.cpp:204
dccl::int32
google::protobuf::int32 int32
a signed 32 bit integer
Definition: common.h:58
dccl::v2::DefaultBytesCodec::encode
Bitset encode() override
Encode an empty field.
Definition: field_codec_default.cpp:177
dccl::v2::DefaultNumericFieldCodec::size
unsigned size() override
The size of the encoded message in bits. Use TypedFieldCodec if the size depends on the data.
Definition: field_codec_default.h:244
dccl::v2::DefaultNumericFieldCodec
Provides a basic bounded arbitrary length numeric (double, float, uint32, uint64, int32,...
Definition: field_codec_default.h:54
dccl::v2::DefaultNumericFieldCodec::decode
WireType decode(Bitset *bits) override
Decode a field. If the field is empty (i.e. was encoded using the zero-argument encode()),...
Definition: field_codec_default.h:209
dccl::v2::DefaultBoolCodec::encode
Bitset encode() override
Encode an empty field.
Definition: field_codec_default.cpp:43
dccl::FieldCodecBase::dccl_field_options
dccl::DCCLFieldOptions dccl_field_options() const
Get the DCCL field option extension value for the current field.
Definition: field_codec.h:335