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