blob: ac096a5629f956e6f218d3da55d90e275573e7c4 [file] [log] [blame]
// Copyright 2019 The Chromium OS Authors. All rights reserved.
// 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].
constexpr uint8_t end_of_attributes_tag = 0x03;
constexpr uint8_t max_begin_attribute_group_tag = 0x0f;
constexpr uint8_t min_out_of_band_value_tag = 0x10;
constexpr uint8_t max_out_of_band_value_tag = 0x1f;
constexpr uint8_t min_attribute_syntax_tag = 0x20;
constexpr uint8_t max_attribute_syntax_tag = 0x5f;
constexpr uint8_t begCollection_value_tag = 0x34;
constexpr uint8_t endCollection_value_tag = 0x37;
constexpr uint8_t memberAttrName_value_tag = 0x4a;
constexpr uint8_t textWithoutLanguage_value_tag = 0x41;
constexpr uint8_t nameWithoutLanguage_value_tag = 0x42;
// 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;
}
// Helper for writing unsigned integer to given address.
template <typename UnsignedInt>
inline void WriteAsBytes(UnsignedInt uval, uint8_t* ptr) {
for (auto ptr2 = ptr + sizeof(UnsignedInt) - 1; ptr2 >= ptr; --ptr2) {
*ptr2 = uval & 0xffu;
uval >>= 8;
}
}
template <>
inline void WriteAsBytes<uint8_t>(uint8_t uval, uint8_t* ptr) {
*ptr = uval;
}
// Write signed integer to next sizeof(Integer) bytes with two's-complement
// binary encoding and shifts |ptr| accordingly.
// Integer must be one of the following: int8_t, int16_t, int32_t, int64_t.
template <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");
// 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
WriteAsBytes(uval, *ptr);
*ptr += sizeof(Integer);
}
// Write integer to next BytesCount bytes and shifts the pointer ptr
// accordingly. Two's-complement binary encoding is used.
// returns false <=> given value is out of range (ptr is not shifted)
// returns true <=> given value was written and ptr was shifted by BytesCount
template <size_t BytesCount, typename InputInt>
bool WriteInteger(uint8_t** ptr, InputInt const& val) {
// given type must be integer
static_assert(std::is_integral<InputInt>::value, "integral expected");
// verify
typedef typename IntegerBySize<BytesCount>::type Integer;
if (val < 0) {
if (static_cast<int64_t>(val) < std::numeric_limits<Integer>::min())
return false;
} else {
if (static_cast<int64_t>(val) > std::numeric_limits<Integer>::max())
return false;
}
// write
WriteInteger<Integer>(ptr, val);
return true;
}
} // namespace ipp
#endif // LIBIPP_IPP_ENCODING_H_