Line data Source code
1 : // Copyright (c) 2022 Made to Order Software Corp. All Rights Reserved.
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : // Snap Websites Server -- snap watchdog library
19 : // Snap Websites Servers -- create a feed where you can write an email
20 : #pragma once
21 :
22 : // libexcept
23 : //
24 : #include <libexcept/exception.h>
25 :
26 :
27 : // snapdev
28 : //
29 : #include <snapdev/is_vector.h>
30 : #include <snapdev/not_used.h>
31 : #include <snapdev/sizeof_bitfield.h>
32 :
33 :
34 : // C++
35 : //
36 : #include <cstring>
37 : #include <functional>
38 : #include <map>
39 : #include <memory>
40 : #include <vector>
41 :
42 :
43 :
44 : namespace snapdev
45 : {
46 :
47 :
48 :
49 20 : DECLARE_LOGIC_ERROR(brs_logic_error);
50 :
51 70 : DECLARE_OUT_OF_RANGE(brs_out_of_range);
52 :
53 20 : DECLARE_MAIN_EXCEPTION(brs_error);
54 :
55 12 : DECLARE_EXCEPTION(brs_error, brs_cannot_be_empty);
56 2 : DECLARE_EXCEPTION(brs_error, brs_magic_missing);
57 2 : DECLARE_EXCEPTION(brs_error, brs_magic_unsupported);
58 2 : DECLARE_EXCEPTION(brs_error, brs_map_name_cannot_be_empty);
59 2 : DECLARE_EXCEPTION(brs_error, brs_unknown_type);
60 :
61 :
62 : typedef std::uint32_t magic_t;
63 : typedef std::uint8_t version_t;
64 : typedef std::string name_t;
65 :
66 : typedef std::uint32_t type_t;
67 :
68 : constexpr type_t const TYPE_FIELD = 0; // regular name=value
69 : constexpr type_t const TYPE_ARRAY = 1; // item in an array (includes a 16 bit index)
70 : constexpr type_t const TYPE_MAP = 2; // item in a map (includes a second name)
71 :
72 : struct hunk_sizes_t
73 : {
74 : std::uint32_t f_type : 2; // type of field (see TYPE_...)
75 : std::uint32_t f_name : 7; // size of this field name (1 to 127)
76 : std::uint32_t f_hunk : 23; // size of this field's data (up to 8Mb)
77 : };
78 :
79 :
80 : constexpr version_t const BRS_ROOT = 0; // indicate root buffer
81 : constexpr version_t const BRS_VERSION = 1; // version of the format
82 :
83 :
84 : constexpr magic_t build_magic(char endian)
85 : {
86 : #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
87 : return ('B' << 24) | ('R' << 16) | (endian << 8) | (static_cast<unsigned char>(BRS_VERSION) << 0);
88 : #else
89 : return ('B' << 0) | ('R' << 8) | (endian << 16) | (static_cast<unsigned char>(BRS_VERSION) << 24);
90 : #endif
91 : }
92 :
93 : constexpr magic_t const BRS_MAGIC_BIG_ENDIAN = build_magic('B');
94 : constexpr magic_t const BRS_MAGIC_LITTLE_ENDIAN = build_magic('L');
95 :
96 : #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
97 : constexpr magic_t const BRS_MAGIC = BRS_MAGIC_BIG_ENDIAN;
98 : #else
99 : constexpr magic_t const BRS_MAGIC = BRS_MAGIC_LITTLE_ENDIAN;
100 : #endif
101 :
102 :
103 :
104 : /** \brief Class to serialize your data.
105 : *
106 : * This class is used to serialize your data. You create a serializer and
107 : * then call add_value() with each one of your fields.
108 : *
109 : * The class supports named fields, fields representing arrays, and
110 : * fields representing maps.
111 : *
112 : * \code
113 : * // create a serialize that writes to a stringstream
114 : * //
115 : * std::stringstream buffer;
116 : * snapdev::serializer out(buffer);
117 : *
118 : * // add a value as is; all basic types, strings, etc. are supported
119 : * // in this case you pass 2 values
120 : * // note that my_value can be any basic struct type
121 : * //
122 : * // if you use a binary type (i.e. `std::int16_t`) then you must re-read
123 : * // the value with the exact same type; if the type changes later,
124 : * // if the size is different, you can detect which type using the size
125 : * // otherwise make sure to change the field name
126 : * //
127 : * out.add_value("field-name", my_value);
128 : *
129 : * // for binary data in a buffer, pass a pointer and a size
130 : * //
131 : * out.add_value("field-name", my_buffer, size_of_my_buffer);
132 : *
133 : * // add a value with an index (array)
134 : * // in this case you pass 3 values
135 : * // this also supports the buffer/size as above
136 : * //
137 : * out.add_value("field-name", 123, my_value);
138 : *
139 : * // add a value with a sub-field name (map)
140 : * // in this case you pass 3 values, the second being a string as well
141 : * // this also supports the buffer/size as above
142 : * //
143 : * out.add_value("field-name", "sub-field", my_value);
144 : *
145 : * // the add to the output stream is immediate
146 : * //
147 : * // so to save it to a file you could do this
148 : * //
149 : * std::ofstream p("my-data.brs");
150 : * p << buffer.str();
151 : * \endcode
152 : *
153 : * If you have a sub-class that you want to serialize within the parent,
154 : * you want to use the recursive class like so:
155 : *
156 : * \code
157 : * s.add_value("type", f_type); // regular field
158 : *
159 : * // start the recursive entry
160 : * {
161 : * brs::recursive r(s, "headers");
162 : * for(auto const & h : f_headers)
163 : * {
164 : * h.serialize(s); // serialize one header
165 : *
166 : * // for example, the header serialization may just be a name/value
167 : * // s.add_value("header:" + f_name, f_value);
168 : * }
169 : * }
170 : * // end the recursive entry
171 : *
172 : * s.add_value("body", f_body); // another regular field
173 : * \endcode
174 : *
175 : * Place the brs::recursive inside a sub-block, that way when you hit the '}'
176 : * it closes the sub-field automatically. This is equivalent to calling the
177 : * start_subfield() and end_subfield() in a safe manner.
178 : *
179 : * \tparam S The type of output stream to write the data to.
180 : */
181 : template<typename S>
182 : class serializer
183 : {
184 : public:
185 : /** \brief Initialize the stream with the magic header.
186 : *
187 : * This function adds the magic header at the beginning of your file.
188 : */
189 16 : serializer(S & output)
190 16 : : f_output(output)
191 : {
192 16 : magic_t const magic(BRS_MAGIC);
193 16 : f_output.write(
194 : reinterpret_cast<typename S::char_type const *>(&magic)
195 : , sizeof(magic));
196 16 : }
197 :
198 : template<typename T>
199 40 : void add_value(name_t name, T const * ptr, std::size_t size)
200 : {
201 40 : if(name.length() == 0)
202 : {
203 2 : throw brs_cannot_be_empty("name cannot be an empty string");
204 : }
205 :
206 : #pragma GCC diagnostic push
207 : #pragma GCC diagnostic ignored "-Wpedantic"
208 114 : hunk_sizes_t const hunk_sizes = {
209 : .f_type = TYPE_FIELD,
210 38 : .f_name = static_cast<std::uint8_t>(name.length()),
211 114 : .f_hunk = static_cast<std::uint32_t>(size & ((1 << SIZEOF_BITFIELD(hunk_sizes_t, f_hunk)) - 1)),
212 : };
213 : #pragma GCC diagnostic pop
214 :
215 76 : if(hunk_sizes.f_name != name.length()
216 38 : || hunk_sizes.f_hunk != size)
217 : {
218 11 : throw brs_out_of_range("name or hunk too large");
219 : }
220 :
221 27 : f_output.write(
222 : reinterpret_cast<typename S::char_type const *>(&hunk_sizes)
223 : , sizeof(hunk_sizes));
224 :
225 54 : f_output.write(
226 : reinterpret_cast<typename S::char_type const *>(name.c_str())
227 27 : , hunk_sizes.f_name);
228 :
229 27 : f_output.write(
230 : reinterpret_cast<typename S::char_type const *>(ptr)
231 : , size);
232 27 : }
233 :
234 :
235 : template<typename T>
236 72 : void add_value(name_t name, int index, T const * ptr, std::size_t size)
237 : {
238 72 : if(name.length() == 0)
239 : {
240 1 : throw brs_cannot_be_empty("name cannot be an empty string");
241 : }
242 :
243 : #pragma GCC diagnostic push
244 : #pragma GCC diagnostic ignored "-Wpedantic"
245 213 : hunk_sizes_t const hunk_sizes = {
246 : .f_type = TYPE_ARRAY,
247 71 : .f_name = static_cast<std::uint8_t>(name.length()),
248 213 : .f_hunk = static_cast<std::uint32_t>(size & ((1 << SIZEOF_BITFIELD(hunk_sizes_t, f_hunk)) - 1)),
249 : };
250 : #pragma GCC diagnostic pop
251 71 : std::uint16_t const idx(static_cast<std::uint16_t>(index));
252 :
253 142 : if(hunk_sizes.f_name != name.length()
254 70 : || hunk_sizes.f_hunk != size
255 131 : || index != idx)
256 : {
257 11 : throw brs_out_of_range("name, index, or hunk too large");
258 : }
259 :
260 60 : f_output.write(
261 : reinterpret_cast<typename S::char_type const *>(&hunk_sizes)
262 : , sizeof(hunk_sizes));
263 :
264 60 : f_output.write(
265 : reinterpret_cast<typename S::char_type const *>(&idx)
266 : , sizeof(idx));
267 :
268 120 : f_output.write(
269 : reinterpret_cast<typename S::char_type const *>(name.c_str())
270 60 : , hunk_sizes.f_name);
271 :
272 60 : f_output.write(
273 : reinterpret_cast<typename S::char_type const *>(ptr)
274 : , size);
275 60 : }
276 :
277 :
278 : template<typename T>
279 88 : void add_value(name_t name, name_t sub_name, T const * ptr, std::size_t size)
280 : {
281 88 : if(name.empty())
282 : {
283 1 : throw brs_cannot_be_empty("name cannot be an empty string");
284 : }
285 :
286 87 : if(sub_name.empty())
287 : {
288 1 : throw brs_cannot_be_empty("sub-name cannot be an empty string");
289 : }
290 :
291 : #pragma GCC diagnostic push
292 : #pragma GCC diagnostic ignored "-Wpedantic"
293 258 : hunk_sizes_t const hunk_sizes = {
294 : .f_type = TYPE_MAP,
295 86 : .f_name = static_cast<std::uint8_t>(name.length()),
296 258 : .f_hunk = static_cast<std::uint32_t>(size & ((1 << SIZEOF_BITFIELD(hunk_sizes_t, f_hunk)) - 1)),
297 : };
298 : #pragma GCC diagnostic pop
299 86 : std::uint8_t const len(static_cast<std::uint8_t>(sub_name.length()));
300 :
301 172 : if(hunk_sizes.f_name != name.length()
302 85 : || hunk_sizes.f_hunk != size
303 161 : || sub_name.length() > std::numeric_limits<decltype(len)>::max())
304 : {
305 12 : throw brs_out_of_range("name, sub-name, or hunk too large");
306 : }
307 :
308 74 : f_output.write(
309 : reinterpret_cast<typename S::char_type const *>(&hunk_sizes)
310 : , sizeof(hunk_sizes));
311 :
312 74 : f_output.write(
313 : reinterpret_cast<typename S::char_type const *>(&len)
314 : , sizeof(len));
315 :
316 74 : f_output.write(
317 : reinterpret_cast<typename S::char_type const *>(sub_name.c_str())
318 : , len);
319 :
320 148 : f_output.write(
321 : reinterpret_cast<typename S::char_type const *>(name.c_str())
322 74 : , hunk_sizes.f_name);
323 :
324 74 : f_output.write(
325 : reinterpret_cast<typename S::char_type const *>(ptr)
326 : , size);
327 74 : }
328 :
329 :
330 : // *** FIELDS ***
331 : /** \brief Save a basic type or struct of basic types.
332 : *
333 : * This one function saves the specified value as is. It is expected to be
334 : * a basic type such as an int or a double. It also supports structures
335 : * that are only composed of basic types. Structures and classes with
336 : * complex types such as a string need to be handled manually.
337 : *
338 : * \tparam T The type of value,
339 : * \param[in] name The name of the field to be saved.
340 : * \param[in] value The value to be saved with that name.
341 : */
342 : template<typename T>
343 : typename std::enable_if_t<!snapdev::is_vector_v<T>
344 : && !std::is_same_v<char const *, T>
345 : && !std::is_same_v<std::string const &, T>, void>
346 14 : add_value(name_t name, T const & value)
347 : {
348 14 : add_value(name, &value, sizeof(value));
349 14 : }
350 :
351 :
352 1 : void add_value(name_t name, std::nullptr_t)
353 : {
354 1 : NOT_USED(name);
355 1 : }
356 :
357 :
358 2 : void add_value(name_t name, char const * value)
359 : {
360 2 : add_value(name, value, strlen(value));
361 2 : }
362 :
363 :
364 9 : void add_value(name_t name, std::string const & value)
365 : {
366 9 : add_value(name, value.c_str(), value.length());
367 9 : }
368 :
369 :
370 3 : void add_value_if_not_empty(name_t name, std::string const & value)
371 : {
372 3 : if(!value.empty())
373 : {
374 1 : add_value(name, value.c_str(), value.length());
375 : }
376 3 : }
377 :
378 :
379 : template<typename T>
380 : typename std::enable_if_t<snapdev::is_vector_v<T>>
381 1 : add_value(name_t name, T const & value)
382 : {
383 1 : add_value(name, value.data(), value.size() * sizeof(typename T::value_type));
384 1 : }
385 :
386 :
387 : // *** INDEXES ***
388 : template<typename T>
389 : typename std::enable_if_t<!snapdev::is_vector_v<T>
390 : && !std::is_same_v<char const *, T>, void>
391 10 : add_value(name_t name, int index, T const & value)
392 : {
393 10 : add_value(name, index, &value, sizeof(value));
394 10 : }
395 :
396 :
397 25 : void add_value(name_t name, int index, std::nullptr_t)
398 : {
399 25 : NOT_USED(name, index);
400 25 : }
401 :
402 :
403 12 : void add_value(name_t name, int index, char const * value)
404 : {
405 12 : add_value(name, index, value, strlen(value));
406 12 : }
407 :
408 :
409 38 : void add_value(name_t name, int index, std::string const & value)
410 : {
411 38 : add_value(name, index, value.c_str(), value.length());
412 38 : }
413 :
414 :
415 : // *** MAPS ***
416 : template<typename T>
417 : typename std::enable_if_t<!std::is_same_v<T, typename std::string>, void>
418 49 : add_value(name_t name, name_t sub_name, T const & value)
419 : {
420 49 : add_value(name, sub_name, &value, sizeof(value));
421 49 : }
422 :
423 :
424 25 : void add_value(name_t name, name_t sub_name, std::nullptr_t)
425 : {
426 25 : NOT_USED(name, sub_name);
427 25 : }
428 :
429 :
430 12 : void add_value(name_t name, name_t sub_name, char const * value)
431 : {
432 12 : add_value(name, sub_name, value, strlen(value));
433 12 : }
434 :
435 :
436 6 : void add_value(name_t name, name_t sub_name, std::string const & value)
437 : {
438 6 : add_value(name, sub_name, value.c_str(), value.length());
439 6 : }
440 :
441 :
442 8 : void add_value_if_not_empty(name_t name, name_t sub_name, std::string const & value)
443 : {
444 8 : if(!value.empty())
445 : {
446 7 : add_value(name, sub_name, value.c_str(), value.length());
447 : }
448 8 : }
449 :
450 :
451 : // *** SUB-FIELDS ***
452 9 : void start_subfield(name_t name)
453 : {
454 9 : if(name.empty())
455 : {
456 1 : throw brs_cannot_be_empty("name cannot be an empty string");
457 : }
458 :
459 : #pragma GCC diagnostic push
460 : #pragma GCC diagnostic ignored "-Wpedantic"
461 16 : hunk_sizes_t const hunk_sizes = {
462 : .f_type = TYPE_FIELD,
463 8 : .f_name = static_cast<std::uint8_t>(name.length()),
464 : .f_hunk = 0,
465 : };
466 : #pragma GCC diagnostic pop
467 :
468 8 : if(hunk_sizes.f_name != name.length())
469 : {
470 1 : throw brs_out_of_range("name too large");
471 : }
472 :
473 7 : f_output.write(
474 : reinterpret_cast<typename S::char_type const *>(&hunk_sizes)
475 : , sizeof(hunk_sizes));
476 :
477 14 : f_output.write(
478 : reinterpret_cast<typename S::char_type const *>(name.c_str())
479 7 : , hunk_sizes.f_name);
480 7 : }
481 :
482 :
483 7 : void end_subfield()
484 : {
485 : #pragma GCC diagnostic push
486 : #pragma GCC diagnostic ignored "-Wpedantic"
487 7 : hunk_sizes_t const hunk_sizes = {
488 : .f_type = TYPE_FIELD,
489 : .f_name = 0,
490 : .f_hunk = 0,
491 : };
492 : #pragma GCC diagnostic pop
493 :
494 7 : f_output.write(
495 : reinterpret_cast<typename S::char_type const *>(&hunk_sizes)
496 : , sizeof(hunk_sizes));
497 7 : }
498 :
499 :
500 : private:
501 : S & f_output = S();
502 : };
503 :
504 :
505 : template<typename S>
506 : class recursive
507 : {
508 : public:
509 7 : recursive(serializer<S> & s, name_t name)
510 7 : : f_serializer(s)
511 : {
512 7 : f_serializer.start_subfield(name);
513 7 : }
514 :
515 7 : ~recursive()
516 : {
517 7 : f_serializer.end_subfield();
518 7 : }
519 :
520 : private:
521 : serializer<S> & f_serializer;
522 : };
523 :
524 :
525 :
526 :
527 :
528 :
529 :
530 :
531 : /** \brief When deserializing, the data is saved in a field.
532 : *
533 : * This field holds the data of one field.
534 : */
535 48 : struct field_t
536 : {
537 184 : void reset()
538 : {
539 184 : f_name.clear();
540 184 : f_sub_name.clear();
541 184 : f_index = -1;
542 184 : f_size = 0;
543 184 : }
544 :
545 : std::string f_name = std::string();
546 : std::string f_sub_name = std::string();
547 : int f_index = -1;
548 : std::size_t f_size = 0; // size of the data (still in stream)
549 : };
550 :
551 :
552 : /** \brief Unserialize the specified buffer.
553 : *
554 : * This function reads each hunk and calls the specified \p callback
555 : * with them. You can then save the data in your object fields.
556 : *
557 : * The root buffer is expected to include the magic code at the start.
558 : * Set the \p include_magic to true in that case to verify that it
559 : * is indeed set and valid. If you do not do that, the unserialization
560 : * will fail since everything will be off by sizeof(magic_t).
561 : *
562 : * \param[in] buffer The buffer to unserialize.
563 : * \param[in] callback The callback manager used to store the callbacks to
564 : * call on each hunk.
565 : * \param[in] includes_magic Whether \p buffer includes the a magic code
566 : * at the start or not. The top buffer is expected to include a magic
567 : * code. Sub-buffers should not include the magic code.
568 : *
569 : * \return true if the unserialization succeeded, false otherwise.
570 : */
571 : template<typename S>
572 22 : class deserializer
573 : {
574 : public:
575 : typedef std::function<bool(deserializer<S> &, field_t const &)> process_hunk_t;
576 :
577 24 : deserializer(S & input)
578 26 : : f_input(input)
579 : {
580 24 : magic_t magic = {};
581 24 : f_input.read(reinterpret_cast<typename S::char_type *>(&magic), sizeof(magic));
582 24 : if(!f_input || f_input.gcount() != sizeof(magic))
583 : {
584 1 : throw brs_magic_missing("magic missing from the start of the buffer.");
585 : }
586 :
587 : // once we have multiple versions, this is where we'll start splitting
588 : // hairs to make it all work; for now, we have one so it's easy
589 : //
590 23 : if(magic != BRS_MAGIC)
591 : {
592 1 : throw brs_magic_unsupported("magic unsupported.");
593 : }
594 22 : }
595 :
596 :
597 198 : bool deserialize(process_hunk_t & callback)
598 : {
599 169 : for(;;)
600 : {
601 198 : hunk_sizes_t hunk_sizes = {};
602 198 : f_input.read(reinterpret_cast<typename S::char_type *>(&hunk_sizes), sizeof(hunk_sizes));
603 198 : if(!f_input || f_input.gcount() != sizeof(hunk_sizes))
604 : {
605 14 : return f_input.eof() && f_input.gcount() == 0;
606 : }
607 :
608 184 : f_field.reset();
609 184 : f_field.f_size = hunk_sizes.f_hunk;
610 :
611 184 : switch(hunk_sizes.f_type)
612 : {
613 43 : case TYPE_FIELD:
614 43 : if(hunk_sizes.f_name == 0
615 7 : && hunk_sizes.f_hunk == 0)
616 : {
617 : // we found an "end sub-field" entry
618 : //
619 7 : return true;
620 : }
621 36 : break;
622 :
623 62 : case TYPE_ARRAY:
624 : {
625 62 : std::uint16_t idx(0);
626 62 : f_input.read(reinterpret_cast<typename S::char_type *>(&idx), sizeof(idx));
627 62 : if(!f_input || f_input.gcount() != sizeof(idx))
628 : {
629 1 : return false;
630 : }
631 61 : f_field.f_index = idx;
632 : }
633 61 : break;
634 :
635 78 : case TYPE_MAP:
636 : {
637 78 : std::uint8_t len(0);
638 78 : f_input.read(reinterpret_cast<typename S::char_type *>(&len), sizeof(len));
639 78 : if(!f_input || f_input.gcount() != sizeof(len))
640 : {
641 1 : return false;
642 : }
643 77 : if(len == 0)
644 : {
645 1 : throw brs_map_name_cannot_be_empty("the length of a map's field name cannot be zero.");
646 : }
647 76 : f_field.f_sub_name.resize(len);
648 76 : f_input.read(reinterpret_cast<typename S::char_type *>(f_field.f_sub_name.data()), len);
649 76 : if(!f_input || f_input.gcount() != len)
650 : {
651 1 : return false;
652 78 : }
653 : }
654 75 : break;
655 :
656 1 : default:
657 1 : throw brs_unknown_type("read a field with an unknown type.");
658 :
659 : }
660 :
661 172 : f_field.f_name.resize(hunk_sizes.f_name);
662 172 : f_input.read(reinterpret_cast<typename S::char_type *>(f_field.f_name.data()), hunk_sizes.f_name);
663 172 : if(!f_input || f_input.gcount() != hunk_sizes.f_name)
664 : {
665 3 : return false;
666 : }
667 :
668 169 : callback(*this, f_field);
669 : }
670 : }
671 :
672 : template<typename T>
673 83 : bool read_data(T & data)
674 : {
675 83 : if(f_field.f_size != sizeof(data))
676 : {
677 : throw brs_logic_error(
678 : "hunk size is "
679 : + std::to_string(f_field.f_size)
680 : + ", but you are trying to read "
681 : + std::to_string(sizeof(data))
682 9 : + '.');
683 : }
684 :
685 74 : f_input.read(reinterpret_cast<typename S::char_type *>(&data), sizeof(data));
686 74 : return verify_size(sizeof(data));
687 : }
688 :
689 87 : bool read_data(std::string & data)
690 : {
691 87 : data.resize(f_field.f_size);
692 87 : f_input.read(reinterpret_cast<typename S::char_type *>(data.data()), f_field.f_size);
693 87 : return verify_size(f_field.f_size);
694 : }
695 :
696 : template<typename T>
697 2 : bool read_data(std::vector<T> & data)
698 : {
699 2 : if(f_field.f_size % sizeof(T) != 0)
700 : {
701 : throw brs_logic_error(
702 : "hunk size ("
703 : + std::to_string(f_field.f_size)
704 : + ") is not a multiple of the vector item size: "
705 : + std::to_string(sizeof(T))
706 1 : + '.');
707 : }
708 :
709 1 : data.resize(f_field.f_size / sizeof(T));
710 1 : f_input.read(reinterpret_cast<typename S::char_type *>(data.data()), f_field.f_size);
711 1 : return verify_size(f_field.f_size);
712 : }
713 :
714 : private:
715 162 : bool verify_size(std::size_t expected_size)
716 : {
717 162 : return f_input && static_cast<ssize_t>(expected_size) == f_input.gcount();
718 : }
719 :
720 : S & f_input;
721 : field_t f_field = field_t();
722 : };
723 :
724 :
725 :
726 : } // namespace snapdev
727 : // vim: ts=4 sw=4 et
|