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