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 // Chris Murphy <cmurphy@aphysci.com>
8 //
9 //
10 // This file is part of the Dynamic Compact Control Language Library
11 // ("DCCL").
12 //
13 // DCCL is free software: you can redistribute it and/or modify
14 // it under the terms of the GNU Lesser General Public License as published by
15 // the Free Software Foundation, either version 2.1 of the License, or
16 // (at your option) any later version.
17 //
18 // DCCL is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 // GNU Lesser General Public License for more details.
22 //
23 // You should have received a copy of the GNU Lesser General Public License
24 // along with DCCL. If not, see <http://www.gnu.org/licenses/>.
25 // implements FieldCodecBase for all the basic DCCL types
26 
27 #ifndef DCCLFIELDCODECDEFAULT20110322H
28 #define DCCLFIELDCODECDEFAULT20110322H
29 
30 #include <chrono>
31 
32 #include <google/protobuf/descriptor.h>
33 
34 #include "dccl/option_extensions.pb.h"
35 
36 #include "../binary.h"
37 #include "../field_codec.h"
38 #include "../field_codec_fixed.h"
39 #include "../thread_safety.h"
40 #include "field_codec_default_message.h"
41 
42 namespace dccl
43 {
45 namespace v2
46 {
50 template <typename WireType, typename FieldType = WireType>
51 class DefaultNumericFieldCodec : public TypedFixedFieldCodec<WireType, FieldType>
52 {
53  public:
54  virtual double max()
55  {
56  DynamicConditions& dc = this->dynamic_conditions(this->this_field());
57 
58  double static_max = this->dccl_field_options().max();
59  if (dc.has_max())
60  {
61  dc.regenerate(this->this_message(), this->root_message());
62  // don't let dynamic conditions breach static bounds
63  return std::max(this->dccl_field_options().min(), std::min(dc.max(), static_max));
64  }
65  else
66  {
67  return static_max;
68  }
69  }
70 
71  virtual double min()
72  {
73  DynamicConditions& dc = this->dynamic_conditions(this->this_field());
74  double static_min = this->dccl_field_options().min();
75  if (dc.has_min())
76  {
77  dc.regenerate(this->this_message(), this->root_message());
78 
79  // don't let dynamic conditions breach static bounds
80  return std::min(this->dccl_field_options().max(), std::max(dc.min(), static_min));
81  }
82  else
83  {
84  return static_min;
85  }
86  }
87 
88  virtual double precision() { return FieldCodecBase::dccl_field_options().precision(); }
89 
90  virtual double resolution()
91  {
92  if (FieldCodecBase::dccl_field_options().has_precision())
93  return std::pow(10.0, -precision());
94  // If none is set returns the default resolution (=1)
95  return FieldCodecBase::dccl_field_options().resolution();
96  }
97 
98  void validate() override
99  {
101  "missing (dccl.field).min");
103  "missing (dccl.field).max");
104 
106  "(dccl.field).resolution must be greater than 0");
108  !(FieldCodecBase::dccl_field_options().has_precision() &&
109  FieldCodecBase::dccl_field_options().has_resolution()),
110  "at most one of either (dccl.field).precision or (dccl.field).resolution can be set");
111 
112  validate_numeric_bounds();
113  }
114 
115  void validate_numeric_bounds()
116  {
117  // ensure given max and min fit within WireType ranges
118  FieldCodecBase::require(static_cast<WireType>(min()) >=
119  std::numeric_limits<WireType>::lowest(),
120  "(dccl.field).min must be >= minimum of this field type.");
121  FieldCodecBase::require(static_cast<WireType>(max()) <=
122  std::numeric_limits<WireType>::max(),
123  "(dccl.field).max must be <= maximum of this field type.");
124 
125  // allowable epsilon for min / max to diverge from nearest quantile
126  const double min_max_eps = 1e-10;
127  bool min_multiple_of_res = std::abs(quantize(min(), resolution()) - min()) < min_max_eps;
128  bool max_multiple_of_res = std::abs(quantize(max(), resolution()) - max()) < min_max_eps;
129  if (FieldCodecBase::dccl_field_options().has_resolution())
130  {
131  // ensure that max and min are multiples of the resolution chosen
133  min_multiple_of_res,
134  "(dccl.field).min must be an exact multiple of (dccl.field).resolution");
136  max_multiple_of_res,
137  "(dccl.field).max must be an exact multiple of (dccl.field).resolution");
138  }
139  else
140  {
141  auto res = resolution();
142  // this was previously allowed so we will only give a warning not throw an exception
143  if (!min_multiple_of_res)
144  dccl::dlog.is(dccl::logger::WARN, dccl::logger::GENERAL) &&
145  dccl::dlog << "Warning: (dccl.field).min should be an exact multiple of "
146  "10^(-(dccl.field).precision), i.e. "
147  << res << ": " << this->this_field()->DebugString() << std::endl;
148  if (!max_multiple_of_res)
149  dccl::dlog.is(dccl::logger::WARN, dccl::logger::GENERAL) &&
150  dccl::dlog << "Warning: (dccl.field).max should be an exact multiple of "
151  "10^(-(dccl.field).precision), i.e. "
152  << res << ": " << this->this_field()->DebugString() << std::endl;
153  }
154 
155  // ensure value fits into double
156  FieldCodecBase::require(std::log2(max() - min()) - std::log2(resolution()) <=
157  std::numeric_limits<double>::digits,
158  "[(dccl.field).max-(dccl.field).min]/(dccl.field).resolution must "
159  "fit in a double-precision floating point value. Please increase "
160  "min, decrease max, or decrease precision.");
161  }
162 
163  Bitset encode() override { return Bitset(size()); }
164 
165  Bitset encode(const WireType& value) override
166  {
167  dccl::dlog.is(dccl::logger::DEBUG2, dccl::logger::ENCODE) &&
168  dlog << "Encode " << value << " with bounds: [" << min() << "," << max() << "]"
169  << std::endl;
170 
171  // round first, before checking bounds
172  double res = resolution();
173  WireType wire_value = dccl::quantize(value, res);
174 
175  // check bounds
176  if (wire_value < min() || wire_value > max())
177  {
178  // strict mode
179  if (this->strict())
181  std::string("Value exceeds min/max bounds for field: ") +
182  FieldCodecBase::this_field()->DebugString(),
183  this->this_field(), this->this_descriptor()));
184  // non-strict (default): if out-of-bounds, send as zeros
185  else
186  return Bitset(size());
187  }
188 
189  // calculate the encoded value: remove the minimum, scale for the resolution, cast to int.
190  wire_value -= dccl::quantize(static_cast<WireType>(min()), res);
191  if (res >= 1)
192  wire_value /= res;
193  else
194  wire_value *= (1.0 / res);
195  auto uint_value = static_cast<dccl::uint64>(dccl::round(wire_value, 0));
196 
197  // "presence" value (0)
199  uint_value += 1;
200 
201  Bitset encoded;
202  encoded.from(uint_value, size());
203  return encoded;
204  }
205 
206  WireType decode(Bitset* bits) override
207  {
208  dccl::dlog.is(dccl::logger::DEBUG2, dccl::logger::DECODE) &&
209  dlog << "Decode with bounds: [" << min() << "," << max() << "]" << std::endl;
210 
211  // The line below SHOULD BE:
212  // dccl::uint64 t = bits->to<dccl::uint64>();
213  // But GCC3.3 requires an explicit template modifier on the method.
214  // See, e.g., http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10959
215  dccl::uint64 uint_value = (bits->template to<dccl::uint64>)();
216 
218  {
219  if (!uint_value)
220  throw NullValueException();
221  --uint_value;
222  }
223 
224  auto wire_value = (WireType)uint_value;
225  double res = resolution();
226  if (res >= 1)
227  wire_value *= res;
228  else
229  wire_value /= (1.0 / res);
230 
231  // round values again to properly handle cases where double precision
232  // leads to slightly off values (e.g. 2.099999999 instead of 2.1)
233  wire_value =
234  dccl::quantize(wire_value + dccl::quantize(static_cast<WireType>(min()), res), res);
235  return wire_value;
236  }
237 
238  // bring size(const WireType&) into scope so callers can access it
240 
241  unsigned size() override
242  {
243  // if not required field, leave one value for unspecified (always encoded as 0)
244  unsigned NULL_VALUE = FieldCodecBase::use_required() ? 0 : 1;
245 
246  return dccl::ceil_log2((max() - min()) / resolution() + 1 + NULL_VALUE);
247  }
248 };
249 
254 {
255  public:
256  Bitset encode(const bool& wire_value) override;
257  Bitset encode() override;
258  bool decode(Bitset* bits) override;
259  unsigned size() override;
260  unsigned size(const bool& wire_value) override { return size(); }
261  void validate() override;
262 };
263 
267 class DefaultStringCodec : public TypedFieldCodec<std::string>
268 {
269  private:
270  Bitset encode() override;
271  Bitset encode(const std::string& wire_value) override;
272  std::string decode(Bitset* bits) override;
273  unsigned size() override;
274  unsigned size(const std::string& wire_value) override;
275  unsigned max_size() override;
276  unsigned min_size() override;
277  void validate() override;
278 
279  private:
280  enum
281  {
282  MAX_STRING_LENGTH = 255
283  };
284 };
285 
287 class DefaultBytesCodec : public TypedFieldCodec<std::string>
288 {
289  public:
290  Bitset encode() override;
291  Bitset encode(const std::string& wire_value) override;
292  std::string decode(Bitset* bits) override;
293  unsigned size() override;
294  unsigned size(const std::string& wire_value) override;
295  unsigned max_size() override;
296  unsigned min_size() override;
297  void validate() override;
298 };
299 
302  : public DefaultNumericFieldCodec<int32, const google::protobuf::EnumValueDescriptor*>
303 {
304  public:
305  int32 pre_encode(const google::protobuf::EnumValueDescriptor* const& field_value) override;
306  const google::protobuf::EnumValueDescriptor* post_decode(const int32& wire_value) override;
307 
308  private:
309  void validate() override {}
310  std::size_t hash() override
311  {
312  return std::hash<std::string>{}(this_field()->enum_type()->DebugString());
313  }
314 
315  double max() override
316  {
317  const google::protobuf::EnumDescriptor* e = this_field()->enum_type();
318  return e->value_count() - 1;
319  }
320  double min() override { return 0; }
321 };
322 
324 {
325  public:
327  {
328  // if no other clock, set std::chrono::system_clock as clock
329  if (!this->has_clock())
330  this->set_clock<std::chrono::system_clock>();
331  }
332 
333  template <typename Clock> static void set_clock()
334  {
335 #if DCCL_THREAD_SUPPORT
336  const std::lock_guard<std::mutex> lock(clock_mutex_);
337 #endif
338 
339  epoch_sec_func_ = []() -> int64
340  {
341  typename Clock::time_point now = Clock::now();
342  std::chrono::seconds sec =
343  std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
344  return sec.count();
345  };
346  }
347  static bool has_clock()
348  {
349 #if DCCL_THREAD_SUPPORT
350  const std::lock_guard<std::mutex> lock(clock_mutex_);
351 #endif
352  return epoch_sec_func_ ? true : false;
353  }
354 
355  protected:
356  static int64 epoch_sec()
357  {
358 #if DCCL_THREAD_SUPPORT
359  const std::lock_guard<std::mutex> lock(clock_mutex_);
360 #endif
361  return epoch_sec_func_();
362  }
363 
364  private:
365 #if DCCL_THREAD_SUPPORT
366  static std::mutex clock_mutex_;
367 #endif
368  static std::function<int64()> epoch_sec_func_;
369 };
370 
371 typedef double time_wire_type;
375 template <typename TimeType, int conversion_factor>
376 class TimeCodecBase : public DefaultNumericFieldCodec<time_wire_type, TimeType>,
377  public TimeCodecClock
378 {
379  public:
380  TimeCodecBase() {}
381 
382  time_wire_type pre_encode(const TimeType& time_of_day) override
383  {
384  time_wire_type max_secs = max();
385  return std::fmod(time_of_day / static_cast<time_wire_type>(conversion_factor), max_secs);
386  }
387 
388  TimeType post_decode(const time_wire_type& encoded_time) override
389  {
390  auto max_secs = static_cast<int64>(max());
391  int64_t now_secs = this->epoch_sec();
392  int64 daystart = now_secs - (now_secs % max_secs);
393  int64 today_time = now_secs - daystart;
394 
395  // If time is more than 12 hours ahead of now, assume it's yesterday.
396  if ((encoded_time - today_time) > (max_secs / 2))
397  daystart -= max_secs;
398  else if ((today_time - encoded_time) > (max_secs / 2))
399  daystart += max_secs;
400 
401  return dccl::round((TimeType)(conversion_factor * (daystart + encoded_time)),
402  precision() - std::log10((double)conversion_factor));
403  }
404 
405  private:
406  void validate() override
407  {
409  }
410 
411  double max() override
412  {
413  return FieldCodecBase::dccl_field_options().num_days() * SECONDS_IN_DAY;
414  }
415 
416  double min() override { return 0; }
417  double precision() override
418  {
419  if (!FieldCodecBase::dccl_field_options().has_precision())
420  return 0; // default to second precision
421  else
422  {
423  return FieldCodecBase::dccl_field_options().precision() +
424  (double)std::log10((double)conversion_factor);
425  }
426  }
427 
428  private:
429  enum
430  {
431  SECONDS_IN_DAY = 86400
432  };
433 };
434 
435 template <typename TimeType> class TimeCodec : public TimeCodecBase<TimeType, 0>
436 {
437  static_assert(sizeof(TimeCodec) == 0, "Must use specialization of TimeCodec");
438 };
439 
440 template <> class TimeCodec<uint64> : public TimeCodecBase<uint64, 1000000>
441 {
442 };
443 template <> class TimeCodec<int64> : public TimeCodecBase<int64, 1000000>
444 {
445 };
446 template <> class TimeCodec<double> : public TimeCodecBase<double, 1>
447 {
448 };
449 
451 template <typename T> class StaticCodec : public TypedFixedFieldCodec<T>
452 {
453  Bitset encode(const T&) override { return Bitset(size()); }
454 
455  Bitset encode() override { return Bitset(size()); }
456 
457  T decode(Bitset* /*bits*/) override
458  {
459  std::istringstream iss(FieldCodecBase::dccl_field_options().static_value());
460  T value;
461  iss >> value;
462  return value;
463  }
464 
465  unsigned size() override { return 0; }
466 
467  void validate() override
468  {
470  "missing (dccl.field).static_value");
471 
472  std::string t = FieldCodecBase::dccl_field_options().static_value();
473  std::istringstream iss(t);
474  T value;
475 
476  if (!(iss >> value))
477  {
478  FieldCodecBase::require(false, "invalid static_value for this type.");
479  }
480  }
481 };
482 } // namespace v2
483 } // namespace dccl
484 
485 #endif
A variable size container of bits (subclassed from std::deque<bool>) with an optional hierarchy....
Definition: bitset.h:43
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:236
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:348
dccl::DCCLFieldOptions dccl_field_options() const
Get the DCCL field option extension value for the current field.
Definition: field_codec.h:334
bool use_required()
Whether to use the required or optional encoding.
Definition: field_codec.h:385
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:62
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:47
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