DCCL v4
Loading...
Searching...
No Matches
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
42namespace dccl
43{
45namespace v2
46{
50template <typename WireType, typename FieldType = WireType>
51class 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
239 using TypedFixedFieldCodec<WireType, FieldType>::size;
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
267class 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
287class 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
371typedef double time_wire_type;
375template <typename TimeType, int conversion_factor>
376class 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
435template <typename TimeType> class TimeCodec : public TimeCodecBase<TimeType, 0>
436{
437 static_assert(sizeof(TimeCodec) == 0, "Must use specialization of TimeCodec");
438};
439
440template <> class TimeCodec<uint64> : public TimeCodecBase<uint64, 1000000>
441{
442};
443template <> class TimeCodec<int64> : public TimeCodecBase<int64, 1000000>
444{
445};
446template <> class TimeCodec<double> : public TimeCodecBase<double, 1>
447{
448};
449
451template <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.
dccl::DCCLFieldOptions dccl_field_options() const
Get the DCCL field option extension value for the current field.
bool use_required()
Whether to use the required or optional encoding.
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
std::enable_if< std::is_floating_point< Float >::value, Float >::type quantize(Float value, double interval)
Definition common.h:90
unsigned ceil_log2(dccl::uint64 v)
Definition binary.h:178