Finish up endian support for portable binary

-added more tests
-should resolve #115
This commit is contained in:
Shane Grant 2015-12-27 23:21:59 -08:00
parent 1ef6415f15
commit 20778fe4b8
2 changed files with 221 additions and 50 deletions

View File

@ -62,8 +62,8 @@ namespace cereal
/*! This archive outputs data to a stream in an extremely compact binary
representation with as little extra metadata as possible.
This archive will record the endianness of the data and assuming that
the user takes care of ensuring serialized types are the same size
This archive will record the endianness of the data as well as the desired in/out endianness
and assuming that the user takes care of ensuring serialized types are the same size
across machines, is portable over different architectures.
When using a binary archive and a file stream, you must use the
@ -78,20 +78,64 @@ namespace cereal
class PortableBinaryOutputArchive : public OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>
{
public:
//! Construct, outputting to the provided stream
/*! @param stream The stream to output to. Can be a stringstream, a file stream, or
even cout! */
PortableBinaryOutputArchive(std::ostream & stream) :
OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
itsStream(stream)
//! A class containing various advanced options for the PortableBinaryOutput archive
class Options
{
this->operator()( portable_binary_detail::is_little_endian() );
public:
//! Represents desired endianness
enum class Endianness : std::uint8_t
{ big, little };
//! Default options, preserve system endianness
static Options Default(){ return Options(); }
//! Save as little endian
static Options LittleEndian(){ return Options( Endianness::little ); }
//! Save as big endian
static Options BigEndian(){ return Options( Endianness::big ); }
//! Specify specific options for the PortableBinaryOutputArchive
/*! @param outputEndian The desired endianness of saved (output) data */
explicit Options( Endianness outputEndian = getEndianness() ) :
itsOutputEndianness( outputEndian ) { }
private:
//! Gets the endianness of the system
inline static Endianness getEndianness()
{ return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
//! Checks if Options is set for little endian
inline bool is_little_endian() const
{ return itsOutputEndianness == Endianness::little; }
friend class PortableBinaryOutputArchive;
Endianness itsOutputEndianness;
};
//! Construct, outputting to the provided stream
/*! @param stream The stream to output to. Should be opened with std::ios::binary flag.
@param options The PortableBinary specific options to use. See the Options struct
for the values of default parameters */
PortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) :
OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
itsStream(stream),
itsConvertEndianness( portable_binary_detail::is_little_endian() ^ options.is_little_endian() )
{
this->operator()( options.is_little_endian() );
}
//! Writes size bytes of data to the output stream
template <std::size_t DataSize> inline
void saveBinary( const void * data, std::size_t size )
{
auto const writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) );
std::size_t writtenSize = 0;
if( itsConvertEndianness )
for( std::size_t i = 0; i < DataSize; ++i )
writtenSize += static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ) + DataSize - i - 1, 1 ) );
else
writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) );
if(writtenSize != size)
throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
@ -99,6 +143,7 @@ namespace cereal
private:
std::ostream & itsStream;
const bool itsConvertEndianness; //!< If set to true, we will need to swap bytes upon saving
};
// ######################################################################
@ -130,23 +175,60 @@ namespace cereal
class PortableBinaryInputArchive : public InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>
{
public:
//! A class containing various advanced options for the PortableBinaryInput archive
class Options
{
public:
//! Represents desired endianness
enum class Endianness : std::uint8_t
{ big, little };
//! Default options, preserve system endianness
static Options Default(){ return Options(); }
//! Load into little endian
static Options LittleEndian(){ return Options( Endianness::little ); }
//! Load into big endian
static Options BigEndian(){ return Options( Endianness::big ); }
//! Specify specific options for the PortableBinaryInputArchive
/*! @param inputEndian The desired endianness of loaded (input) data */
explicit Options( Endianness inputEndian = getEndianness() ) :
itsInputEndianness( inputEndian ) { }
private:
//! Gets the endianness of the system
inline static Endianness getEndianness()
{ return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
//! Checks if Options is set for little endian
inline bool is_little_endian() const
{ return itsInputEndianness == Endianness::little; }
friend class PortableBinaryInputArchive;
Endianness itsInputEndianness;
};
//! Construct, loading from the provided stream
/*! @param stream The stream to read from. */
PortableBinaryInputArchive(std::istream & stream) :
/*! @param stream The stream to read from. Should be opened with std::ios::binary flag.
@param options The PortableBinary specific options to use. See the Options struct
for the values of default parameters */
PortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) :
InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
itsStream(stream),
itsConvertEndianness( false )
{
bool streamLittleEndian;
this->operator()( streamLittleEndian );
itsConvertEndianness = portable_binary_detail::is_little_endian() ^ streamLittleEndian;
itsConvertEndianness = options.is_little_endian() ^ streamLittleEndian;
}
//! Reads size bytes of data from the input stream
/*! @param data The data to save
@param size The number of bytes in the data
@tparam DataSize T The size of the actual type of the data elements being loaded */
template <std::size_t DataSize>
template <std::size_t DataSize> inline
void loadBinary( void * const data, std::size_t size )
{
// load data
@ -180,7 +262,7 @@ namespace cereal
static_assert( !std::is_floating_point<T>::value ||
(std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
"Portable binary only supports IEEE 754 standardized floating point" );
ar.saveBinary(std::addressof(t), sizeof(t));
ar.template saveBinary<sizeof(T)>(std::addressof(t), sizeof(t));
}
//! Loading for POD types from portable binary
@ -219,7 +301,7 @@ namespace cereal
(std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
"Portable binary only supports IEEE 754 standardized floating point" );
ar.saveBinary( bd.data, static_cast<std::size_t>( bd.size ) );
ar.template saveBinary<sizeof(TT)>( bd.data, static_cast<std::size_t>( bd.size ) );
}
//! Loading binary data from portable binary

View File

@ -26,6 +26,7 @@
*/
#include "common.hpp"
#include <boost/test/unit_test.hpp>
#include <cmath>
namespace mynamespace { struct MyCustomClass {}; }
@ -47,7 +48,123 @@ inline void swapBytes( T & t )
cereal::portable_binary_detail::swap_bytes<sizeof(T)>( reinterpret_cast<std::uint8_t*>(&t) );
}
BOOST_AUTO_TEST_CASE( portable_binary_archive )
// swaps all output data
#define CEREAL_TEST_SWAP_OUTPUT \
swapBytes(o_bool); \
swapBytes(o_uint8); \
swapBytes(o_int8); \
swapBytes(o_uint16); \
swapBytes(o_int16); \
swapBytes(o_uint32); \
swapBytes(o_int32); \
swapBytes(o_uint64); \
swapBytes(o_int64); \
swapBytes(o_float); \
swapBytes(o_double);
#define CEREAL_TEST_CHECK_EQUAL \
BOOST_CHECK_EQUAL(i_bool , o_bool); \
BOOST_CHECK_EQUAL(i_uint8 , o_uint8); \
BOOST_CHECK_EQUAL(i_int8 , o_int8); \
BOOST_CHECK_EQUAL(i_uint16 , o_uint16); \
BOOST_CHECK_EQUAL(i_int16 , o_int16); \
BOOST_CHECK_EQUAL(i_uint32 , o_uint32); \
BOOST_CHECK_EQUAL(i_int32 , o_int32); \
BOOST_CHECK_EQUAL(i_uint64 , o_uint64); \
BOOST_CHECK_EQUAL(i_int64 , o_int64); \
if( !std::isnan(i_float) && !std::isnan(o_float) ) BOOST_CHECK_CLOSE(i_float , o_float, (float)1e-5); \
if( !std::isnan(i_float) && !std::isnan(o_float) ) BOOST_CHECK_CLOSE(i_double , o_double, 1e-5);
// Last parameter exists to keep everything hidden in options
template <class IArchive, class OArchive>
void test_endian_serialization( typename IArchive::Options const & iOptions, typename OArchive::Options const & oOptions, const bool inputLittleEndian )
{
std::random_device rd;
std::mt19937 gen(rd());
for(size_t i=0; i<100; ++i)
{
bool o_bool = random_value<uint8_t>(gen) % 2 ? true : false;
uint8_t o_uint8 = random_value<uint8_t>(gen);
int8_t o_int8 = random_value<int8_t>(gen);
uint16_t o_uint16 = random_value<uint16_t>(gen);
int16_t o_int16 = random_value<int16_t>(gen);
uint32_t o_uint32 = random_value<uint32_t>(gen);
int32_t o_int32 = random_value<int32_t>(gen);
uint64_t o_uint64 = random_value<uint64_t>(gen);
int64_t o_int64 = random_value<int64_t>(gen);
float o_float = random_value<float>(gen);
double o_double = random_value<double>(gen);
std::ostringstream os;
{
OArchive oar(os, oOptions);
oar(o_bool);
oar(o_uint8);
oar(o_int8);
oar(o_uint16);
oar(o_int16);
oar(o_uint32);
oar(o_int32);
oar(o_uint64);
oar(o_int64);
oar(o_float);
oar(o_double);
}
bool i_bool = false;
uint8_t i_uint8 = 0;
int8_t i_int8 = 0;
uint16_t i_uint16 = 0;
int16_t i_int16 = 0;
uint32_t i_uint32 = 0;
int32_t i_int32 = 0;
uint64_t i_uint64 = 0;
int64_t i_int64 = 0;
float i_float = 0;
double i_double = 0;
std::istringstream is(os.str());
{
IArchive iar(is, iOptions);
iar(i_bool);
iar(i_uint8);
iar(i_int8);
iar(i_uint16);
iar(i_int16);
iar(i_uint32);
iar(i_int32);
iar(i_uint64);
iar(i_int64);
iar(i_float);
iar(i_double);
}
// Convert to big endian if we expect to read big and didn't start big
if( cereal::portable_binary_detail::is_little_endian() ^ inputLittleEndian ) // Convert to little endian if
{
CEREAL_TEST_SWAP_OUTPUT
}
CEREAL_TEST_CHECK_EQUAL
}
}
BOOST_AUTO_TEST_CASE( portable_binary_archive_endian_conversions )
{
// (last parameter is whether we load as little endian)
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
cereal::PortableBinaryInputArchive::Options::BigEndian(), cereal::PortableBinaryOutputArchive::Options::BigEndian(), false );
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
cereal::PortableBinaryInputArchive::Options::LittleEndian(), cereal::PortableBinaryOutputArchive::Options::BigEndian(), true );
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
cereal::PortableBinaryInputArchive::Options::BigEndian(), cereal::PortableBinaryOutputArchive::Options::LittleEndian(), false );
test_endian_serialization<cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive>(
cereal::PortableBinaryInputArchive::Options::LittleEndian(), cereal::PortableBinaryOutputArchive::Options::LittleEndian(), true );
}
// Tests the default behavior to swap bytes to current machine's endianness
BOOST_AUTO_TEST_CASE( portable_binary_archive_default_behavior )
{
std::random_device rd;
std::mt19937 gen(rd());
@ -67,17 +184,7 @@ BOOST_AUTO_TEST_CASE( portable_binary_archive )
double o_double = random_value<double>(gen);
// swap the bytes on all of the data
swapBytes(o_bool);
swapBytes(o_uint8);
swapBytes(o_int8);
swapBytes(o_uint16);
swapBytes(o_int16);
swapBytes(o_uint32);
swapBytes(o_int32);
swapBytes(o_uint64);
swapBytes(o_int64);
swapBytes(o_float);
swapBytes(o_double);
CEREAL_TEST_SWAP_OUTPUT
std::ostringstream os;
{
@ -99,17 +206,7 @@ BOOST_AUTO_TEST_CASE( portable_binary_archive )
}
// swap back to original values
swapBytes(o_bool);
swapBytes(o_uint8);
swapBytes(o_int8);
swapBytes(o_uint16);
swapBytes(o_int16);
swapBytes(o_uint32);
swapBytes(o_int32);
swapBytes(o_uint64);
swapBytes(o_int64);
swapBytes(o_float);
swapBytes(o_double);
CEREAL_TEST_SWAP_OUTPUT
bool i_bool = false;
uint8_t i_uint8 = 0;
@ -139,17 +236,9 @@ BOOST_AUTO_TEST_CASE( portable_binary_archive )
iar(i_double);
}
BOOST_CHECK_EQUAL(i_bool , o_bool);
BOOST_CHECK_EQUAL(i_uint8 , o_uint8);
BOOST_CHECK_EQUAL(i_int8 , o_int8);
BOOST_CHECK_EQUAL(i_uint16 , o_uint16);
BOOST_CHECK_EQUAL(i_int16 , o_int16);
BOOST_CHECK_EQUAL(i_uint32 , o_uint32);
BOOST_CHECK_EQUAL(i_int32 , o_int32);
BOOST_CHECK_EQUAL(i_uint64 , o_uint64);
BOOST_CHECK_EQUAL(i_int64 , o_int64);
BOOST_CHECK_CLOSE(i_float , o_float, (float)1e-5);
BOOST_CHECK_CLOSE(i_double , o_double, 1e-5);
CEREAL_TEST_CHECK_EQUAL
}
}
#undef CEREAl_TEST_SWAP_DATA
#undef CEREAL_TEST_CHECK_EQUAL