1 /*! \file binary.hpp 2 \brief Binary input and output archives */ 3 /* 4 Copyright (c) 2014, Randolph Voorhies, Shane Grant 5 All rights reserved. 6 7 Redistribution and use in source and binary forms, with or without 8 modification, are permitted provided that the following conditions are met: 9 * Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above copyright 12 notice, this list of conditions and the following disclaimer in the 13 documentation and/or other materials provided with the distribution. 14 * Neither the name of cereal nor the 15 names of its contributors may be used to endorse or promote products 16 derived from this software without specific prior written permission. 17 18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 DISCLAIMED. IN NO EVENT SHALL RANDOLPH VOORHIES OR SHANE GRANT BE LIABLE FOR ANY 22 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 #ifndef CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_ 30 #define CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_ 31 32 #include "cereal/cereal.hpp" 33 #include <sstream> 34 #include <limits> 35 36 namespace cereal 37 { 38 namespace portable_binary_detail 39 { 40 //! Returns true if the current machine is little endian 41 /*! @ingroup Internal */ is_little_endian()42 inline std::uint8_t is_little_endian() 43 { 44 static std::int32_t test = 1; 45 return *reinterpret_cast<std::int8_t*>( &test ) == 1; 46 } 47 48 //! Swaps the order of bytes for some chunk of memory 49 /*! @param data The data as a uint8_t pointer 50 @tparam DataSize The true size of the data 51 @ingroup Internal */ 52 template <std::size_t DataSize> swap_bytes(std::uint8_t * data)53 inline void swap_bytes( std::uint8_t * data ) 54 { 55 for( std::size_t i = 0, end = DataSize / 2; i < end; ++i ) 56 std::swap( data[i], data[DataSize - i - 1] ); 57 } 58 } // end namespace portable_binary_detail 59 60 // ###################################################################### 61 //! An output archive designed to save data in a compact binary representation portable over different architectures 62 /*! This archive outputs data to a stream in an extremely compact binary 63 representation with as little extra metadata as possible. 64 65 This archive will record the endianness of the data as well as the desired in/out endianness 66 and assuming that the user takes care of ensuring serialized types are the same size 67 across machines, is portable over different architectures. 68 69 When using a binary archive and a file stream, you must use the 70 std::ios::binary format flag to avoid having your data altered 71 inadvertently. 72 73 \warning This archive has not been thoroughly tested across different architectures. 74 Please report any issues, optimizations, or feature requests at 75 <a href="www.github.com/USCiLab/cereal">the project github</a>. 76 77 \ingroup Archives */ 78 class PortableBinaryOutputArchive : public OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision> 79 { 80 public: 81 //! A class containing various advanced options for the PortableBinaryOutput archive 82 class Options 83 { 84 public: 85 //! Represents desired endianness 86 enum class Endianness : std::uint8_t 87 { big, little }; 88 89 //! Default options, preserve system endianness Default()90 static Options Default(){ return Options(); } 91 92 //! Save as little endian LittleEndian()93 static Options LittleEndian(){ return Options( Endianness::little ); } 94 95 //! Save as big endian BigEndian()96 static Options BigEndian(){ return Options( Endianness::big ); } 97 98 //! Specify specific options for the PortableBinaryOutputArchive 99 /*! @param outputEndian The desired endianness of saved (output) data */ Options(Endianness outputEndian=getEndianness ())100 explicit Options( Endianness outputEndian = getEndianness() ) : 101 itsOutputEndianness( outputEndian ) { } 102 103 private: 104 //! Gets the endianness of the system getEndianness()105 inline static Endianness getEndianness() 106 { return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; } 107 108 //! Checks if Options is set for little endian is_little_endian() const109 inline std::uint8_t is_little_endian() const 110 { return itsOutputEndianness == Endianness::little; } 111 112 friend class PortableBinaryOutputArchive; 113 Endianness itsOutputEndianness; 114 }; 115 116 //! Construct, outputting to the provided stream 117 /*! @param stream The stream to output to. Should be opened with std::ios::binary flag. 118 @param options The PortableBinary specific options to use. See the Options struct 119 for the values of default parameters */ PortableBinaryOutputArchive(std::ostream & stream,Options const & options=Options::Default ())120 PortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) : 121 OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this), 122 itsStream(stream), 123 itsConvertEndianness( portable_binary_detail::is_little_endian() ^ options.is_little_endian() ) 124 { 125 this->operator()( options.is_little_endian() ); 126 } 127 128 ~PortableBinaryOutputArchive() CEREAL_NOEXCEPT = default; 129 130 //! Writes size bytes of data to the output stream 131 template <std::size_t DataSize> inline saveBinary(const void * data,std::size_t size)132 void saveBinary( const void * data, std::size_t size ) 133 { 134 std::size_t writtenSize = 0; 135 136 if( itsConvertEndianness ) 137 { 138 for( std::size_t i = 0; i < size; i += DataSize ) 139 for( std::size_t j = 0; j < DataSize; ++j ) 140 writtenSize += static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ) + DataSize - j - 1 + i, 1 ) ); 141 } 142 else 143 writtenSize = static_cast<std::size_t>( itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size ) ); 144 145 if(writtenSize != size) 146 throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize)); 147 } 148 149 private: 150 std::ostream & itsStream; 151 const uint8_t itsConvertEndianness; //!< If set to true, we will need to swap bytes upon saving 152 }; 153 154 // ###################################################################### 155 //! An input archive designed to load data saved using PortableBinaryOutputArchive 156 /*! This archive outputs data to a stream in an extremely compact binary 157 representation with as little extra metadata as possible. 158 159 This archive will load the endianness of the serialized data and 160 if necessary transform it to match that of the local machine. This comes 161 at a significant performance cost compared to non portable archives if 162 the transformation is necessary, and also causes a small performance hit 163 even if it is not necessary. 164 165 It is recommended to use portable archives only if you know that you will 166 be sending binary data to machines with different endianness. 167 168 The archive will do nothing to ensure types are the same size - that is 169 the responsibility of the user. 170 171 When using a binary archive and a file stream, you must use the 172 std::ios::binary format flag to avoid having your data altered 173 inadvertently. 174 175 \warning This archive has not been thoroughly tested across different architectures. 176 Please report any issues, optimizations, or feature requests at 177 <a href="www.github.com/USCiLab/cereal">the project github</a>. 178 179 \ingroup Archives */ 180 class PortableBinaryInputArchive : public InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision> 181 { 182 public: 183 //! A class containing various advanced options for the PortableBinaryInput archive 184 class Options 185 { 186 public: 187 //! Represents desired endianness 188 enum class Endianness : std::uint8_t 189 { big, little }; 190 191 //! Default options, preserve system endianness Default()192 static Options Default(){ return Options(); } 193 194 //! Load into little endian LittleEndian()195 static Options LittleEndian(){ return Options( Endianness::little ); } 196 197 //! Load into big endian BigEndian()198 static Options BigEndian(){ return Options( Endianness::big ); } 199 200 //! Specify specific options for the PortableBinaryInputArchive 201 /*! @param inputEndian The desired endianness of loaded (input) data */ Options(Endianness inputEndian=getEndianness ())202 explicit Options( Endianness inputEndian = getEndianness() ) : 203 itsInputEndianness( inputEndian ) { } 204 205 private: 206 //! Gets the endianness of the system getEndianness()207 inline static Endianness getEndianness() 208 { return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; } 209 210 //! Checks if Options is set for little endian is_little_endian() const211 inline std::uint8_t is_little_endian() const 212 { return itsInputEndianness == Endianness::little; } 213 214 friend class PortableBinaryInputArchive; 215 Endianness itsInputEndianness; 216 }; 217 218 //! Construct, loading from the provided stream 219 /*! @param stream The stream to read from. Should be opened with std::ios::binary flag. 220 @param options The PortableBinary specific options to use. See the Options struct 221 for the values of default parameters */ PortableBinaryInputArchive(std::istream & stream,Options const & options=Options::Default ())222 PortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) : 223 InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this), 224 itsStream(stream), 225 itsConvertEndianness( false ) 226 { 227 uint8_t streamLittleEndian; 228 this->operator()( streamLittleEndian ); 229 itsConvertEndianness = options.is_little_endian() ^ streamLittleEndian; 230 } 231 232 ~PortableBinaryInputArchive() CEREAL_NOEXCEPT = default; 233 234 //! Reads size bytes of data from the input stream 235 /*! @param data The data to save 236 @param size The number of bytes in the data 237 @tparam DataSize T The size of the actual type of the data elements being loaded */ 238 template <std::size_t DataSize> inline loadBinary(void * const data,std::size_t size)239 void loadBinary( void * const data, std::size_t size ) 240 { 241 // load data 242 auto const readSize = static_cast<std::size_t>( itsStream.rdbuf()->sgetn( reinterpret_cast<char*>( data ), size ) ); 243 244 if(readSize != size) 245 throw Exception("Failed to read " + std::to_string(size) + " bytes from input stream! Read " + std::to_string(readSize)); 246 247 // flip bits if needed 248 if( itsConvertEndianness ) 249 { 250 std::uint8_t * ptr = reinterpret_cast<std::uint8_t*>( data ); 251 for( std::size_t i = 0; i < size; i += DataSize ) 252 portable_binary_detail::swap_bytes<DataSize>( ptr + i ); 253 } 254 } 255 256 private: 257 std::istream & itsStream; 258 uint8_t itsConvertEndianness; //!< If set to true, we will need to swap bytes upon loading 259 }; 260 261 // ###################################################################### 262 // Common BinaryArchive serialization functions 263 264 //! Saving for POD types to portable binary 265 template<class T> inline 266 typename std::enable_if<std::is_arithmetic<T>::value, void>::type CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar,T const & t)267 CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar, T const & t) 268 { 269 static_assert( !std::is_floating_point<T>::value || 270 (std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559), 271 "Portable binary only supports IEEE 754 standardized floating point" ); 272 ar.template saveBinary<sizeof(T)>(std::addressof(t), sizeof(t)); 273 } 274 275 //! Loading for POD types from portable binary 276 template<class T> inline 277 typename std::enable_if<std::is_arithmetic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar,T & t)278 CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar, T & t) 279 { 280 static_assert( !std::is_floating_point<T>::value || 281 (std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559), 282 "Portable binary only supports IEEE 754 standardized floating point" ); 283 ar.template loadBinary<sizeof(T)>(std::addressof(t), sizeof(t)); 284 } 285 286 //! Serializing NVP types to portable binary 287 template <class Archive, class T> inline CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive,PortableBinaryOutputArchive)288 CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive) 289 CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, NameValuePair<T> & t ) 290 { 291 ar( t.value ); 292 } 293 294 //! Serializing SizeTags to portable binary 295 template <class Archive, class T> inline CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive,PortableBinaryOutputArchive)296 CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive) 297 CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, SizeTag<T> & t ) 298 { 299 ar( t.size ); 300 } 301 302 //! Saving binary data to portable binary 303 template <class T> inline CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar,BinaryData<T> const & bd)304 void CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar, BinaryData<T> const & bd) 305 { 306 typedef typename std::remove_pointer<T>::type TT; 307 static_assert( !std::is_floating_point<TT>::value || 308 (std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559), 309 "Portable binary only supports IEEE 754 standardized floating point" ); 310 311 ar.template saveBinary<sizeof(TT)>( bd.data, static_cast<std::size_t>( bd.size ) ); 312 } 313 314 //! Loading binary data from portable binary 315 template <class T> inline CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar,BinaryData<T> & bd)316 void CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar, BinaryData<T> & bd) 317 { 318 typedef typename std::remove_pointer<T>::type TT; 319 static_assert( !std::is_floating_point<TT>::value || 320 (std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559), 321 "Portable binary only supports IEEE 754 standardized floating point" ); 322 323 ar.template loadBinary<sizeof(TT)>( bd.data, static_cast<std::size_t>( bd.size ) ); 324 } 325 } // namespace cereal 326 327 // register archives for polymorphic support 328 CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryOutputArchive) 329 CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryInputArchive) 330 331 // tie input and output archives together 332 CEREAL_SETUP_ARCHIVE_TRAITS(cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive) 333 334 #endif // CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_ 335