rpc_portable_binary.hpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  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 cereal 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 RANDOLPH VOORHIES OR SHANE GRANT 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 __ASIO2_RPC_CEREAL_ARCHIVES_PORTABLE_BINARY_HPP__
  28. #define __ASIO2_RPC_CEREAL_ARCHIVES_PORTABLE_BINARY_HPP__
  29. #include <asio2/base/detail/push_options.hpp>
  30. #include <cereal/cereal.hpp>
  31. #include <sstream>
  32. #include <limits>
  33. namespace cereal
  34. {
  35. namespace rpc_portable_binary_detail
  36. {
  37. //! Returns true if the current machine is little endian
  38. /*! @ingroup Internal */
  39. inline std::uint8_t is_little_endian() noexcept
  40. {
  41. static std::int32_t test = 1;
  42. return *reinterpret_cast<std::int8_t*>( &test ) == 1;
  43. }
  44. //! Swaps the order of bytes for some chunk of memory
  45. /*! @param data The data as a uint8_t pointer
  46. @tparam DataSize The true size of the data
  47. @ingroup Internal */
  48. template <std::size_t DataSize>
  49. inline void swap_bytes( std::uint8_t * data ) noexcept
  50. {
  51. for( std::size_t i = 0, end = DataSize / 2; i < end; ++i )
  52. std::swap( data[i], data[DataSize - i - 1] );
  53. }
  54. } // end namespace rpc_portable_binary_detail
  55. // ######################################################################
  56. //! An output archive designed to save data in a compact binary representation portable over different architectures
  57. /*! This archive outputs data to a stream in an extremely compact binary
  58. representation with as little extra metadata as possible.
  59. This archive will record the endianness of the data as well as the desired in/out endianness
  60. and assuming that the user takes care of ensuring serialized types are the same size
  61. across machines, is portable over different architectures.
  62. When using a binary archive and a file stream, you must use the
  63. std::ios::binary format flag to avoid having your data altered
  64. inadvertently.
  65. \warning This archive has not been thoroughly tested across different architectures.
  66. Please report any issues, optimizations, or feature requests at
  67. <a href="www.github.com/USCiLab/cereal">the project github</a>.
  68. \ingroup Archives */
  69. class RPCPortableBinaryOutputArchive : public OutputArchive<RPCPortableBinaryOutputArchive, AllowEmptyClassElision>
  70. {
  71. public:
  72. //! A class containing various advanced options for the PortableBinaryOutput archive
  73. class Options
  74. {
  75. public:
  76. //! Represents desired endianness
  77. enum class Endianness : std::uint8_t
  78. { big, little };
  79. //! Default options, preserve system endianness
  80. static Options Default() noexcept { return Options(); }
  81. //! Save as little endian
  82. static Options LittleEndian() noexcept { return Options( Endianness::little ); }
  83. //! Save as big endian
  84. static Options BigEndian() noexcept { return Options( Endianness::big ); }
  85. //! Specify specific options for the RPCPortableBinaryOutputArchive
  86. /*! @param outputEndian The desired endianness of saved (output) data */
  87. explicit Options( Endianness outputEndian = getEndianness() ) noexcept :
  88. itsOutputEndianness( outputEndian ) { }
  89. private:
  90. //! Gets the endianness of the system
  91. inline static Endianness getEndianness() noexcept
  92. { return rpc_portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
  93. //! Checks if Options is set for little endian
  94. inline std::uint8_t is_little_endian() const noexcept
  95. { return itsOutputEndianness == Endianness::little; }
  96. friend class RPCPortableBinaryOutputArchive;
  97. Endianness itsOutputEndianness;
  98. };
  99. template <class T> inline
  100. RPCPortableBinaryOutputArchive & operator<<( T && arg )
  101. {
  102. OutputArchive<RPCPortableBinaryOutputArchive, AllowEmptyClassElision>::operator<<(std::forward<T>(arg));
  103. return (*this);
  104. }
  105. //! Construct, outputting to the provided stream
  106. /*! @param stream The stream to output to. Should be opened with std::ios::binary flag.
  107. @param options The PortableBinary specific options to use. See the Options struct
  108. for the values of default parameters */
  109. RPCPortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) :
  110. OutputArchive<RPCPortableBinaryOutputArchive, AllowEmptyClassElision>(this),
  111. itsStream(stream),
  112. itsConvertEndianness( rpc_portable_binary_detail::is_little_endian() ^ options.is_little_endian() )
  113. {
  114. options_.itsOutputEndianness = options.itsOutputEndianness;
  115. }
  116. ~RPCPortableBinaryOutputArchive() CEREAL_NOEXCEPT = default;
  117. RPCPortableBinaryOutputArchive& save_endian()
  118. {
  119. this->operator()( options_.is_little_endian() );
  120. return (*this);
  121. }
  122. //! Writes size bytes of data to the output stream
  123. template <std::streamsize DataSize> inline
  124. void saveBinary( const void * data, std::streamsize size )
  125. {
  126. std::streamsize writtenSize = 0;
  127. if( itsConvertEndianness )
  128. {
  129. for( std::streamsize i = 0; i < size; i += DataSize )
  130. for( std::streamsize j = 0; j < DataSize; ++j )
  131. writtenSize += itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ) + DataSize - j - 1 + i, 1 );
  132. }
  133. else
  134. writtenSize = itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size );
  135. if(writtenSize != size)
  136. throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
  137. }
  138. inline std::ostream & stream() { return this->itsStream; }
  139. private:
  140. std::ostream & itsStream;
  141. const uint8_t itsConvertEndianness; //!< If set to true, we will need to swap bytes upon saving
  142. Options options_;
  143. };
  144. // ######################################################################
  145. //! An input archive designed to load data saved using RPCPortableBinaryOutputArchive
  146. /*! This archive outputs data to a stream in an extremely compact binary
  147. representation with as little extra metadata as possible.
  148. This archive will load the endianness of the serialized data and
  149. if necessary transform it to match that of the local machine. This comes
  150. at a significant performance cost compared to non portable archives if
  151. the transformation is necessary, and also causes a small performance hit
  152. even if it is not necessary.
  153. It is recommended to use portable archives only if you know that you will
  154. be sending binary data to machines with different endianness.
  155. The archive will do nothing to ensure types are the same size - that is
  156. the responsibility of the user.
  157. When using a binary archive and a file stream, you must use the
  158. std::ios::binary format flag to avoid having your data altered
  159. inadvertently.
  160. \warning This archive has not been thoroughly tested across different architectures.
  161. Please report any issues, optimizations, or feature requests at
  162. <a href="www.github.com/USCiLab/cereal">the project github</a>.
  163. \ingroup Archives */
  164. class RPCPortableBinaryInputArchive : public InputArchive<RPCPortableBinaryInputArchive, AllowEmptyClassElision>
  165. {
  166. public:
  167. //! A class containing various advanced options for the PortableBinaryInput archive
  168. class Options
  169. {
  170. public:
  171. //! Represents desired endianness
  172. enum class Endianness : std::uint8_t
  173. { big, little };
  174. //! Default options, preserve system endianness
  175. static Options Default() noexcept { return Options(); }
  176. //! Load into little endian
  177. static Options LittleEndian() noexcept { return Options( Endianness::little ); }
  178. //! Load into big endian
  179. static Options BigEndian() noexcept { return Options( Endianness::big ); }
  180. //! Specify specific options for the RPCPortableBinaryInputArchive
  181. /*! @param inputEndian The desired endianness of loaded (input) data */
  182. explicit Options( Endianness inputEndian = getEndianness() ) noexcept :
  183. itsInputEndianness( inputEndian ) { }
  184. private:
  185. //! Gets the endianness of the system
  186. inline static Endianness getEndianness() noexcept
  187. { return rpc_portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
  188. //! Checks if Options is set for little endian
  189. inline std::uint8_t is_little_endian() const noexcept
  190. { return itsInputEndianness == Endianness::little; }
  191. friend class RPCPortableBinaryInputArchive;
  192. Endianness itsInputEndianness;
  193. };
  194. template <class T> inline
  195. RPCPortableBinaryInputArchive & operator>>( T && arg )
  196. {
  197. InputArchive<RPCPortableBinaryInputArchive, AllowEmptyClassElision>::operator>>(std::forward<T>(arg));
  198. return (*this);
  199. }
  200. //! Construct, loading from the provided stream
  201. /*! @param stream The stream to read from. Should be opened with std::ios::binary flag.
  202. @param options The PortableBinary specific options to use. See the Options struct
  203. for the values of default parameters */
  204. RPCPortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) :
  205. InputArchive<RPCPortableBinaryInputArchive, AllowEmptyClassElision>(this),
  206. itsStream(stream),
  207. itsConvertEndianness( false )
  208. {
  209. options_.itsInputEndianness = options.itsInputEndianness;
  210. }
  211. ~RPCPortableBinaryInputArchive() CEREAL_NOEXCEPT = default;
  212. RPCPortableBinaryInputArchive& load_endian()
  213. {
  214. uint8_t streamLittleEndian;
  215. this->operator()( streamLittleEndian );
  216. if (streamLittleEndian > uint8_t(1))
  217. throw Exception("Illegal data");
  218. itsConvertEndianness = options_.is_little_endian() ^ streamLittleEndian;
  219. return (*this);
  220. }
  221. //! Reads size bytes of data from the input stream
  222. /*! @param data The data to save
  223. @param size The number of bytes in the data
  224. @tparam DataSize T The size of the actual type of the data elements being loaded */
  225. template <std::streamsize DataSize> inline
  226. void loadBinary( void * const data, std::streamsize size )
  227. {
  228. // load data
  229. auto const readSize = itsStream.rdbuf()->sgetn( reinterpret_cast<char*>( data ), size );
  230. if(readSize != size)
  231. throw Exception("Failed to read " + std::to_string(size) + " bytes from input stream! Read " + std::to_string(readSize));
  232. // flip bits if needed
  233. if( itsConvertEndianness )
  234. {
  235. std::uint8_t * ptr = reinterpret_cast<std::uint8_t*>( data );
  236. for( std::streamsize i = 0; i < size; i += DataSize )
  237. rpc_portable_binary_detail::swap_bytes<DataSize>( ptr + i );
  238. }
  239. }
  240. inline std::istream & stream() { return this->itsStream; }
  241. private:
  242. std::istream & itsStream;
  243. uint8_t itsConvertEndianness; //!< If set to true, we will need to swap bytes upon loading
  244. Options options_;
  245. };
  246. // ######################################################################
  247. // Common BinaryArchive serialization functions
  248. //! Saving for POD types to portable binary
  249. template<class T> inline
  250. typename std::enable_if<std::is_arithmetic<T>::value, void>::type
  251. CEREAL_SAVE_FUNCTION_NAME(RPCPortableBinaryOutputArchive & ar, T const & t)
  252. {
  253. static_assert( !std::is_floating_point<T>::value ||
  254. (std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
  255. "Portable binary only supports IEEE 754 standardized floating point" );
  256. ar.template saveBinary<sizeof(T)>(std::addressof(t), sizeof(t));
  257. }
  258. //! Loading for POD types from portable binary
  259. template<class T> inline
  260. typename std::enable_if<std::is_arithmetic<T>::value, void>::type
  261. CEREAL_LOAD_FUNCTION_NAME(RPCPortableBinaryInputArchive & ar, T & t)
  262. {
  263. static_assert( !std::is_floating_point<T>::value ||
  264. (std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
  265. "Portable binary only supports IEEE 754 standardized floating point" );
  266. ar.template loadBinary<sizeof(T)>(std::addressof(t), sizeof(t));
  267. }
  268. //! Serializing NVP types to portable binary
  269. template <class Archive, class T> inline
  270. CEREAL_ARCHIVE_RESTRICT(RPCPortableBinaryInputArchive, RPCPortableBinaryOutputArchive)
  271. CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, NameValuePair<T> & t )
  272. {
  273. ar( t.value );
  274. }
  275. //! Serializing SizeTags to portable binary
  276. template <class Archive, class T> inline
  277. CEREAL_ARCHIVE_RESTRICT(RPCPortableBinaryInputArchive, RPCPortableBinaryOutputArchive)
  278. CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, SizeTag<T> & t )
  279. {
  280. ar( t.size );
  281. }
  282. //! Saving binary data to portable binary
  283. template <class T> inline
  284. void CEREAL_SAVE_FUNCTION_NAME(RPCPortableBinaryOutputArchive & ar, BinaryData<T> const & bd)
  285. {
  286. typedef typename std::remove_pointer<T>::type TT;
  287. static_assert( !std::is_floating_point<TT>::value ||
  288. (std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
  289. "Portable binary only supports IEEE 754 standardized floating point" );
  290. ar.template saveBinary<sizeof(TT)>( bd.data, static_cast<std::streamsize>( bd.size ) );
  291. }
  292. //! Loading binary data from portable binary
  293. template <class T> inline
  294. void CEREAL_LOAD_FUNCTION_NAME(RPCPortableBinaryInputArchive & ar, BinaryData<T> & bd)
  295. {
  296. typedef typename std::remove_pointer<T>::type TT;
  297. static_assert( !std::is_floating_point<TT>::value ||
  298. (std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
  299. "Portable binary only supports IEEE 754 standardized floating point" );
  300. ar.template loadBinary<sizeof(TT)>( bd.data, static_cast<std::streamsize>( bd.size ) );
  301. }
  302. // ######################################################################
  303. //! Epilogue for SizeTags for RPCPortableBinary archives
  304. template <class T> inline
  305. void epilogue( RPCPortableBinaryOutputArchive & ar, SizeTag<T> const & sz)
  306. {
  307. std::ignore = ar;
  308. std::ignore = sz;
  309. }
  310. //! Epilogue for SizeTags for RPCPortableBinary archives
  311. template <class T> inline
  312. void epilogue( RPCPortableBinaryInputArchive & ar, SizeTag<T> const & sz)
  313. {
  314. std::streambuf* buff = ar.stream().rdbuf();
  315. std::streamsize need = std::streamsize(sz.size);
  316. std::streamsize have = buff ? buff->in_avail() : std::streamsize(0);
  317. if (need < 0 || need > have)
  318. throw Exception("Illegal data");
  319. }
  320. using rpc_oarchive = RPCPortableBinaryOutputArchive;
  321. using rpc_iarchive = RPCPortableBinaryInputArchive;
  322. } // namespace cereal
  323. // register archives for polymorphic support
  324. CEREAL_REGISTER_ARCHIVE(cereal::RPCPortableBinaryOutputArchive)
  325. CEREAL_REGISTER_ARCHIVE(cereal::RPCPortableBinaryInputArchive)
  326. // tie input and output archives together
  327. CEREAL_SETUP_ARCHIVE_TRAITS(cereal::RPCPortableBinaryInputArchive, cereal::RPCPortableBinaryOutputArchive)
  328. #include <asio2/base/detail/pop_options.hpp>
  329. #endif // __ASIO2_RPC_CEREAL_ARCHIVES_PORTABLE_BINARY_HPP__