libcruft-util/endian.hpp

307 lines
8.9 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-2020, Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "std.hpp"
#include "cast.hpp"
#include "types/sized.hpp"
#include <compare>
#include <iosfwd>
#include <type_traits>
#include <cstdint>
#include <cstring>
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); }
//-------------------------------------------------------------------------
constexpr f32
bswap (f32 v) noexcept
{
return cruft::cast::bit<f32> (bswap (cruft::cast::bit<u32> (v)));
}
constexpr f64
bswap (f64 v) noexcept
{
return cruft::cast::bit<f64> (bswap (cruft::cast::bit<u64> (v)));
}
//-------------------------------------------------------------------------
template <typename T>
constexpr T
identity (T &&v) {
return v;
}
//-------------------------------------------------------------------------
#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); }
/// 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 (u08 const *data)
{
std::aligned_union_t <sizeof (T), T> buffer;
memcpy (reinterpret_cast<char *> (&buffer), data, sizeof (T));
return *reinterpret_cast<const T*> (&buffer);
}
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readle (u08 const* ptr)
{
return ltoh (readhe<T> (ptr));
}
template <
typename T,
typename = std::enable_if_t<
std::is_integral_v<T>
>
>
T readbe (u08 const* ptr)
{
return btoh (readhe<T> (ptr));
}
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 SchemeV> struct converter<SchemeV, SchemeV> {
template <typename T>
static T convert (T t) { return 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 types::sized::bytes<sizeof(T)>::sint sint;
typename types::sized::bytes<sizeof(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 alignas(RawT) 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 lu16 = endian::little<u16>;
using lu32 = endian::little<u32>;
using lu64 = endian::little<u64>;
using li16 = endian::little<i16>;
using li32 = endian::little<i32>;
using li64 = endian::little<i64>;
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>;
}
#include <fmt/ostream.h>
template <cruft::endian::scheme SchemeV, typename StorageT>
struct fmt::formatter<cruft::endian::value<SchemeV, StorageT>> :
public fmt::ostream_formatter
{};