Skip to content

Commit c30d077

Browse files
committed
fix: add zero-allocation BinaryArchive traversal API (#2)
1 parent b6d835b commit c30d077

1 file changed

Lines changed: 386 additions & 0 deletions

File tree

Lines changed: 386 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <cstring>
6+
#include <string_view>
7+
8+
#include "ESPressio_BinaryArchive.hpp"
9+
10+
namespace ESPressio::Serializable {
11+
12+
class BinaryArchiveVisitor {
13+
public:
14+
virtual ~BinaryArchiveVisitor() = default;
15+
16+
virtual bool OnObjectBegin(
17+
uint32_t,
18+
std::size_t
19+
) noexcept { return true; }
20+
21+
virtual bool OnObjectProperty(
22+
std::string_view,
23+
uint32_t,
24+
uint32_t,
25+
std::size_t
26+
) noexcept { return true; }
27+
28+
virtual bool OnObjectEnd(
29+
uint32_t,
30+
std::size_t
31+
) noexcept { return true; }
32+
33+
virtual bool OnArrayBegin(
34+
uint32_t,
35+
std::size_t
36+
) noexcept { return true; }
37+
38+
virtual bool OnArrayElement(
39+
uint32_t,
40+
uint32_t,
41+
std::size_t
42+
) noexcept { return true; }
43+
44+
virtual bool OnArrayEnd(
45+
uint32_t,
46+
std::size_t
47+
) noexcept { return true; }
48+
49+
virtual bool OnNull(
50+
std::size_t
51+
) noexcept { return true; }
52+
53+
virtual bool OnBoolean(
54+
bool,
55+
std::size_t
56+
) noexcept { return true; }
57+
58+
virtual bool OnSignedInteger(
59+
int64_t,
60+
std::size_t
61+
) noexcept { return true; }
62+
63+
virtual bool OnUnsignedInteger(
64+
uint64_t,
65+
std::size_t
66+
) noexcept { return true; }
67+
68+
virtual bool OnFloat32(
69+
float,
70+
std::size_t
71+
) noexcept { return true; }
72+
73+
virtual bool OnFloat64(
74+
double,
75+
std::size_t
76+
) noexcept { return true; }
77+
78+
virtual bool OnString(
79+
std::string_view,
80+
std::size_t
81+
) noexcept { return true; }
82+
};
83+
84+
85+
namespace BinaryArchiveTraversalDetail {
86+
87+
struct State {
88+
const BinaryArchiveDecodeLimits& Limits;
89+
std::size_t TotalNodes = 0;
90+
};
91+
92+
inline std::size_t Remaining(
93+
const uint8_t* cursor,
94+
const uint8_t* end
95+
) noexcept {
96+
if (
97+
cursor == nullptr ||
98+
end == nullptr ||
99+
cursor > end
100+
) {
101+
return 0;
102+
}
103+
104+
return static_cast<std::size_t>(end - cursor);
105+
}
106+
107+
inline bool ReadU16(
108+
const uint8_t*& cursor,
109+
const uint8_t* end,
110+
uint16_t& value
111+
) noexcept {
112+
if (Remaining(cursor, end) < 2) {
113+
return false;
114+
}
115+
116+
value =
117+
static_cast<uint16_t>(cursor[0]) |
118+
(static_cast<uint16_t>(cursor[1]) << 8u);
119+
120+
cursor += 2;
121+
return true;
122+
}
123+
124+
inline bool ReadU32(
125+
const uint8_t*& cursor,
126+
const uint8_t* end,
127+
uint32_t& value
128+
) noexcept {
129+
if (Remaining(cursor, end) < 4) {
130+
return false;
131+
}
132+
133+
value = 0;
134+
for (int shift = 0; shift < 32; shift += 8) {
135+
value |= static_cast<uint32_t>(*cursor++) << shift;
136+
}
137+
return true;
138+
}
139+
140+
inline bool ReadU64(
141+
const uint8_t*& cursor,
142+
const uint8_t* end,
143+
uint64_t& value
144+
) noexcept {
145+
if (Remaining(cursor, end) < 8) {
146+
return false;
147+
}
148+
149+
value = 0;
150+
for (int shift = 0; shift < 64; shift += 8) {
151+
value |= static_cast<uint64_t>(*cursor++) << shift;
152+
}
153+
return true;
154+
}
155+
156+
inline bool VisitNode(
157+
const uint8_t*& cursor,
158+
const uint8_t* end,
159+
BinaryArchiveVisitor& visitor,
160+
State& state,
161+
std::size_t depth
162+
) noexcept {
163+
if (
164+
cursor == nullptr ||
165+
end == nullptr ||
166+
cursor >= end ||
167+
depth > state.Limits.MaximumDepth ||
168+
state.TotalNodes >= state.Limits.MaximumTotalNodes
169+
) {
170+
return false;
171+
}
172+
173+
++state.TotalNodes;
174+
175+
const auto type =
176+
static_cast<SerializationNodeType>(*cursor++);
177+
178+
switch (type) {
179+
case SerializationNodeType::Null:
180+
return visitor.OnNull(depth);
181+
182+
case SerializationNodeType::Object: {
183+
uint16_t count = 0;
184+
if (
185+
!ReadU16(cursor, end, count) ||
186+
count > state.Limits.MaximumObjectMembers ||
187+
!visitor.OnObjectBegin(count, depth)
188+
) {
189+
return false;
190+
}
191+
192+
for (uint16_t index = 0; index < count; ++index) {
193+
uint16_t nameLength = 0;
194+
if (
195+
!ReadU16(cursor, end, nameLength) ||
196+
nameLength > state.Limits.MaximumNameLength ||
197+
Remaining(cursor, end) < nameLength
198+
) {
199+
return false;
200+
}
201+
202+
const std::string_view name(
203+
reinterpret_cast<const char*>(cursor),
204+
nameLength
205+
);
206+
cursor += nameLength;
207+
208+
if (
209+
!visitor.OnObjectProperty(
210+
name,
211+
index,
212+
count,
213+
depth
214+
) ||
215+
!VisitNode(
216+
cursor,
217+
end,
218+
visitor,
219+
state,
220+
depth + 1
221+
)
222+
) {
223+
return false;
224+
}
225+
}
226+
227+
return visitor.OnObjectEnd(count, depth);
228+
}
229+
230+
case SerializationNodeType::Array: {
231+
uint32_t count = 0;
232+
if (
233+
!ReadU32(cursor, end, count) ||
234+
count > state.Limits.MaximumArrayElements ||
235+
!visitor.OnArrayBegin(count, depth)
236+
) {
237+
return false;
238+
}
239+
240+
for (uint32_t index = 0; index < count; ++index) {
241+
if (
242+
!visitor.OnArrayElement(
243+
index,
244+
count,
245+
depth
246+
) ||
247+
!VisitNode(
248+
cursor,
249+
end,
250+
visitor,
251+
state,
252+
depth + 1
253+
)
254+
) {
255+
return false;
256+
}
257+
}
258+
259+
return visitor.OnArrayEnd(count, depth);
260+
}
261+
262+
case SerializationNodeType::Boolean:
263+
if (Remaining(cursor, end) < 1) {
264+
return false;
265+
}
266+
return visitor.OnBoolean(*cursor++ != 0, depth);
267+
268+
case SerializationNodeType::SignedInteger: {
269+
uint64_t raw = 0;
270+
if (!ReadU64(cursor, end, raw)) {
271+
return false;
272+
}
273+
int64_t value = 0;
274+
std::memcpy(&value, &raw, sizeof(value));
275+
return visitor.OnSignedInteger(value, depth);
276+
}
277+
278+
case SerializationNodeType::UnsignedInteger: {
279+
uint64_t value = 0;
280+
return
281+
ReadU64(cursor, end, value) &&
282+
visitor.OnUnsignedInteger(value, depth);
283+
}
284+
285+
case SerializationNodeType::Float32: {
286+
uint32_t raw = 0;
287+
if (!ReadU32(cursor, end, raw)) {
288+
return false;
289+
}
290+
float value = 0.0f;
291+
std::memcpy(&value, &raw, sizeof(value));
292+
return visitor.OnFloat32(value, depth);
293+
}
294+
295+
case SerializationNodeType::Float64: {
296+
uint64_t raw = 0;
297+
if (!ReadU64(cursor, end, raw)) {
298+
return false;
299+
}
300+
double value = 0.0;
301+
std::memcpy(&value, &raw, sizeof(value));
302+
return visitor.OnFloat64(value, depth);
303+
}
304+
305+
case SerializationNodeType::String: {
306+
uint32_t length = 0;
307+
if (
308+
!ReadU32(cursor, end, length) ||
309+
length > state.Limits.MaximumStringLength ||
310+
Remaining(cursor, end) < length
311+
) {
312+
return false;
313+
}
314+
315+
const std::string_view value(
316+
reinterpret_cast<const char*>(cursor),
317+
length
318+
);
319+
cursor += length;
320+
return visitor.OnString(value, depth);
321+
}
322+
}
323+
324+
return false;
325+
}
326+
327+
} // namespace BinaryArchiveTraversalDetail
328+
329+
330+
inline bool TraverseBinaryArchive(
331+
const uint8_t* data,
332+
std::size_t size,
333+
BinaryArchiveVisitor& visitor,
334+
const BinaryArchiveDecodeLimits& limits = {}
335+
) noexcept {
336+
if (
337+
data == nullptr ||
338+
size < 6 ||
339+
limits.MaximumTotalNodes == 0 ||
340+
data[0] != 'E' ||
341+
data[1] != 'S' ||
342+
data[2] != 'P' ||
343+
data[3] != 'B' ||
344+
data[4] != 2u
345+
) {
346+
return false;
347+
}
348+
349+
const uint8_t* cursor = data + 5;
350+
const uint8_t* end = data + size;
351+
BinaryArchiveTraversalDetail::State state{limits};
352+
353+
if (
354+
cursor >= end ||
355+
static_cast<SerializationNodeType>(*cursor) !=
356+
SerializationNodeType::Object ||
357+
!BinaryArchiveTraversalDetail::VisitNode(
358+
cursor,
359+
end,
360+
visitor,
361+
state,
362+
0
363+
)
364+
) {
365+
return false;
366+
}
367+
368+
return cursor == end;
369+
}
370+
371+
372+
inline bool ValidateBinaryArchive(
373+
const uint8_t* data,
374+
std::size_t size,
375+
const BinaryArchiveDecodeLimits& limits = {}
376+
) noexcept {
377+
BinaryArchiveVisitor visitor;
378+
return TraverseBinaryArchive(
379+
data,
380+
size,
381+
visitor,
382+
limits
383+
);
384+
}
385+
386+
} // namespace ESPressio::Serializable

0 commit comments

Comments
 (0)