CARLA
Loading...
Searching...
No Matches
frameDataCodec.h
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma de Barcelona (UAB).
2// This work is licensed under the terms of the MIT license.
3// For a copy, see <https://opensource.org/licenses/MIT>.
4
5#pragma once
6
7#include <cstddef>
8#include <cstdint>
9#include <cstring>
10#include <exception>
11#include <istream>
12#include <optional>
13#include <ostream>
14#include <streambuf>
15#include <string>
16#include <type_traits>
17#include <utility>
18
19namespace carla {
20namespace multigpu {
21
22 /// Frame data (the SEND_FRAME payload) is a sequence of packets
23 /// [uint8 id][uint32 payload size][payload] in host byte order. Every server
24 /// runs the same build, so nothing is byte swapped.
25 constexpr std::size_t kFrameDataHeaderSize = 5u;
26
27 /// Id of the frame counter packet, the last packet of every complete frame.
28 constexpr uint8_t kFrameCounterPacketId = 18u;
29
30 /// Version of the whole Multi-GPU wire format, carried in every frame. It is
31 /// only reported: readers skip what they do not know instead of rejecting it.
32 constexpr uint8_t kMultiGpuProtocolVersion = 1u;
33
34 enum class FrameDataStatus : uint8_t {
35 Ok,
41 /// Packets that contradict each other (set by the reader's owner, e.g. a
42 /// FrameTime for another frame than the FrameCounter).
44 };
45
46 inline const char *FrameDataStatusName(FrameDataStatus status) {
47 switch (status) {
48 case FrameDataStatus::Ok: return "complete";
49 case FrameDataStatus::TruncatedHeader: return "truncated packet header";
50 case FrameDataStatus::TruncatedPayload: return "truncated packet payload";
51 case FrameDataStatus::ShortPacket: return "packet shorter than its fields";
52 case FrameDataStatus::ReaderError: return "packet reader failed";
53 case FrameDataStatus::MissingFrameCounter: return "no frame counter";
54 case FrameDataStatus::Inconsistent: return "packets that contradict each other";
55 }
56 return "unknown status";
57 }
58
61 /// Value of the frame counter packet, if it was read.
62 std::optional<uint64_t> frame;
63 /// Id, offset and declared size of the last packet looked at.
64 uint8_t packet_id = 0u;
65 std::size_t offset = 0u;
66 uint32_t packet_size = 0u;
67 /// Packets whose id this build does not know.
68 uint32_t skipped = 0u;
69 uint8_t first_skipped_id = 0u;
70 /// Known packets with trailing bytes this build does not read (written by
71 /// a newer build).
72 uint32_t grown = 0u;
73
74 /// Only a complete frame may be played.
75 bool IsComplete() const {
77 }
78 };
79
80namespace detail {
81
82 /// Read-only stream buffer over one packet's payload. It cannot seek, so a
83 /// reader can neither leave its packet nor make the packet loop lose its place.
84 class PayloadBuffer : public std::streambuf {
85 public:
86
87 PayloadBuffer(const uint8_t *data, std::size_t size) {
88 char *begin = const_cast<char *>(reinterpret_cast<const char *>(data));
89 setg(begin, begin, begin + size);
90 }
91
92 std::size_t Remaining() const {
93 return static_cast<std::size_t>(egptr() - gptr());
94 }
95 };
96
97 template <typename T>
98 void WritePod(std::ostream &out, const T &value) {
99 static_assert(std::is_trivially_copyable_v<T>, "only plain values go on the wire");
100 out.write(reinterpret_cast<const char *>(&value), sizeof(T));
101 }
102
103 template <typename T>
104 bool ReadPod(std::istream &in, T &value) {
105 static_assert(std::is_trivially_copyable_v<T>, "only plain values go on the wire");
106 in.read(reinterpret_cast<char *>(&value), sizeof(T));
107 return !in.fail();
108 }
109
110} // namespace detail
111
112 inline void WriteFrameDataHeader(std::ostream &out, uint8_t id, uint32_t payload_size) {
113 detail::WritePod(out, id);
114 detail::WritePod(out, payload_size);
115 }
116
117 /// Writes a packet whose payload was built beforehand.
118 inline void WriteFrameDataPacket(std::ostream &out, uint8_t id, const std::string &payload) {
119 WriteFrameDataHeader(out, id, static_cast<uint32_t>(payload.size()));
120 out.write(payload.data(), static_cast<std::streamsize>(payload.size()));
121 }
122
123 /// Walks the packets of one frame. @a read_packet(id, payload, size) reads a
124 /// known packet from @a payload, a stream that ends where the packet ends,
125 /// and returns true; for an id it does not know it returns false without
126 /// reading. The loop always moves on by the declared size, so unknown
127 /// packets, packets that grew in a newer build and empty packets are skipped,
128 /// and it never reads outside [data, data + size).
129 template <typename ReadPacket>
130 FrameDataReadResult ReadFrameData(const uint8_t *data, std::size_t size, ReadPacket &&read_packet) {
131 FrameDataReadResult result;
132 std::size_t offset = 0u;
133 while (offset < size) {
134 result.offset = offset;
135 if (size - offset < kFrameDataHeaderSize) {
137 return result;
138 }
139 uint8_t id = 0u;
140 uint32_t payload_size = 0u;
141 std::memcpy(&id, data + offset, sizeof(id));
142 std::memcpy(&payload_size, data + offset + sizeof(id), sizeof(payload_size));
143 result.packet_id = id;
144 result.packet_size = payload_size;
145 const std::size_t payload = offset + kFrameDataHeaderSize;
146 if (payload_size > size - payload) {
148 return result;
149 }
150
151 detail::PayloadBuffer buffer(data + payload, payload_size);
152 std::istream in(&buffer);
153 bool known = true;
154 if (id == kFrameCounterPacketId) {
155 uint64_t frame = 0u;
156 if (detail::ReadPod(in, frame)) {
157 result.frame = frame;
158 }
159 } else {
160#ifndef LIBCARLA_NO_EXCEPTIONS
161 try {
162#endif // LIBCARLA_NO_EXCEPTIONS
163 known = read_packet(id, in, payload_size);
164#ifndef LIBCARLA_NO_EXCEPTIONS
165 } catch (const std::exception &) {
167 return result;
168 }
169#endif // LIBCARLA_NO_EXCEPTIONS
170 }
171
172 if (!known) {
173 if (result.skipped++ == 0u) {
174 result.first_skipped_id = id;
175 }
176 } else if (in.fail()) {
178 return result;
179 } else if (buffer.Remaining() > 0u) {
180 ++result.grown;
181 }
182 offset = payload + payload_size;
183 }
184 if (!result.frame) {
186 }
187 return result;
188 }
189
190 /// Reads the fields of one packet (or one record inside it) without ever
191 /// consuming more than its declared size. A read past the end fails, and a
192 /// failed reader fails its stream, so the packet loop reports a short packet.
193 ///
194 /// Payload layouts of the Multi-GPU packets:
195 /// - fixed-size records: [u32 count][u16 record_size]{record}
196 /// - variable-size records: [u32 count]{[u32 record_size][record]}
197 /// - optional fields (TLV): {[u8 kind][u32 size][value]}
198 /// A record or field only ever grows at its end; readers read the fields
199 /// they know and skip the rest.
201 public:
202
203 BoundedReader(std::istream &in, uint32_t size)
204 : _in(in),
205 _remaining(size) {}
206
207 template <typename T>
208 bool Read(T &value) {
209 if (_failed || (_remaining < sizeof(T)) || !detail::ReadPod(_in, value)) {
210 return Fail();
211 }
212 _remaining -= static_cast<uint32_t>(sizeof(T));
213 return true;
214 }
215
216 bool ReadBytes(void *out, uint32_t bytes) {
217 if (_failed || (_remaining < bytes)) {
218 return Fail();
219 }
220 _in.read(static_cast<char *>(out), static_cast<std::streamsize>(bytes));
221 if (_in.fail()) {
222 return Fail();
223 }
224 _remaining -= bytes;
225 return true;
226 }
227
228 bool Skip(uint32_t bytes) {
229 if (_failed || (_remaining < bytes)) {
230 return Fail();
231 }
232 _in.ignore(static_cast<std::streamsize>(bytes));
233 if (static_cast<uint32_t>(_in.gcount()) != bytes) {
234 return Fail();
235 }
236 _remaining -= bytes;
237 return true;
238 }
239
240 /// Runs @a read(record) on the next @a bytes as a reader of their own, then
241 /// skips what it left unread (a record that grew in a newer build).
242 template <typename F>
243 bool ReadRecord(uint32_t bytes, F &&read) {
244 if (_failed || (_remaining < bytes)) {
245 return Fail();
246 }
247 BoundedReader record(_in, bytes);
248 if (!read(record) || record.Failed() || !record.Skip(record.Remaining())) {
249 return Fail();
250 }
251 _remaining -= bytes;
252 return true;
253 }
254
255 /// Header of a list of fixed-size records, checked against the bytes left.
256 bool ReadFixedListHeader(uint16_t min_record_size, uint32_t &count, uint16_t &record_size) {
257 count = 0u;
258 record_size = 0u;
259 if (!Read(count) || !Read(record_size)) {
260 return false;
261 }
262 if ((record_size < min_record_size) ||
263 (static_cast<uint64_t>(count) * record_size > _remaining)) {
264 return Fail();
265 }
266 return true;
267 }
268
269 /// Count of a list of variable-size records, checked against the bytes left.
270 bool ReadVariableListCount(uint32_t &count) {
271 count = 0u;
272 if (!Read(count)) {
273 return false;
274 }
275 if (count > _remaining / sizeof(uint32_t)) {
276 return Fail();
277 }
278 return true;
279 }
280
281 /// Reads one [u32 record_size][record] entry of a variable-size list.
282 template <typename F>
283 bool ReadSizedRecord(F &&read) {
284 uint32_t bytes = 0u;
285 return Read(bytes) && ReadRecord(bytes, std::forward<F>(read));
286 }
287
288 /// Reads TLV fields until the reader is exhausted. @a read(kind, value)
289 /// reads a known kind and returns true, or returns true without reading
290 /// to skip an unknown one; false aborts the packet.
291 template <typename F>
292 bool ReadFields(F &&read) {
293 while (_remaining > 0u) {
294 uint8_t kind = 0u;
295 uint32_t bytes = 0u;
296 if (!Read(kind) || !Read(bytes)) {
297 return false;
298 }
299 if (!ReadRecord(bytes, [&](BoundedReader &value) { return read(kind, value); })) {
300 return false;
301 }
302 }
303 return !_failed;
304 }
305
306 uint32_t Remaining() const {
307 return _remaining;
308 }
309
310 bool Failed() const {
311 return _failed;
312 }
313
314 private:
315
316 bool Fail() {
317 _failed = true;
318 _in.setstate(std::ios_base::failbit);
319 return false;
320 }
321
322 std::istream &_in;
323
324 uint32_t _remaining;
325
326 bool _failed = false;
327 };
328
329 /// Header of a list of fixed-size records.
330 inline void WriteFixedListHeader(std::ostream &out, uint32_t count, uint16_t record_size) {
331 detail::WritePod(out, count);
332 detail::WritePod(out, record_size);
333 }
334
335 /// One [u32 size][bytes] entry: a variable-size record or, after a kind byte,
336 /// a TLV value.
337 inline void WriteSizedBytes(std::ostream &out, const std::string &bytes) {
338 detail::WritePod(out, static_cast<uint32_t>(bytes.size()));
339 out.write(bytes.data(), static_cast<std::streamsize>(bytes.size()));
340 }
341
342 inline void WriteField(std::ostream &out, uint8_t kind, const std::string &value) {
343 detail::WritePod(out, kind);
344 WriteSizedBytes(out, value);
345 }
346
347} // namespace multigpu
348} // namespace carla
Reads the fields of one packet (or one record inside it) without ever consuming more than its declare...
bool ReadFixedListHeader(uint16_t min_record_size, uint32_t &count, uint16_t &record_size)
Header of a list of fixed-size records, checked against the bytes left.
bool ReadRecord(uint32_t bytes, F &&read)
Runs read(record) on the next bytes as a reader of their own, then skips what it left unread (a recor...
bool ReadSizedRecord(F &&read)
Reads one [u32 record_size][record] entry of a variable-size list.
BoundedReader(std::istream &in, uint32_t size)
bool ReadVariableListCount(uint32_t &count)
Count of a list of variable-size records, checked against the bytes left.
bool ReadBytes(void *out, uint32_t bytes)
bool ReadFields(F &&read)
Reads TLV fields until the reader is exhausted.
Read-only stream buffer over one packet's payload.
PayloadBuffer(const uint8_t *data, std::size_t size)
bool ReadPod(std::istream &in, T &value)
void WritePod(std::ostream &out, const T &value)
void WriteFixedListHeader(std::ostream &out, uint32_t count, uint16_t record_size)
Header of a list of fixed-size records.
@ Inconsistent
Packets that contradict each other (set by the reader's owner, e.g.
FrameDataReadResult ReadFrameData(const uint8_t *data, std::size_t size, ReadPacket &&read_packet)
Walks the packets of one frame.
constexpr std::size_t kFrameDataHeaderSize
Frame data (the SEND_FRAME payload) is a sequence of packets [uint8 id][uint32 payload size][payload]...
const char * FrameDataStatusName(FrameDataStatus status)
void WriteField(std::ostream &out, uint8_t kind, const std::string &value)
void WriteFrameDataHeader(std::ostream &out, uint8_t id, uint32_t payload_size)
void WriteSizedBytes(std::ostream &out, const std::string &bytes)
One [u32 size][bytes] entry: a variable-size record or, after a kind byte, a TLV value.
constexpr uint8_t kFrameCounterPacketId
Id of the frame counter packet, the last packet of every complete frame.
constexpr uint8_t kMultiGpuProtocolVersion
Version of the whole Multi-GPU wire format, carried in every frame.
void WriteFrameDataPacket(std::ostream &out, uint8_t id, const std::string &payload)
Writes a packet whose payload was built beforehand.
This file contains definitions of common data structures used in traffic manager.
bool IsComplete() const
Only a complete frame may be played.
uint32_t skipped
Packets whose id this build does not know.
std::optional< uint64_t > frame
Value of the frame counter packet, if it was read.
uint32_t grown
Known packets with trailing bytes this build does not read (written by a newer build).
uint8_t packet_id
Id, offset and declared size of the last packet looked at.