| // Copyright 2015 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef CAST_STREAMING_IMPL_EXPANDED_VALUE_BASE_H_ | |
| #define CAST_STREAMING_IMPL_EXPANDED_VALUE_BASE_H_ | |
| #include <stdint.h> | |
| #include <limits> | |
| #include "util/osp_logging.h" | |
| namespace openscreen::cast { | |
| // Abstract base template class for common "sequence value" data types such as | |
| // RtpTimeTicks, FrameId, or PacketId which generally increment/decrement in | |
| // predictable amounts as media is streamed, and which often need to be reliably | |
| // truncated and re-expanded for over-the-wire transmission. | |
| // | |
| // FullWidthInteger should be a signed integer POD type that is of sufficiently | |
| // high width (in bits) such that it is never expected to under/overflow during | |
| // the longest reasonable length of continuous system operation. Subclass is | |
| // the class inheriting the common functionality provided in this template, and | |
| // is used to provide operator overloads. The Subclass must friend this class | |
| // to enable these operator overloads. | |
| // | |
| // Please see RtpTimeTicks and unit test code for examples of how to define | |
| // Subclasses and add features specific to their concrete data type, and how to | |
| // use data types derived from ExpandedValueBase. For example, a RtpTimeTicks | |
| // adds math operators consisting of the meaningful and valid set of operations | |
| // allowed for doing "time math." On the other hand, FrameId only adds math | |
| // operators for incrementing/decrementing since multiplication and division are | |
| // meaningless. | |
| template <typename FullWidthInteger, class Subclass> | |
| class ExpandedValueBase { | |
| static_assert(std::numeric_limits<FullWidthInteger>::is_signed, | |
| "FullWidthInteger must be a signed integer."); | |
| static_assert(std::numeric_limits<FullWidthInteger>::is_integer, | |
| "FullWidthInteger must be a signed integer."); | |
| public: | |
| // Methods that return the lower bits of this value. This should only be used | |
| // for serializing/wire-formatting, and not to subvert the restricted set of | |
| // operators allowed on this data type. | |
| constexpr uint8_t lower_8_bits() const { | |
| return static_cast<uint8_t>(value_); | |
| } | |
| constexpr uint16_t lower_16_bits() const { | |
| return static_cast<uint16_t>(value_); | |
| } | |
| constexpr uint32_t lower_32_bits() const { | |
| return static_cast<uint32_t>(value_); | |
| } | |
| // Compute the greatest value less than or equal to `this` value whose lower | |
| // bits are those of `x`. The purpose of this method is to re-instantiate an | |
| // original value from its truncated form, usually when deserializing | |
| // off-the-wire, when `this` value is known to be the greatest possible valid | |
| // value. | |
| // | |
| // Use case example: Start with an original 32-bit value of 0x000001fe (510 | |
| // decimal) and truncate, throwing away its upper 24 bits: 0xfe. Now, send | |
| // this truncated value over-the-wire to a peer who needs to expand it back to | |
| // the original 32-bit value. The peer knows that the greatest possible valid | |
| // value is 0x00000202 (514 decimal). This method will initially attempt to | |
| // just concatenate the upper 24 bits of |this->value_| with |x| (the 8-bit | |
| // value), and get a result of 0x000002fe (766 decimal). However, this is | |
| // greater than |this->value_|, so the upper 24 bits are subtracted by one to | |
| // get 0x000001fe, which is the original value. | |
| template <typename ShortUnsigned> | |
| Subclass ExpandLessThanOrEqual(ShortUnsigned x) const { | |
| static_assert(!std::numeric_limits<ShortUnsigned>::is_signed, | |
| "`x` must be an unsigned integer."); | |
| static_assert(std::numeric_limits<ShortUnsigned>::is_integer, | |
| "`x` must be an unsigned integer."); | |
| static_assert(sizeof(ShortUnsigned) <= sizeof(FullWidthInteger), | |
| "`x` must fit within the FullWidthInteger."); | |
| if (sizeof(ShortUnsigned) < sizeof(FullWidthInteger)) { | |
| // Initially, the `result` is composed of upper bits from `value_` and | |
| // lower bits from `x`. | |
| const FullWidthInteger short_max = | |
| std::numeric_limits<ShortUnsigned>::max(); | |
| FullWidthInteger result = (value_ & ~short_max) | x; | |
| // If the `result` is larger than `value_`, decrement the upper bits by | |
| // one. In other words, `x` must always be interpreted as a truncated | |
| // version of a value less than or equal to `value_`. | |
| if (result > value_) | |
| result -= short_max + 1; | |
| return Subclass(result); | |
| } else { | |
| // Debug builds: Ensure the highest bit is not set (which would cause | |
| // overflow when casting to the signed integer). | |
| OSP_CHECK_EQ( | |
| static_cast<ShortUnsigned>(0), | |
| x & (static_cast<ShortUnsigned>(1) << ((sizeof(x) * 8) - 1))); | |
| return Subclass(x); | |
| } | |
| } | |
| // Compute the smallest value greater than `this` value whose lower bits are | |
| // those of `x`. | |
| template <typename ShortUnsigned> | |
| Subclass ExpandGreaterThan(ShortUnsigned x) const { | |
| const Subclass maximum_possible_result( | |
| value_ + std::numeric_limits<ShortUnsigned>::max() + 1); | |
| return maximum_possible_result.ExpandLessThanOrEqual(x); | |
| } | |
| // Compute the value closest to `this` value whose lower bits are those of | |
| // `x`. The result is always within `max_distance_for_expansion()` of `this` | |
| // value. The purpose of this method is to re-instantiate an original value | |
| // from its truncated form, usually when deserializing off-the-wire. See | |
| // comments for ExpandLessThanOrEqual() above for further explanation. | |
| template <typename ShortUnsigned> | |
| Subclass Expand(ShortUnsigned x) const { | |
| const Subclass maximum_possible_result( | |
| value_ + max_distance_for_expansion<ShortUnsigned>()); | |
| return maximum_possible_result.ExpandLessThanOrEqual(x); | |
| } | |
| // Comparison operators. | |
| constexpr bool operator==(const ExpandedValueBase& rhs) const { | |
| return value_ == rhs.value_; | |
| } | |
| constexpr bool operator!=(const ExpandedValueBase& rhs) const { | |
| return value_ != rhs.value_; | |
| } | |
| constexpr bool operator<(const ExpandedValueBase& rhs) const { | |
| return value_ < rhs.value_; | |
| } | |
| constexpr bool operator>(const ExpandedValueBase& rhs) const { | |
| return value_ > rhs.value_; | |
| } | |
| constexpr bool operator<=(const ExpandedValueBase& rhs) const { | |
| return value_ <= rhs.value_; | |
| } | |
| constexpr bool operator>=(const ExpandedValueBase& rhs) const { | |
| return value_ >= rhs.value_; | |
| } | |
| // (De)Serialize for transmission over IPC. Do not use these to subvert the | |
| // valid set of operators allowed by this class or its Subclass. | |
| uint64_t SerializeForIPC() const { | |
| static_assert(sizeof(uint64_t) >= sizeof(FullWidthInteger), | |
| "Cannot serialize FullWidthInteger into an uint64_t."); | |
| return static_cast<uint64_t>(value_); | |
| } | |
| static Subclass DeserializeForIPC(uint64_t serialized) { | |
| return Subclass(static_cast<FullWidthInteger>(serialized)); | |
| } | |
| // Design limit: Values that are truncated to the ShortUnsigned type must be | |
| // no more than this maximum distance from each other in order to ensure the | |
| // original value can be determined correctly. | |
| template <typename ShortUnsigned> | |
| static constexpr FullWidthInteger max_distance_for_expansion() { | |
| return std::numeric_limits<ShortUnsigned>::max() / 2; | |
| } | |
| protected: | |
| // Only subclasses are permitted to instantiate directly. | |
| constexpr explicit ExpandedValueBase(FullWidthInteger value) | |
| : value_(value) {} | |
| FullWidthInteger value_; | |
| }; | |
| } // namespace openscreen::cast | |
| #endif // CAST_STREAMING_IMPL_EXPANDED_VALUE_BASE_H_ |