| // Copyright 2019 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef LIBIPP_IPP_ENCODING_H_ | |
| #define LIBIPP_IPP_ENCODING_H_ | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <type_traits> | |
| // Internal constants and functions used during parsing & building IPP frames. | |
| // You probably do not want to use it directly. See ipp.h for information how to | |
| // use this library. | |
| namespace ipp { | |
| // Some constants from the IPP specification [rfc8010] (section 3.2). | |
| // begin-attribute-group-tag is from the range 0x00-0x0f without 0x03. | |
| // value-tag is from the range 0x10-0xff. | |
| constexpr uint8_t end_of_attributes_tag = 0x03; | |
| constexpr uint8_t max_begin_attribute_group_tag = 0x0f; | |
| constexpr uint8_t begCollection_value_tag = 0x34; | |
| constexpr uint8_t endCollection_value_tag = 0x37; | |
| constexpr uint8_t memberAttrName_value_tag = 0x4a; | |
| // Helper to match signed integer type basing on number of bytes. | |
| template <size_t BytesCount> | |
| struct IntegerBySize; | |
| template <> | |
| struct IntegerBySize<1> { | |
| typedef int8_t type; | |
| }; | |
| template <> | |
| struct IntegerBySize<2> { | |
| typedef int16_t type; | |
| }; | |
| template <> | |
| struct IntegerBySize<4> { | |
| typedef int32_t type; | |
| }; | |
| // Helper for reading unsigned integer from given address. | |
| template <typename UnsignedInt> | |
| inline UnsignedInt ReadAsBytes(const uint8_t* ptr) { | |
| UnsignedInt uval = 0; | |
| for (auto ptr_end = ptr + sizeof(UnsignedInt); ptr < ptr_end; ++ptr) { | |
| uval <<= 8; | |
| uval += *ptr; | |
| } | |
| return uval; | |
| } | |
| template <> | |
| inline uint8_t ReadAsBytes<uint8_t>(const uint8_t* ptr) { | |
| return *ptr; | |
| } | |
| // Reads signed integer saved on sizeof(Integer) bytes at position ptr with | |
| // two's-complement binary encoding and returns it. | |
| // The following types can be set as Integer: int8_t, int16_t, int32_t, int64_t. | |
| // Input parameter cannot be nullptr. | |
| template <typename Integer> | |
| Integer ReadInteger(const uint8_t* const ptr) { | |
| // given type must by signed integer | |
| static_assert(std::is_integral<Integer>::value, "integral expected"); | |
| static_assert(std::is_signed<Integer>::value, "signed integral expected"); | |
| // finds corresponding unsigned type | |
| typedef typename std::make_unsigned<Integer>::type UnsignedInteger; | |
| // parse two's-complement binary encoding | |
| UnsignedInteger uval = ReadAsBytes<UnsignedInteger>(ptr); | |
| if ((*ptr >> 7) == 0) { | |
| // first bit = 0: positive value or zero | |
| return static_cast<Integer>(uval); | |
| } else if ((uval << 1) == 0) { | |
| // first bit = 1 and the rest are zeroes: minimal possible value | |
| return std::numeric_limits<Integer>::min(); | |
| } else { | |
| // first bit = 1 and at least one other 1: decode from two complement | |
| // conversion | |
| --uval; | |
| return -(static_cast<Integer>(~uval)); | |
| } | |
| } | |
| // Parses signed integer saved on BytesCount bytes at position ptr and saves it | |
| // to given variable. The pointer ptr is shifted by BytesCount. | |
| // Two's-complement binary encoding is assumed. | |
| // Allowed values of BytesCount: 1, 2, 4. | |
| // Input parameters cannot be nullptr. | |
| template <size_t BytesCount, typename OutInt> | |
| void ParseSignedInteger(const uint8_t** ptr, OutInt* out_val) { | |
| // output type must by integer and must be long enough | |
| static_assert(std::is_integral<OutInt>::value, "integral expected"); | |
| static_assert(std::is_signed<OutInt>::value, "signed integral expected"); | |
| static_assert(BytesCount <= sizeof(OutInt), "given variable is too short"); | |
| // parse, verify and return | |
| typedef typename IntegerBySize<BytesCount>::type Integer; | |
| *out_val = ReadInteger<Integer>(*ptr); | |
| *ptr += BytesCount; | |
| } | |
| // Parses unsigned integer saved on BytesCount bytes at position ptr and saves | |
| // it to given variable. The pointer ptr is shifted by BytesCount. If parsed | |
| // value is negative, no changes are made to out_val. | |
| // Two's-complement binary encoding is assumed. | |
| // returns false <=> parsed value is negative (ptr is shifted anyway) | |
| // returns true <=> parsed value is >= 0 | |
| // Allowed values of BytesCount: 1, 2, 4. | |
| // Input parameters cannot be nullptr. | |
| template <size_t BytesCount, typename OutInt> | |
| bool ParseUnsignedInteger(const uint8_t** ptr, OutInt* out_val) { | |
| // output type must by integer and must be long enough | |
| static_assert(std::is_integral<OutInt>::value, "integral expected"); | |
| static_assert(BytesCount <= sizeof(OutInt), "given variable is too short"); | |
| // parse, verify and return | |
| typedef typename IntegerBySize<BytesCount>::type Integer; | |
| const Integer val = ReadInteger<Integer>(*ptr); | |
| *ptr += BytesCount; | |
| if (val < 0) | |
| return false; | |
| *out_val = val; | |
| return true; | |
| } | |
| // Write unsigned integer to next BytesCount bytes with the most | |
| // significant byte coming first and shifts |ptr| accordingly. | |
| // BytesCount must equal sizeof(Integer). | |
| // Integer must be one of the following: uint8_t, uint16_t, uint32_t, | |
| // uint64_t. | |
| template <size_t BytesCount, typename Integer> | |
| inline void WriteUnsigned(uint8_t** ptr, Integer uval) { | |
| // given type must by unsigned integer | |
| static_assert(std::is_integral<Integer>::value, "integral expected"); | |
| static_assert(std::is_unsigned<Integer>::value, "unsigned integral expected"); | |
| static_assert(sizeof(Integer) == BytesCount, "invalid size of integer"); | |
| // save as a sequence of bytes starting from the last one | |
| for (auto ptr2 = *ptr + sizeof(Integer) - 1; ptr2 >= *ptr; --ptr2) { | |
| *ptr2 = uval & 0xffu; | |
| uval >>= 8; | |
| } | |
| // move the pointer | |
| *ptr += sizeof(Integer); | |
| } | |
| template <> | |
| inline void WriteUnsigned<1, uint8_t>(uint8_t** ptr, uint8_t uval) { | |
| **ptr = uval; | |
| ++*ptr; | |
| } | |
| // Write signed integer to next BytesCount bytes with two's-complement | |
| // binary encoding and shifts |ptr| accordingly. | |
| // BytesCount must equal sizeof(Integer). | |
| // Integer must be one of the following: int8_t, int16_t, int32_t, int64_t. | |
| template <size_t BytesCount, typename Integer> | |
| void WriteInteger(uint8_t** ptr, Integer val) { | |
| // given type must by signed integer | |
| static_assert(std::is_integral<Integer>::value, "integral expected"); | |
| static_assert(std::is_signed<Integer>::value, "signed integral expected"); | |
| static_assert(sizeof(Integer) == BytesCount, "invalid size of integer"); | |
| // finds corresponding unsigned type | |
| typedef typename std::make_unsigned<Integer>::type UnsignedInteger; | |
| // save as two's-complement binary encoding | |
| UnsignedInteger uval; | |
| if (val >= 0) { | |
| uval = val; | |
| } else if (val == std::numeric_limits<Integer>::min()) { | |
| // minimal possible value: first bit = 1 and the rest are zeroes | |
| uval = 1; | |
| uval <<= (8 * sizeof(Integer) - 1); | |
| } else { | |
| // decode from two complement conversion | |
| uval = -val; | |
| uval = ~uval; | |
| ++uval; | |
| } | |
| // write value starting from the last byte | |
| WriteUnsigned<BytesCount>(ptr, uval); | |
| } | |
| } // namespace ipp | |
| #endif // LIBIPP_IPP_ENCODING_H_ |