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 <sys/time.h>
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 "field_codec_default_message.h"
43 
44 namespace dccl
45 {
47 namespace v2
48 {
52 template <typename WireType, typename FieldType = WireType>
53 class DefaultNumericFieldCodec : public TypedFixedFieldCodec<WireType, FieldType>
54 {
55  public:
56  virtual double max()
57  {
58  DynamicConditions& dc = this->dynamic_conditions(this->this_field());
59 
60  double static_max = this->dccl_field_options().max();
61  if (dc.has_max())
62  {
63  dc.regenerate(this->this_message(), this->root_message());
64  // don't let dynamic conditions breach static bounds
65  return std::max(this->dccl_field_options().min(), std::min(dc.max(), static_max));
66  }
67  else
68  {
69  return static_max;
70  }
71  }
72 
73  virtual double min()
74  {
75  DynamicConditions& dc = this->dynamic_conditions(this->this_field());
76  double static_min = this->dccl_field_options().min();
77  if (dc.has_min())
78  {
79  dc.regenerate(this->this_message(), this->root_message());
80 
81  // don't let dynamic conditions breach static bounds
82  return std::min(this->dccl_field_options().max(), std::max(dc.min(), static_min));
83  }
84  else
85  {
86  return static_min;
87  }
88  }
89 
90  virtual double precision() { return FieldCodecBase::dccl_field_options().precision(); }
91 
92  virtual double resolution()
93  {
94  if (FieldCodecBase::dccl_field_options().has_precision())
95  return std::pow(10.0, -precision());
96  // If none is set returns the default resolution (=1)
97  return FieldCodecBase::dccl_field_options().resolution();
98  }
99 
100  void validate() override
101  {
103  "missing (dccl.field).min");
105  "missing (dccl.field).max");
106 
108  "(dccl.field).resolution must be greater than 0");
110  !(FieldCodecBase::dccl_field_options().has_precision() &&
111  FieldCodecBase::dccl_field_options().has_resolution()),
112  "at most one of either (dccl.field).precision or (dccl.field).resolution can be set");
113 
114  validate_numeric_bounds();
115  }
116 
117  void validate_numeric_bounds()
118  {
119  // ensure given max and min fit within WireType ranges
120  FieldCodecBase::require(static_cast<WireType>(min()) >=
121  std::numeric_limits<WireType>::lowest(),
122  "(dccl.field).min must be >= minimum of this field type.");
123  FieldCodecBase::require(static_cast<WireType>(max()) <=
124  std::numeric_limits<WireType>::max(),
125  "(dccl.field).max must be <= maximum of this field type.");
126 
127  // allowable epsilon for min / max to diverge from nearest quantile
128  const double min_max_eps = 1e-10;
129  bool min_multiple_of_res = std::abs(quantize(min(), resolution()) - min()) < min_max_eps;
130  bool max_multiple_of_res = std::abs(quantize(max(), resolution()) - max()) < min_max_eps;
131  if (FieldCodecBase::dccl_field_options().has_resolution())
132  {
133  // ensure that max and min are multiples of the resolution chosen
135  min_multiple_of_res,
136  "(dccl.field).min must be an exact multiple of (dccl.field).resolution");
138  max_multiple_of_res,
139  "(dccl.field).max must be an exact multiple of (dccl.field).resolution");
140  }
141  else
142  {
143  auto res = resolution();
144  // this was previously allowed so we will only give a warning not throw an exception
145  if (!min_multiple_of_res)
146  dccl::dlog.is(dccl::logger::WARN, dccl::logger::GENERAL) &&
147  dccl::dlog << "Warning: (dccl.field).min should be an exact multiple of "
148  "10^(-(dccl.field).precision), i.e. "
149  << res << ": " << this->this_field()->DebugString() << std::endl;
150  if (!max_multiple_of_res)
151  dccl::dlog.is(dccl::logger::WARN, dccl::logger::GENERAL) &&
152  dccl::dlog << "Warning: (dccl.field).max should be an exact multiple of "
153  "10^(-(dccl.field).precision), i.e. "
154  << res << ": " << this->this_field()->DebugString() << std::endl;
155  }
156 
157  // ensure value fits into double
158  FieldCodecBase::require(std::log2(max() - min()) - std::log2(resolution()) <=
159  std::numeric_limits<double>::digits,
160  "[(dccl.field).max-(dccl.field).min]/(dccl.field).resolution must "
161  "fit in a double-precision floating point value. Please increase "
162  "min, decrease max, or decrease precision.");
163  }
164 
165  Bitset encode() override { return Bitset(size()); }
166 
167  Bitset encode(const WireType& value) override
168  {
169  dccl::dlog.is(dccl::logger::DEBUG2, dccl::logger::ENCODE) &&
170  dlog << "Encode " << value << " with bounds: [" << min() << "," << max() << "]"
171  << std::endl;
172 
173  // round first, before checking bounds
174  double res = resolution();
175  WireType wire_value = dccl::quantize(value, res);
176 
177  // check bounds
178  if (wire_value < min() || wire_value > max())
179  {
180  // strict mode
181  if (this->strict())
183  std::string("Value exceeds min/max bounds for field: ") +
184  FieldCodecBase::this_field()->DebugString(),
185  this->this_field(), this->this_descriptor()));
186  // non-strict (default): if out-of-bounds, send as zeros
187  else
188  return Bitset(size());
189  }
190 
191  // calculate the encoded value: remove the minimum, scale for the resolution, cast to int.
192  wire_value -= dccl::quantize(static_cast<WireType>(min()), res);
193  if (res >= 1)
194  wire_value /= res;
195  else
196  wire_value *= (1.0 / res);
197  auto uint_value = static_cast<dccl::uint64>(dccl::round(wire_value, 0));
198 
199  // "presence" value (0)
201  uint_value += 1;
202 
203  Bitset encoded;
204  encoded.from(uint_value, size());
205  return encoded;
206  }
207 
208  WireType decode(Bitset* bits) override
209  {
210  dccl::dlog.is(dccl::logger::DEBUG2, dccl::logger::DECODE) &&
211  dlog << "Decode with bounds: [" << min() << "," << max() << "]" << std::endl;
212 
213  // The line below SHOULD BE:
214  // dccl::uint64 t = bits->to<dccl::uint64>();
215  // But GCC3.3 requires an explicit template modifier on the method.
216  // See, e.g., http://gcc.gnu.org/bugzilla/show_bug.cgi?id=10959
217  dccl::uint64 uint_value = (bits->template to<dccl::uint64>)();
218 
220  {
221  if (!uint_value)
222  throw NullValueException();
223  --uint_value;
224  }
225 
226  auto wire_value = (WireType)uint_value;
227  double res = resolution();
228  if (res >= 1)
229  wire_value *= res;
230  else
231  wire_value /= (1.0 / res);
232 
233  // round values again to properly handle cases where double precision
234  // leads to slightly off values (e.g. 2.099999999 instead of 2.1)
235  wire_value =
236  dccl::quantize(wire_value + dccl::quantize(static_cast<WireType>(min()), res), res);
237  return wire_value;
238  }
239 
240  // bring size(const WireType&) into scope so callers can access it
242 
243  unsigned size() override
244  {
245  // if not required field, leave one value for unspecified (always encoded as 0)
246  unsigned NULL_VALUE = FieldCodecBase::use_required() ? 0 : 1;
247 
248  return dccl::ceil_log2((max() - min()) / resolution() + 1 + NULL_VALUE);
249  }
250 };
251 
256 {
257  private:
258  Bitset encode(const bool& wire_value) override;
259  Bitset encode() override;
260  bool decode(Bitset* bits) override;
261  unsigned size() override;
262  void validate() override;
263 };
264 
268 class DefaultStringCodec : public TypedFieldCodec<std::string>
269 {
270  private:
271  Bitset encode() override;
272  Bitset encode(const std::string& wire_value) override;
273  std::string decode(Bitset* bits) override;
274  unsigned size() override;
275  unsigned size(const std::string& wire_value) override;
276  unsigned max_size() override;
277  unsigned min_size() override;
278  void validate() override;
279 
280  private:
281  enum
282  {
283  MAX_STRING_LENGTH = 255
284  };
285 };
286 
288 class DefaultBytesCodec : public TypedFieldCodec<std::string>
289 {
290  private:
291  Bitset encode() override;
292  Bitset encode(const std::string& wire_value) override;
293  std::string decode(Bitset* bits) override;
294  unsigned size() override;
295  unsigned size(const std::string& wire_value) override;
296  unsigned max_size() override;
297  unsigned min_size() override;
298  void validate() override;
299 };
300 
303  : public DefaultNumericFieldCodec<int32, const google::protobuf::EnumValueDescriptor*>
304 {
305  public:
306  int32 pre_encode(const google::protobuf::EnumValueDescriptor* const& field_value) override;
307  const google::protobuf::EnumValueDescriptor* post_decode(const int32& wire_value) override;
308 
309  private:
310  void validate() override {}
311 
312  double max() override
313  {
314  const google::protobuf::EnumDescriptor* e = this_field()->enum_type();
315  return e->value_count() - 1;
316  }
317  double min() override { return 0; }
318 };
319 
320 typedef double time_wire_type;
324 template <typename TimeType, int conversion_factor>
325 class TimeCodecBase : public DefaultNumericFieldCodec<time_wire_type, TimeType>
326 {
327  public:
328  time_wire_type pre_encode(const TimeType& time_of_day) override
329  {
330  time_wire_type max_secs = max();
331  return std::fmod(time_of_day / static_cast<time_wire_type>(conversion_factor), max_secs);
332  }
333 
334  TimeType post_decode(const time_wire_type& encoded_time) override
335  {
336  auto max_secs = (int64)max();
337  timeval t;
338  gettimeofday(&t, nullptr);
339  int64 now = t.tv_sec;
340  int64 daystart = now - (now % max_secs);
341  int64 today_time = now - daystart;
342 
343  // If time is more than 12 hours ahead of now, assume it's yesterday.
344  if ((encoded_time - today_time) > (max_secs / 2))
345  {
346  daystart -= max_secs;
347  }
348  else if ((today_time - encoded_time) > (max_secs / 2))
349  {
350  daystart += max_secs;
351  }
352 
353  return dccl::round((TimeType)(conversion_factor * (daystart + encoded_time)),
354  precision() - std::log10((double)conversion_factor));
355  }
356 
357  private:
358  void validate() override
359  {
360  DefaultNumericFieldCodec<time_wire_type, TimeType>::validate_numeric_bounds();
361  }
362 
363  double max() override
364  {
365  return FieldCodecBase::dccl_field_options().num_days() * SECONDS_IN_DAY;
366  }
367 
368  double min() override { return 0; }
369  double precision() override
370  {
371  if (!FieldCodecBase::dccl_field_options().has_precision())
372  return 0; // default to second precision
373  else
374  {
375  return FieldCodecBase::dccl_field_options().precision() +
376  (double)std::log10((double)conversion_factor);
377  }
378  }
379 
380  private:
381  enum
382  {
383  SECONDS_IN_DAY = 86400
384  };
385 };
386 
387 template <typename TimeType> class TimeCodec : public TimeCodecBase<TimeType, 0>
388 {
389  static_assert(sizeof(TimeCodec) == 0, "Must use specialization of TimeCodec");
390 };
391 
392 template <> class TimeCodec<uint64> : public TimeCodecBase<uint64, 1000000>
393 {
394 };
395 template <> class TimeCodec<int64> : public TimeCodecBase<int64, 1000000>
396 {
397 };
398 template <> class TimeCodec<double> : public TimeCodecBase<double, 1>
399 {
400 };
401 
403 template <typename T> class StaticCodec : public TypedFixedFieldCodec<T>
404 {
405  Bitset encode(const T&) override { return Bitset(size()); }
406 
407  Bitset encode() override { return Bitset(size()); }
408 
409  T decode(Bitset* /*bits*/) override
410  {
411  std::istringstream iss(FieldCodecBase::dccl_field_options().static_value());
412  T value;
413  iss >> value;
414  return value;
415  }
416 
417  unsigned size() override { return 0; }
418 
419  void validate() override
420  {
422  "missing (dccl.field).static_value");
423 
424  std::string t = FieldCodecBase::dccl_field_options().static_value();
425  std::istringstream iss(t);
426  T value;
427 
428  if (!(iss >> value))
429  {
430  FieldCodecBase::require(false, "invalid static_value for this type.");
431  }
432  }
433 };
434 } // namespace v2
435 } // namespace dccl
436 
437 #endif
dccl::v2::StaticCodec
Placeholder codec that takes no space on the wire (0 bits).
Definition: field_codec_default.h:403
dccl::v2::DefaultNumericFieldCodec::encode
Bitset encode() override
Encode an empty field.
Definition: field_codec_default.h:165
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:602
dccl::uint64
google::protobuf::uint64 uint64
an unsigned 64 bit integer
Definition: common.h:59
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:253
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:167
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:597
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:191
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:328
dccl::v2::TimeCodec
Definition: field_codec_default.h:387
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:246
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:100
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:336
dccl::v2::TimeCodecBase
Encodes time of day (default: second precision, but can be set with (dccl.field).precision extension)
Definition: field_codec_default.h:325
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:61
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:268
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:255
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:302
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:288
dccl::FieldCodecBase::use_required
bool use_required()
Whether to use the required or optional encoding.
Definition: field_codec.h:373
dccl::int32
google::protobuf::int32 int32
a signed 32 bit integer
Definition: common.h:57
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:243
dccl::v2::DefaultNumericFieldCodec
Provides a basic bounded arbitrary length numeric (double, float, uint32, uint64, int32,...
Definition: field_codec_default.h:53
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:208
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:322