blob: b599dfec4f6b017ec8cb824928c18f22d4dad678 [file] [log] [blame]
// 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_