Line data Source code
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 the copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS 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 */
42 11912 : inline std::uint8_t is_little_endian()
43 : {
44 : static std::int32_t test = 1;
45 11912 : 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>
53 47700 : inline void swap_bytes( std::uint8_t * data )
54 : {
55 141700 : for( std::size_t i = 0, end = DataSize / 2; i < end; ++i )
56 94000 : std::swap( data[i], data[DataSize - i - 1] );
57 47700 : }
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
90 3604 : static Options Default(){ return Options(); }
91 :
92 : //! Save as little endian
93 2 : static Options LittleEndian(){ return Options( Endianness::little ); }
94 :
95 : //! Save as big endian
96 2 : 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 */
100 3608 : explicit Options( Endianness outputEndian = getEndianness() ) :
101 3608 : itsOutputEndianness( outputEndian ) { }
102 :
103 : private:
104 : //! Gets the endianness of the system
105 3604 : inline static Endianness getEndianness()
106 3604 : { return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
107 :
108 : //! Checks if Options is set for little endian
109 8008 : inline std::uint8_t is_little_endian() const
110 8008 : { 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 */
120 4004 : PortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) :
121 : OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
122 : itsStream(stream),
123 4004 : itsConvertEndianness( portable_binary_detail::is_little_endian() ^ options.is_little_endian() )
124 : {
125 4004 : this->operator()( options.is_little_endian() );
126 4004 : }
127 :
128 4004 : ~PortableBinaryOutputArchive() CEREAL_NOEXCEPT = default;
129 :
130 : //! Writes size bytes of data to the output stream
131 : template <std::streamsize DataSize> inline
132 2573552 : void saveBinary( const void * data, std::streamsize size )
133 : {
134 2573552 : std::streamsize writtenSize = 0;
135 :
136 2573552 : if( itsConvertEndianness )
137 : {
138 25000 : for( std::streamsize i = 0; i < size; i += DataSize )
139 111200 : for( std::streamsize j = 0; j < DataSize; ++j )
140 88800 : writtenSize += itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ) + DataSize - j - 1 + i, 1 );
141 : }
142 : else
143 2570952 : writtenSize = itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size );
144 :
145 2573552 : if(writtenSize != size)
146 0 : throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
147 2573552 : }
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
192 3804 : static Options Default(){ return Options(); }
193 :
194 : //! Load into little endian
195 2 : static Options LittleEndian(){ return Options( Endianness::little ); }
196 :
197 : //! Load into big endian
198 2 : 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 */
202 3808 : explicit Options( Endianness inputEndian = getEndianness() ) :
203 3808 : itsInputEndianness( inputEndian ) { }
204 :
205 : private:
206 : //! Gets the endianness of the system
207 3804 : inline static Endianness getEndianness()
208 3804 : { return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
209 :
210 : //! Checks if Options is set for little endian
211 4204 : inline std::uint8_t is_little_endian() const
212 4204 : { 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 */
222 3804 : PortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) :
223 : InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
224 : itsStream(stream),
225 4204 : itsConvertEndianness( false )
226 : {
227 : uint8_t streamLittleEndian;
228 4204 : this->operator()( streamLittleEndian );
229 4204 : itsConvertEndianness = options.is_little_endian() ^ streamLittleEndian;
230 4204 : }
231 :
232 4204 : ~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::streamsize DataSize> inline
239 2575052 : void loadBinary( void * const data, std::streamsize size )
240 : {
241 : // load data
242 2575052 : auto const readSize = itsStream.rdbuf()->sgetn( reinterpret_cast<char*>( data ), size );
243 :
244 2575052 : if(readSize != size)
245 0 : 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 2575052 : if( itsConvertEndianness )
249 : {
250 3500 : std::uint8_t * ptr = reinterpret_cast<std::uint8_t*>( data );
251 26800 : for( std::streamsize i = 0; i < size; i += DataSize )
252 23300 : portable_binary_detail::swap_bytes<DataSize>( ptr + i );
253 : }
254 2575052 : }
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
267 2501358 : 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 2501358 : ar.template saveBinary<sizeof(T)>(std::addressof(t), sizeof(t));
273 2501358 : }
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
278 2502858 : 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 2502858 : ar.template loadBinary<sizeof(T)>(std::addressof(t), sizeof(t));
284 2502858 : }
285 :
286 : //! Serializing NVP types to portable binary
287 : template <class Archive, class T> inline
288 : CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
289 1481998 : CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, NameValuePair<T> & t )
290 : {
291 1481998 : ar( t.value );
292 1481798 : }
293 :
294 : //! Serializing SizeTags to portable binary
295 : template <class Archive, class T> inline
296 : CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
297 163188 : CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, SizeTag<T> & t )
298 : {
299 163188 : ar( t.size );
300 163188 : }
301 :
302 : //! Saving binary data to portable binary
303 : template <class T> inline
304 72194 : 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 72194 : ar.template saveBinary<sizeof(TT)>( bd.data, static_cast<std::streamsize>( bd.size ) );
312 72194 : }
313 :
314 : //! Loading binary data from portable binary
315 : template <class T> inline
316 72194 : 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 72194 : ar.template loadBinary<sizeof(TT)>( bd.data, static_cast<std::streamsize>( bd.size ) );
324 72194 : }
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_
|