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 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 */
42 23824 : inline std::uint8_t is_little_endian()
43 : {
44 : static std::int32_t test = 1;
45 23824 : 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 95400 : inline void swap_bytes( std::uint8_t * data )
54 : {
55 283400 : for( std::size_t i = 0, end = DataSize / 2; i < end; ++i )
56 188000 : std::swap( data[i], data[DataSize - i - 1] );
57 95400 : }
58 4200 : } // end namespace portable_binary_detail
59 :
60 21000 : // ######################################################################
61 16800 : //! An output archive designed to save data in a compact binary representation portable over different architectures
62 4200 : /*! This archive outputs data to a stream in an extremely compact binary
63 84200 : representation with as little extra metadata as possible.
64 :
65 252600 : This archive will record the endianness of the data as well as the desired in/out endianness
66 168400 : and assuming that the user takes care of ensuring serialized types are the same size
67 84200 : across machines, is portable over different architectures.
68 2800 :
69 : When using a binary archive and a file stream, you must use the
70 5600 : std::ios::binary format flag to avoid having your data altered
71 2800 : inadvertently.
72 2800 :
73 4200 : \warning This archive has not been thoroughly tested across different architectures.
74 : Please report any issues, optimizations, or feature requests at
75 4200 : <a href="www.github.com/USCiLab/cereal">the project github</a>.
76 0 :
77 4200 : \ingroup Archives */
78 0 : class PortableBinaryOutputArchive : public OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>
79 : {
80 0 : public:
81 0 : //! A class containing various advanced options for the PortableBinaryOutput archive
82 0 : 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 : static Options Default(){ return Options(); }
91 :
92 : //! Save as little endian
93 : static Options LittleEndian(){ return Options( Endianness::little ); }
94 :
95 : //! Save as big endian
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 */
100 2002 : explicit Options( Endianness outputEndian = getEndianness() ) :
101 : itsOutputEndianness( outputEndian ) { }
102 :
103 : private:
104 : //! Gets the endianness of the system
105 4006 : 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
109 : inline std::uint8_t is_little_endian() const
110 3002 : { return itsOutputEndianness == Endianness::little; }
111 2002 :
112 : friend class PortableBinaryOutputArchive;
113 4 : Endianness itsOutputEndianness;
114 : };
115 6208 :
116 6012 : //! 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 4004 : for the values of default parameters */
120 9018 : PortableBinaryOutputArchive(std::ostream & stream, Options const & options = Options::Default()) :
121 5014 : OutputArchive<PortableBinaryOutputArchive, AllowEmptyClassElision>(this),
122 : itsStream(stream),
123 : itsConvertEndianness( portable_binary_detail::is_little_endian() ^ options.is_little_endian() )
124 8012 : {
125 9212 : this->operator()( options.is_little_endian() );
126 1200 : }
127 :
128 : ~PortableBinaryOutputArchive() CEREAL_NOEXCEPT = default;
129 3600 :
130 5802 : //! Writes size bytes of data to the output stream
131 200 : template <std::streamsize DataSize> inline
132 : void saveBinary( const void * data, std::streamsize size )
133 2002 : {
134 400 : std::streamsize writtenSize = 0;
135 6408 :
136 2002 : if( itsConvertEndianness )
137 : {
138 6008 : for( std::streamsize i = 0; i < size; i += DataSize )
139 : for( std::streamsize j = 0; j < DataSize; ++j )
140 5806 : writtenSize += itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ) + DataSize - j - 1 + i, 1 );
141 4006 : }
142 212010 : else
143 5806 : writtenSize = itsStream.rdbuf()->sputn( reinterpret_cast<const char*>( data ), size );
144 212010 :
145 2000 : if(writtenSize != size)
146 213810 : throw Exception("Failed to write " + std::to_string(size) + " bytes to output stream! Wrote " + std::to_string(writtenSize));
147 4023522 : }
148 2000 :
149 4023522 : private:
150 200 : std::ostream & itsStream;
151 4023722 : const uint8_t itsConvertEndianness; //!< If set to true, we will need to swap bytes upon saving
152 908101 : };
153 212210 :
154 908101 : // ######################################################################
155 212010 : //! An input archive designed to load data saved using PortableBinaryOutputArchive
156 908101 : /*! This archive outputs data to a stream in an extremely compact binary
157 215810 : representation with as little extra metadata as possible.
158 4258330 :
159 226200 : This archive will load the endianness of the serialized data and
160 4385930 : if necessary transform it to match that of the local machine. This comes
161 3800 : at a significant performance cost compared to non portable archives if
162 4208330 : the transformation is necessary, and also causes a small performance hit
163 2177757 : even if it is not necessary.
164 0 :
165 2182957 : It is recommended to use portable archives only if you know that you will
166 0 : be sending binary data to machines with different endianness.
167 2182957 :
168 87986 : The archive will do nothing to ensure types are the same size - that is
169 184808 : the responsibility of the user.
170 87986 :
171 184808 : When using a binary archive and a file stream, you must use the
172 87986 : std::ios::binary format flag to avoid having your data altered
173 185008 : inadvertently.
174 1280658 :
175 11000 : \warning This archive has not been thoroughly tested across different architectures.
176 1287858 : Please report any issues, optimizations, or feature requests at
177 200 : <a href="www.github.com/USCiLab/cereal">the project github</a>.
178 1278258 :
179 2646103 : \ingroup Archives */
180 0 : class PortableBinaryInputArchive : public InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>
181 2647303 : {
182 0 : public:
183 2647303 : //! A class containing various advanced options for the PortableBinaryInput archive
184 598134 : class Options
185 3402 : {
186 598134 : public:
187 3402 : //! Represents desired endianness
188 598134 : enum class Endianness : std::uint8_t
189 4802 : { big, little };
190 2605917 :
191 207400 : //! Default options, preserve system endianness
192 2727917 : static Options Default(){ return Options(); }
193 1400 :
194 2563117 : //! Load into little endian
195 781883 : static Options LittleEndian(){ return Options( Endianness::little ); }
196 0 :
197 783483 : //! Load into big endian
198 0 : static Options BigEndian(){ return Options( Endianness::big ); }
199 783483 :
200 66000 : //! Specify specific options for the PortableBinaryInputArchive
201 0 : /*! @param inputEndian The desired endianness of loaded (input) data */
202 66200 : explicit Options( Endianness inputEndian = getEndianness() ) :
203 0 : itsInputEndianness( inputEndian ) { }
204 66000 :
205 600 : private:
206 187149 : //! Gets the endianness of the system
207 3000 : inline static Endianness getEndianness()
208 187149 : { return portable_binary_detail::is_little_endian() ? Endianness::little : Endianness::big; }
209 600 :
210 185549 : //! Checks if Options is set for little endian
211 63800 : inline std::uint8_t is_little_endian() const
212 200 : { return itsInputEndianness == Endianness::little; }
213 64800 :
214 0 : friend class PortableBinaryInputArchive;
215 64600 : Endianness itsInputEndianness;
216 161981 : };
217 200 :
218 162181 : //! Construct, loading from the provided stream
219 0 : /*! @param stream The stream to read from. Should be opened with std::ios::binary flag.
220 161981 : @param options The PortableBinary specific options to use. See the Options struct
221 600 : for the values of default parameters */
222 3400 : PortableBinaryInputArchive(std::istream & stream, Options const & options = Options::Default()) :
223 3600 : InputArchive<PortableBinaryInputArchive, AllowEmptyClassElision>(this),
224 1600 : itsStream(stream),
225 400 : itsConvertEndianness( false )
226 : {
227 159781 : uint8_t streamLittleEndian;
228 0 : this->operator()( streamLittleEndian );
229 161381 : itsConvertEndianness = options.is_little_endian() ^ streamLittleEndian;
230 0 : }
231 161381 :
232 600 : ~PortableBinaryInputArchive() CEREAL_NOEXCEPT = default;
233 :
234 2402 : //! Reads size bytes of data from the input stream
235 200 : /*! @param data The data to save
236 400 : @param size The number of bytes in the data
237 1200 : @tparam DataSize T The size of the actual type of the data elements being loaded */
238 200 : template <std::streamsize DataSize> inline
239 1400 : void loadBinary( void * const data, std::streamsize size )
240 200 : {
241 1200 : // load data
242 200 : auto const readSize = itsStream.rdbuf()->sgetn( reinterpret_cast<char*>( data ), size );
243 0 :
244 2002 : if(readSize != size)
245 2002 : 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 1200 : if( itsConvertEndianness )
249 25802 : {
250 3202 : std::uint8_t * ptr = reinterpret_cast<std::uint8_t*>( data );
251 0 : for( std::streamsize i = 0; i < size; i += DataSize )
252 25000 : portable_binary_detail::swap_bytes<DataSize>( ptr + i );
253 2002 : }
254 25802 : }
255 4006 :
256 : private:
257 : std::istream & itsStream;
258 23800 : uint8_t itsConvertEndianness; //!< If set to true, we will need to swap bytes upon loading
259 : };
260 0 :
261 0 : // ######################################################################
262 0 : // Common BinaryArchive serialization functions
263 :
264 25802 : //! Saving for POD types to portable binary
265 4006 : template<class T> inline
266 4006 : typename std::enable_if<std::is_arithmetic<T>::value, void>::type
267 2002 : CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar, T const & t)
268 0 : {
269 : static_assert( !std::is_floating_point<T>::value ||
270 6008 : (std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
271 6008 : "Portable binary only supports IEEE 754 standardized floating point" );
272 2002 : ar.template saveBinary<sizeof(T)>(std::addressof(t), sizeof(t));
273 : }
274 6008 :
275 4006 : //! Loading for POD types from portable binary
276 1200 : template<class T> inline
277 0 : typename std::enable_if<std::is_arithmetic<T>::value, void>::type
278 0 : CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar, T & t)
279 4 : {
280 0 : static_assert( !std::is_floating_point<T>::value ||
281 212610 : (std::is_floating_point<T>::value && std::numeric_limits<T>::is_iec559),
282 4 : "Portable binary only supports IEEE 754 standardized floating point" );
283 : ar.template loadBinary<sizeof(T)>(std::addressof(t), sizeof(t));
284 212610 : }
285 4006 :
286 213818 : //! Serializing NVP types to portable binary
287 1208 : template <class Archive, class T> inline
288 4006 : CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
289 : CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, NameValuePair<T> & t )
290 212610 : {
291 5206 : ar( t.value );
292 5206 : }
293 4006 :
294 0 : //! Serializing SizeTags to portable binary
295 6006 : template <class Archive, class T> inline
296 214610 : CEREAL_ARCHIVE_RESTRICT(PortableBinaryInputArchive, PortableBinaryOutputArchive)
297 185208 : CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar, SizeTag<T> & t )
298 : {
299 : ar( t.size );
300 185008 : }
301 :
302 4208530 : //! Saving binary data to portable binary
303 0 : template <class T> inline
304 : void CEREAL_SAVE_FUNCTION_NAME(PortableBinaryOutputArchive & ar, BinaryData<T> const & bd)
305 4023522 : {
306 186208 : typedef typename std::remove_pointer<T>::type TT;
307 4023722 : static_assert( !std::is_floating_point<TT>::value ||
308 200 : (std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
309 25800 : "Portable binary only supports IEEE 754 standardized floating point" );
310 0 :
311 4023522 : ar.template saveBinary<sizeof(TT)>( bd.data, static_cast<std::streamsize>( bd.size ) );
312 187208 : }
313 6002 :
314 25800 : //! Loading binary data from portable binary
315 23800 : template <class T> inline
316 6002 : void CEREAL_LOAD_FUNCTION_NAME(PortableBinaryInputArchive & ar, BinaryData<T> & bd)
317 4023722 : {
318 1278658 : typedef typename std::remove_pointer<T>::type TT;
319 0 : static_assert( !std::is_floating_point<TT>::value ||
320 23800 : (std::is_floating_point<TT>::value && std::numeric_limits<TT>::is_iec559),
321 1274856 : "Portable binary only supports IEEE 754 standardized floating point" );
322 3802 :
323 2185357 : ar.template loadBinary<sizeof(TT)>( bd.data, static_cast<std::streamsize>( bd.size ) );
324 0 : }
325 23800 : } // namespace cereal
326 934301 :
327 1275056 : // register archives for polymorphic support
328 914303 : CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryOutputArchive)
329 0 : CEREAL_REGISTER_ARCHIVE(cereal::PortableBinaryInputArchive)
330 200 :
331 0 : // tie input and output archives together
332 910501 : CEREAL_SETUP_ARCHIVE_TRAITS(cereal::PortableBinaryInputArchive, cereal::PortableBinaryOutputArchive)
333 1275056 :
334 2570317 : #endif // CEREAL_ARCHIVES_PORTABLE_BINARY_HPP_
|