portable_binary.hpp 14 KB

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