Loading...
Searching...
No Matches
binary_stream.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Length-prefixed little-endian primitives for Q3's own binary records.
5 *
6 * Every read is bounded before it allocates: a count is checked against the
7 * bytes actually left in the file, so a corrupt or foreign length field is
8 * diagnosed as a corrupt file rather than becoming a multi-gigabyte allocation
9 * that surfaces as `std::bad_alloc` or libc++'s bare "vector".
10 *
11 * `io/mean_field_record/checkpoint.hpp` carries its own copy of these, which
12 * has already diverged: it returns zero-filled values on a truncated read
13 * where these throw. Unifying the two is worth doing and is deliberately not
14 * done here, since it changes the behaviour of a record this branch does not
15 * otherwise touch.
16 */
17
18#include <cstddef>
19#include <cstdint>
20#include <istream>
21#include <ostream>
22#include <stdexcept>
23#include <string>
24#include <vector>
25
27
29
30template <typename ValueType> void write_scalar(std::ostream& stream, const ValueType& value) {
31 stream.write(reinterpret_cast<const char*>(&value), sizeof(ValueType));
32}
33
34template <typename ValueType> ValueType read_scalar(std::istream& stream) {
35 ValueType value{};
36 stream.read(reinterpret_cast<char*>(&value), sizeof(ValueType));
37 if (!stream) {
38 throw std::runtime_error("interpolation record: the file ended early; it is truncated.");
39 }
40 return value;
41}
42
43/**
44 * A count read from the file, times an element size, must fit in what is
45 * left of the file. Without this a corrupt or foreign length field becomes a
46 * multi-gigabyte allocation (std::bad_alloc, or libc++'s bare "vector"
47 * length_error) instead of the "corrupt" diagnosis every other check gives.
48 */
49inline void require_bytes_available(std::istream& stream, std::uint64_t bytes) {
50 const std::streampos position = stream.tellg();
51 stream.seekg(0, std::ios::end);
52 const std::streampos end = stream.tellg();
53 stream.seekg(position);
54 if (position < 0 || end < position || static_cast<std::uint64_t>(end - position) < bytes) {
55 throw std::runtime_error("interpolation record: a length field exceeds the bytes left in "
56 "the file; it is corrupt or truncated.");
57 }
58}
59
60inline void write_string(std::ostream& stream, const std::string& text) {
61 write_scalar<int64_t>(stream, static_cast<int64_t>(text.size()));
62 stream.write(text.data(), static_cast<std::streamsize>(text.size()));
63}
64
65inline std::string read_string(std::istream& stream) {
66 const int64_t length = read_scalar<int64_t>(stream);
67 if (length < 0 || length > 1024) {
68 throw std::runtime_error("interpolation record: implausible string length; the file is "
69 "corrupt.");
70 }
71 std::string text(static_cast<std::size_t>(length), '\0');
72 stream.read(text.data(), length);
73 if (!stream) {
74 throw std::runtime_error("interpolation record: the file ended early; it is truncated.");
75 }
76 return text;
77}
78
79inline void write_vector3d(std::ostream& stream, const Vector3D& vector) {
80 write_scalar<double>(stream, vector.x);
81 write_scalar<double>(stream, vector.y);
82 write_scalar<double>(stream, vector.z);
83}
84
85inline Vector3D read_vector3d(std::istream& stream) {
86 Vector3D vector;
87 vector.x = read_scalar<double>(stream);
88 vector.y = read_scalar<double>(stream);
89 vector.z = read_scalar<double>(stream);
90 return vector;
91}
92
93inline void write_matrix(std::ostream& stream, const Matrix3x3& matrix) {
94 for (int row = 0; row < 3; row++) {
95 write_vector3d(stream, matrix[row]);
96 }
97}
98
99inline Matrix3x3 read_matrix(std::istream& stream) {
100 Matrix3x3 matrix;
101 for (int row = 0; row < 3; row++) {
102 matrix[row] = read_vector3d(stream);
103 }
104 return matrix;
105}
106
107/// Length-prefixed array of trivially copyable values.
108template <typename ValueType>
109void write_array(std::ostream& stream, const std::vector<ValueType>& values) {
110 write_scalar<int64_t>(stream, static_cast<int64_t>(values.size()));
111 if (!values.empty()) {
112 stream.write(reinterpret_cast<const char*>(values.data()),
113 static_cast<std::streamsize>(values.size() * sizeof(ValueType)));
114 }
115}
116
117template <typename ValueType> std::vector<ValueType> read_array(std::istream& stream) {
118 const int64_t count = read_scalar<int64_t>(stream);
119 if (count < 0) {
120 throw std::runtime_error("interpolation record: negative array length; the file is "
121 "corrupt.");
122 }
123 require_bytes_available(stream, static_cast<std::uint64_t>(count) * sizeof(ValueType));
124 std::vector<ValueType> values(static_cast<std::size_t>(count));
125 if (count > 0) {
126 stream.read(reinterpret_cast<char*>(values.data()),
127 static_cast<std::streamsize>(values.size() * sizeof(ValueType)));
128 if (!stream) {
129 throw std::runtime_error(
130 "interpolation record: the file ended early; it is truncated.");
131 }
132 }
133 return values;
134}
135
136template <typename ValueType>
137void write_nested_arrays(std::ostream& stream, const std::vector<std::vector<ValueType>>& rows) {
138 write_scalar<int64_t>(stream, static_cast<int64_t>(rows.size()));
139 for (const std::vector<ValueType>& row : rows) {
140 write_array(stream, row);
141 }
142}
143
144template <typename ValueType>
145std::vector<std::vector<ValueType>> read_nested_arrays(std::istream& stream) {
146 const int64_t count = read_scalar<int64_t>(stream);
147 if (count < 0) {
148 throw std::runtime_error("interpolation record: negative array count; the file is "
149 "corrupt.");
150 }
151 require_bytes_available(stream, static_cast<std::uint64_t>(count) * sizeof(int64_t));
152 std::vector<std::vector<ValueType>> rows(static_cast<std::size_t>(count));
153 for (std::vector<ValueType>& row : rows) {
154 row = read_array<ValueType>(stream);
155 }
156 return rows;
157}
158
159} // namespace q3::internal::interpolation_stream
void write_nested_arrays(std::ostream &stream, const std::vector< std::vector< ValueType > > &rows)
Vector3D read_vector3d(std::istream &stream)
std::vector< ValueType > read_array(std::istream &stream)
void write_matrix(std::ostream &stream, const Matrix3x3 &matrix)
std::string read_string(std::istream &stream)
void write_scalar(std::ostream &stream, const ValueType &value)
std::vector< std::vector< ValueType > > read_nested_arrays(std::istream &stream)
Matrix3x3 read_matrix(std::istream &stream)
ValueType read_scalar(std::istream &stream)
void require_bytes_available(std::istream &stream, std::uint64_t bytes)
void write_string(std::ostream &stream, const std::string &text)
void write_vector3d(std::ostream &stream, const Vector3D &vector)
void write_array(std::ostream &stream, const std::vector< ValueType > &values)
Length-prefixed array of trivially copyable values.
3×3 matrix stored row-major: row i = mat[i]