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  {
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
A variable size container of bits (subclassed from std::deque<bool>) with an optional hierarchy....
Definition: bitset.h:42
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
const google::protobuf::Descriptor * this_descriptor() const
Returns the Descriptor (message schema meta-data) for the immediate parent Message.
const google::protobuf::FieldDescriptor * this_field() const
Returns the FieldDescriptor (field schema meta-data) for this field.
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::DCCLFieldOptions dccl_field_options() const
Get the DCCL field option extension value for the current field.
Definition: field_codec.h:335
bool use_required()
Whether to use the required or optional encoding.
Definition: field_codec.h:386
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
Exception used to signal null (non-existent) value within field codecs during decode.
Definition: exception.h:63
Base class for static-typed (no dccl::any) field encoders/decoders. Most single-valued user defined v...
Base class for static-typed field encoders/decoders that use a fixed number of bits on the wire regar...
Provides a bool encoder. Uses 1 bit if field is required, 2 bits if optional
unsigned size() override
The size of the encoded message in bits. Use TypedFieldCodec if the size depends on the data.
Bitset encode() override
Encode an empty field.
bool decode(Bitset *bits) override
Decode a field. If the field is empty (i.e. was encoded using the zero-argument encode()),...
void validate() override
Validate a field. Use require() inside your overloaded validate() to assert requirements or throw Exc...
Provides an fixed length byte string encoder.
unsigned min_size() override
Calculate minimum size of the field in bits.
Bitset encode() override
Encode an empty field.
unsigned size() override
Calculate the size (in bits) of an empty field.
void validate() override
Validate a field. Use require() inside your overloaded validate() to assert requirements or throw Exc...
unsigned max_size() override
Calculate maximum size of the field in bits.
std::string decode(Bitset *bits) override
Decode a field. If the field is empty (i.e. was encoded using the zero-argument encode()),...
Provides an enum encoder. This converts the enumeration to an integer (based on the enumeration index...
Provides a basic bounded arbitrary length numeric (double, float, uint32, uint64, int32,...
Bitset encode() override
Encode an empty field.
WireType decode(Bitset *bits) override
Decode a field. If the field is empty (i.e. was encoded using the zero-argument encode()),...
unsigned size() override
The size of the encoded message in bits. Use TypedFieldCodec if the size depends on the data.
void validate() override
Validate a field. Use require() inside your overloaded validate() to assert requirements or throw Exc...
Bitset encode(const WireType &value) override
Encode a non-empty field.
Provides an variable length ASCII string encoder. Can encode strings up to 255 bytes by using a lengt...
Placeholder codec that takes no space on the wire (0 bits).
Encodes time of day (default: second precision, but can be set with (dccl.field).precision extension)
Dynamic Compact Control Language namespace.
Definition: any.h:50
google::protobuf::uint64 uint64
an unsigned 64 bit integer
Definition: common.h:60
google::protobuf::int32 int32
a signed 32 bit integer
Definition: common.h:58
google::protobuf::int64 int64
a signed 64 bit integer
Definition: common.h:62
unsigned ceil_log2(dccl::uint64 v)
Definition: binary.h:178