libcruft-util/endian.hpp

277 lines
8.3 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2010-2019 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "std.hpp"
#include "types/bits.hpp"
#include "cast.hpp"
#include <cstring>
#include <cstdint>
#include <type_traits>
#include <iosfwd>
namespace cruft {
//-------------------------------------------------------------------------
// CXX: Update using "if constexpr" when available. It doesn't seem to be
// worth the dicking about to use enable_if_t to differentiate the
// signed/unsigned cases.
#define SIGNED_BSWAP(S) \
constexpr \
int##S##_t \
bswap (int##S##_t v) noexcept { \
const union { \
int##S##_t s; \
uint##S##_t u; \
} vu = { v }; \
\
return __builtin_bswap##S (vu.u); \
}
SIGNED_BSWAP(16)
SIGNED_BSWAP(32)
SIGNED_BSWAP(64)
constexpr int8_t bswap ( int8_t v) noexcept { return v; }
constexpr uint8_t bswap (uint8_t v) noexcept { return v; }
constexpr uint16_t bswap (uint16_t v) noexcept { return __builtin_bswap16 (v); }
constexpr uint32_t bswap (uint32_t v) noexcept { return __builtin_bswap32 (v); }
constexpr uint64_t bswap (uint64_t v) noexcept { return __builtin_bswap64 (v); }
// use a type-punning union to punt the byte swapping operation to the
// unsigned integer code.
//
// this is debatably safe under production compilers like gcc + clang
// despite what the standard may say about unions.
constexpr float
bswap (float v) noexcept
{
union {
float f;
uint32_t u;
} temp { .f = v };
temp.u = bswap (temp.u);
return temp.f;
}
//-------------------------------------------------------------------------
template <typename T>
constexpr T
identity (T &&v) {
return v;
}
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readle (const void *_data)
{
auto data = reinterpret_cast<const uint8_t*> (_data);
T value = 0;
for (size_t i = 0; i < sizeof (value); ++i)
value |= T{data[i]} << (i * 8);
return value;
}
/// read bytes from a supplied data pointer and return an integer using
/// host endian layout.
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readhe (const std::uint8_t *data)
{
std::aligned_union_t <sizeof (T), T> buffer;
memcpy (reinterpret_cast<char *> (&buffer), data, sizeof (T));
return *reinterpret_cast<const T*> (&buffer);
}
//-------------------------------------------------------------------------
#if defined(WORDS_BIGENDIAN)
template <typename T> constexpr T hton (T v) { return v; }
template <typename T> constexpr T ntoh (T v) { return v; }
template <typename T> constexpr T htob (T v) { return v; }
template <typename T> constexpr T htol (T v) { return bswap (v); }
template <typename T> constexpr T btoh (T v) { return v; }
template <typename T> constexpr T ltoh (T v) { return bswap (v); }
#else
template <typename T> constexpr T hton (T v) { return bswap (v); }
template <typename T> constexpr T ntoh (T v) { return bswap (v); }
template <typename T> constexpr T htob (T v) { return bswap (v); }
template <typename T> constexpr T htol (T v) { return v; }
template <typename T> constexpr T btoh (T v) { return bswap (v); }
template <typename T> constexpr T ltoh (T v) { return v; }
#endif
template <typename T> T btol (T t) { return bswap (t); }
template <typename T> T ltob (T t) { return bswap (t); }
namespace endian {
//---------------------------------------------------------------------
// Uses the TIFF header values. Just because. Don't rely on this.
enum class scheme : uint16_t {
BIG = 0x4D4D,
LITTLE = 0x4949,
big = BIG,
little = LITTLE,
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
native = little,
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
native = big
#else
#error Unhandled endianness
#endif
};
template <scheme DstV, scheme SrcV>
struct converter;
template <> struct converter<scheme::little,scheme::big> {
template <typename T>
static T convert (T t) { return btol (t); }
};
template <> struct converter<scheme::big,scheme::little> {
template <typename T>
static T convert (T t) { return ltob (t); }
};
template <scheme DstV, scheme SrcV, typename T>
T convert (T t)
{
return converter<DstV,SrcV>::convert (t);
}
}
//-------------------------------------------------------------------------
struct from_endian {
explicit from_endian (endian::scheme _endian):
src (_endian)
{ ; }
template <typename T>
T operator() (const T v) const {
static_assert (std::is_integral<T>::value || std::is_enum<T>::value,
"endian conversion is only defined for integrals currently");
union {
typename sized_type<T>::sint sint;
typename sized_type<T>::uint uint;
};
if (std::is_signed<T>::value)
sint = v;
else
uint = v;
uint = (src == endian::scheme::LITTLE) ? ltoh (uint) : btoh (uint);
if (std::is_signed<T>::value)
return T (sint);
else
return T (uint);
}
endian::scheme src;
};
namespace endian {
template <scheme StorageV, typename RawT>
struct value {
constexpr RawT native (void) const { return convert<StorageV,scheme::native> (raw); }
operator RawT () const
{
return native ();
}
decltype(auto) operator+ () const { return +native (); }
constexpr value operator>> (std::size_t s) const {
RawT const val = native () >> s;
return { .raw = convert<scheme::native,StorageV> (val) };
}
constexpr value operator<< (std::size_t s) const {
RawT const val = native () << s;
return { .raw = convert<scheme::native,StorageV> (val) };
}
value operator& (value rhs) const { return value { .raw = raw & rhs.raw }; }
template <typename OperandT>
value operator& (OperandT rhs) const {
RawT const val = native () & rhs;
return { .raw = convert<scheme::native,StorageV> (val) };
}
auto& operator= (RawT const &val) { raw = convert<scheme::native,StorageV> (val); return *this; }
bool operator== (value const &rhs) const { return raw == rhs.raw; }
RawT raw;
};
template <scheme StorageV, typename RawT, typename OperandT>
constexpr decltype(auto) operator== (value<StorageV,RawT> const &a, OperandT const &b)
{
return a.native () == b;
}
template <scheme StorageV, typename RawT, typename OperandT>
constexpr decltype(auto) operator== (OperandT const &a, value<StorageV,RawT> const &b)
{
return a == b.native ();
}
template <typename ValueT> using little = value<scheme::little,ValueT>;
template <typename ValueT> using big = value<scheme::big ,ValueT>;
template <scheme StorageV, typename ValueT>
std::ostream&
operator<< (std::ostream &os, value<StorageV,ValueT> const &val)
{
return os << +ValueT(val);
}
}
using bu16 = endian::big<u16>;
using bu32 = endian::big<u32>;
using bu64 = endian::big<u64>;
using bi16 = endian::big<i16>;
using bi32 = endian::big<i32>;
using bi64 = endian::big<i64>;
}