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  {
408  DefaultNumericFieldCodec<time_wire_type, TimeType>::validate_numeric_bounds();
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
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:451
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:163
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:657
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:165
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:260
dccl
Dynamic Compact Control Language namespace.
Definition: any.h:46
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:652
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:382
dccl::v2::TimeCodec
Definition: field_codec_default.h:435
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:36
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:98
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:348
dccl::v2::TimeCodecBase
Encodes time of day (default: second precision, but can be set with (dccl.field).precision extension)
Definition: field_codec_default.h:376
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:236
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:267
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:61
dccl::Bitset
A variable size container of bits (subclassed from std::deque<bool>) with an optional hierarchy....
Definition: bitset.h:42
dccl::v2::DefaultBoolCodec
Provides a bool encoder. Uses 1 bit if field is required, 2 bits if optional
Definition: field_codec_default.h:253
dccl::v2::TimeCodecClock
Definition: field_codec_default.h:323
dccl::OutOfRangeException
Definition: exception.h:67
dccl::v2::DefaultEnumCodec
Provides an enum encoder. This converts the enumeration to an integer (based on the enumeration index...
Definition: field_codec_default.h:301
dccl::TypedFieldCodec
Base class for static-typed (no dccl::any) field encoders/decoders. Most single-valued user defined v...
Definition: field_codec_typed.h:72
dccl::v2::DefaultBytesCodec
Provides an fixed length byte string encoder.
Definition: field_codec_default.h:287
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:385
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:241
dccl::v2::DefaultNumericFieldCodec
Provides a basic bounded arbitrary length numeric (double, float, uint32, uint64, int32,...
Definition: field_codec_default.h:51
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:206
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:334