1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef CAST_STREAMING_EXPANDED_VALUE_BASE_H_ 6 #define CAST_STREAMING_EXPANDED_VALUE_BASE_H_ 7 8 #include <stdint.h> 9 10 #include <limits> 11 12 #include "util/osp_logging.h" 13 14 namespace openscreen { 15 namespace cast { 16 17 // Abstract base template class for common "sequence value" data types such as 18 // RtpTimeTicks, FrameId, or PacketId which generally increment/decrement in 19 // predictable amounts as media is streamed, and which often need to be reliably 20 // truncated and re-expanded for over-the-wire transmission. 21 // 22 // FullWidthInteger should be a signed integer POD type that is of sufficiently 23 // high width (in bits) such that it is never expected to under/overflow during 24 // the longest reasonable length of continuous system operation. Subclass is 25 // the class inheriting the common functionality provided in this template, and 26 // is used to provide operator overloads. The Subclass must friend this class 27 // to enable these operator overloads. 28 // 29 // Please see RtpTimeTicks and unit test code for examples of how to define 30 // Subclasses and add features specific to their concrete data type, and how to 31 // use data types derived from ExpandedValueBase. For example, a RtpTimeTicks 32 // adds math operators consisting of the meaningful and valid set of operations 33 // allowed for doing "time math." On the other hand, FrameId only adds math 34 // operators for incrementing/decrementing since multiplication and division are 35 // meaningless. 36 template <typename FullWidthInteger, class Subclass> 37 class ExpandedValueBase { 38 static_assert(std::numeric_limits<FullWidthInteger>::is_signed, 39 "FullWidthInteger must be a signed integer."); 40 static_assert(std::numeric_limits<FullWidthInteger>::is_integer, 41 "FullWidthInteger must be a signed integer."); 42 43 public: 44 // Methods that return the lower bits of this value. This should only be used 45 // for serializing/wire-formatting, and not to subvert the restricted set of 46 // operators allowed on this data type. lower_8_bits()47 constexpr uint8_t lower_8_bits() const { 48 return static_cast<uint8_t>(value_); 49 } lower_16_bits()50 constexpr uint16_t lower_16_bits() const { 51 return static_cast<uint16_t>(value_); 52 } lower_32_bits()53 constexpr uint32_t lower_32_bits() const { 54 return static_cast<uint32_t>(value_); 55 } 56 57 // Compute the greatest value less than or equal to |this| value whose lower 58 // bits are those of |x|. The purpose of this method is to re-instantiate an 59 // original value from its truncated form, usually when deserializing 60 // off-the-wire, when |this| value is known to be the greatest possible valid 61 // value. 62 // 63 // Use case example: Start with an original 32-bit value of 0x000001fe (510 64 // decimal) and truncate, throwing away its upper 24 bits: 0xfe. Now, send 65 // this truncated value over-the-wire to a peer who needs to expand it back to 66 // the original 32-bit value. The peer knows that the greatest possible valid 67 // value is 0x00000202 (514 decimal). This method will initially attempt to 68 // just concatenate the upper 24 bits of |this->value_| with |x| (the 8-bit 69 // value), and get a result of 0x000002fe (766 decimal). However, this is 70 // greater than |this->value_|, so the upper 24 bits are subtracted by one to 71 // get 0x000001fe, which is the original value. 72 template <typename ShortUnsigned> ExpandLessThanOrEqual(ShortUnsigned x)73 Subclass ExpandLessThanOrEqual(ShortUnsigned x) const { 74 static_assert(!std::numeric_limits<ShortUnsigned>::is_signed, 75 "|x| must be an unsigned integer."); 76 static_assert(std::numeric_limits<ShortUnsigned>::is_integer, 77 "|x| must be an unsigned integer."); 78 static_assert(sizeof(ShortUnsigned) <= sizeof(FullWidthInteger), 79 "|x| must fit within the FullWidthInteger."); 80 81 if (sizeof(ShortUnsigned) < sizeof(FullWidthInteger)) { 82 // Initially, the |result| is composed of upper bits from |value_| and 83 // lower bits from |x|. 84 const FullWidthInteger short_max = 85 std::numeric_limits<ShortUnsigned>::max(); 86 FullWidthInteger result = (value_ & ~short_max) | x; 87 88 // If the |result| is larger than |value_|, decrement the upper bits by 89 // one. In other words, |x| must always be interpreted as a truncated 90 // version of a value less than or equal to |value_|. 91 if (result > value_) 92 result -= short_max + 1; 93 94 return Subclass(result); 95 } else { 96 // Debug builds: Ensure the highest bit is not set (which would cause 97 // overflow when casting to the signed integer). 98 OSP_DCHECK_EQ( 99 static_cast<ShortUnsigned>(0), 100 x & (static_cast<ShortUnsigned>(1) << ((sizeof(x) * 8) - 1))); 101 return Subclass(x); 102 } 103 } 104 105 // Compute the smallest value greater than |this| value whose lower bits are 106 // those of |x|. 107 template <typename ShortUnsigned> ExpandGreaterThan(ShortUnsigned x)108 Subclass ExpandGreaterThan(ShortUnsigned x) const { 109 const Subclass maximum_possible_result( 110 value_ + std::numeric_limits<ShortUnsigned>::max() + 1); 111 return maximum_possible_result.ExpandLessThanOrEqual(x); 112 } 113 114 // Compute the value closest to |this| value whose lower bits are those of 115 // |x|. The result is always within |max_distance_for_expansion()| of |this| 116 // value. The purpose of this method is to re-instantiate an original value 117 // from its truncated form, usually when deserializing off-the-wire. See 118 // comments for ExpandLessThanOrEqual() above for further explanation. 119 template <typename ShortUnsigned> Expand(ShortUnsigned x)120 Subclass Expand(ShortUnsigned x) const { 121 const Subclass maximum_possible_result( 122 value_ + max_distance_for_expansion<ShortUnsigned>()); 123 return maximum_possible_result.ExpandLessThanOrEqual(x); 124 } 125 126 // Comparison operators. 127 constexpr bool operator==(Subclass rhs) const { return value_ == rhs.value_; } 128 constexpr bool operator!=(Subclass rhs) const { return value_ != rhs.value_; } 129 constexpr bool operator<(Subclass rhs) const { return value_ < rhs.value_; } 130 constexpr bool operator>(Subclass rhs) const { return value_ > rhs.value_; } 131 constexpr bool operator<=(Subclass rhs) const { return value_ <= rhs.value_; } 132 constexpr bool operator>=(Subclass rhs) const { return value_ >= rhs.value_; } 133 134 // (De)Serialize for transmission over IPC. Do not use these to subvert the 135 // valid set of operators allowed by this class or its Subclass. SerializeForIPC()136 uint64_t SerializeForIPC() const { 137 static_assert(sizeof(uint64_t) >= sizeof(FullWidthInteger), 138 "Cannot serialize FullWidthInteger into an uint64_t."); 139 return static_cast<uint64_t>(value_); 140 } DeserializeForIPC(uint64_t serialized)141 static Subclass DeserializeForIPC(uint64_t serialized) { 142 return Subclass(static_cast<FullWidthInteger>(serialized)); 143 } 144 145 // Design limit: Values that are truncated to the ShortUnsigned type must be 146 // no more than this maximum distance from each other in order to ensure the 147 // original value can be determined correctly. 148 template <typename ShortUnsigned> max_distance_for_expansion()149 static constexpr FullWidthInteger max_distance_for_expansion() { 150 return std::numeric_limits<ShortUnsigned>::max() / 2; 151 } 152 153 protected: 154 // Only subclasses are permitted to instantiate directly. ExpandedValueBase(FullWidthInteger value)155 constexpr explicit ExpandedValueBase(FullWidthInteger value) 156 : value_(value) {} 157 158 FullWidthInteger value_; 159 }; 160 161 } // namespace cast 162 } // namespace openscreen 163 164 #endif // CAST_STREAMING_EXPANDED_VALUE_BASE_H_ 165