Crunch
A Message Definition Language for Getting Things Right
Loading...
Searching...
No Matches
crunch_detail.hpp
1#pragma once
2
3#include <crunch/core/crunch_endian.hpp>
4#include <crunch/core/crunch_header.hpp>
5#include <crunch/integrity/crunch_integrity.hpp>
6#include <crunch/messages/crunch_messages.hpp>
7#include <crunch/serdes/crunch_serdes.hpp>
8#include <crunch/serdes/crunch_static_layout.hpp>
9#include <cstddef>
10#include <cstring>
11#include <span>
12#include <variant>
16namespace Crunch::detail {
17
30template <typename Message, typename Integrity, typename Serdes, std::size_t N>
31struct Buffer {
32 using MessageType = Message;
33 using IntegrityType = Integrity;
34 using SerdesType = Serdes;
35
36 // cppcheck-suppress unusedStructMember
37 static constexpr std::size_t Size = N;
38
39 std::array<std::byte, N> data;
40 std::size_t used_bytes{0};
41
42 [[nodiscard]] constexpr auto span() noexcept {
43 return std::span<std::byte, N>{data};
44 }
45 [[nodiscard]] constexpr auto span() const noexcept {
46 return std::span<const std::byte, N>{data};
47 }
48
49 [[nodiscard]] constexpr auto serialized_message_span() const noexcept {
50 return std::span<const std::byte>{data.data(), used_bytes};
51 }
52};
53
54namespace detail {
55template <typename T>
56struct is_buffer : std::false_type {};
57
58template <typename Message, typename Integrity, typename Serdes, std::size_t N>
59struct is_buffer<Buffer<Message, Integrity, Serdes, N>> : std::true_type {};
60} // namespace detail
61
62template <typename T>
63concept IsBuffer = detail::is_buffer<T>::value;
64
74template <messages::CrunchMessage Message, typename Integrity, typename Serdes>
76[[nodiscard]] consteval auto GetBufferSize() noexcept -> std::size_t {
77 return Serdes::template Size<Message>() + Integrity::size();
78}
79
91template <typename Message>
93[[nodiscard]] constexpr auto Validate(const Message& message) noexcept
94 -> std::optional<Error>;
95
104template <typename T>
105constexpr std::optional<Error> ValidateField(const T& field) {
106 if constexpr (requires { field.validate_presence(); }) {
107 if (auto err = field.validate_presence(); err) {
108 return err;
109 }
110 }
111
113 if (const auto* ptr = field.get()) {
114 // Recurse into the submessage content
115 if (auto err = Validate(*ptr); err) {
116 return err;
117 }
118 }
119 } else {
120 // Scalar, String, Array
121 if (auto err = field.Validate(); err) {
122 return err;
123 }
124 }
125 return std::nullopt;
126}
127
136template <typename Message>
138[[nodiscard]] constexpr auto Validate(const Message& message) noexcept
139 -> std::optional<Error> {
140 std::optional<Error> err;
141 std::apply(
142 [&](const auto&... fields) {
143 ([&] {
144 if (err.has_value()) {
145 return false;
146 }
147 err = ValidateField(fields);
148 return !err.has_value();
149 }() &&
150 ...);
151 },
152 message.get_fields());
153
154 if (err.has_value()) {
155 return err;
156 }
157
158 return message.Validate();
159}
160
171template <typename Integrity, typename Serdes, messages::CrunchMessage Message,
172 std::size_t N>
174
175[[nodiscard]] std::size_t SerializeWithoutValidation(
176 std::array<std::byte, N>& buffer, const Message& message) noexcept {
177 constexpr std::size_t ChecksumSize = Integrity::size();
178 constexpr std::size_t PayloadSize = N - ChecksumSize;
179
180 std::span<std::byte, PayloadSize> payload_span(buffer.data(), PayloadSize);
181
182 // Write Header
183 WriteHeader<Message, Serdes>(payload_span);
184
185 // Serialize Payload (Serdes policy executes logic on full span)
186 const std::size_t bytes_written = Serdes::Serialize(message, payload_span);
187
188 // Calculate and Append Checksum
189 if constexpr (ChecksumSize > 0) {
190 // Calculate checksum over the header and payload (bytes_written
191 // includes both).
192 std::span<std::byte> used_payload_span =
193 payload_span.subspan(0, bytes_written);
194 auto checksum = Integrity::calculate(used_payload_span);
195
196 std::span<std::byte, ChecksumSize> checksum_span(
197 buffer.data() + bytes_written, ChecksumSize);
198 std::copy(checksum.begin(), checksum.end(), checksum_span.begin());
199 }
200 return bytes_written + ChecksumSize;
201}
202
219template <typename Integrity, typename Serdes, messages::CrunchMessage Message,
220 std::size_t N>
222[[nodiscard]] auto Serialize(std::array<std::byte, N>& buffer,
223 const Message& message) noexcept
224 -> std::expected<std::size_t, Error> {
225 // Validate Message
226 if (auto err = Validate(message); err.has_value()) {
227 return std::unexpected(*err);
228 }
229 return SerializeWithoutValidation<Integrity, Serdes>(buffer, message);
230}
231
247template <typename Integrity, typename Serdes, typename Message>
250[[nodiscard]] auto Deserialize(std::span<const std::byte> buffer,
251 Message& message) noexcept
252 -> std::optional<Error> {
253 constexpr std::size_t ChecksumSize = Integrity::size();
254
255 if (buffer.size() < ChecksumSize) {
256 return Error::deserialization("buffer too small for checksum");
257 }
258 const std::size_t PayloadSize = buffer.size() - ChecksumSize;
259
260 std::span<const std::byte> payload_span = buffer.subspan(0, PayloadSize);
261
262 // Execute Integrity Check per policy
263 if constexpr (ChecksumSize > 0) {
264 const auto expected_checksum = Integrity::calculate(payload_span);
265 std::span<const std::byte, ChecksumSize> actual_checksum_span(
266 buffer.data() + PayloadSize, ChecksumSize);
267
268 // Simple comparison
269 const bool match =
270 std::equal(expected_checksum.begin(), expected_checksum.end(),
271 actual_checksum_span.begin());
272 if (!match) {
273 return Error::integrity();
274 }
275 }
276
277 // Validate Header (Version, Format, MessageId)
278 auto header_result = ValidateHeader<Message, Serdes>(payload_span);
279 if (!header_result) {
280 return header_result.error();
281 }
282
283 // Deserialize (Serdes policy executes its logic on the full span)
284 if (const auto err = Serdes::Deserialize(payload_span, message);
285 err.has_value()) {
286 return err;
287 }
288
289 // Validate deserialized message
290 if (auto err = Validate(message); err.has_value()) {
291 return err;
292 }
293
294 return std::nullopt;
295}
296
300template <MessageId Id, typename... Messages>
301consteval std::size_t CountMessageId() {
302 return ((Messages::message_id == Id ? 1 : 0) + ...);
303}
304
308template <typename... Messages>
309consteval bool HasDuplicateMessageIds() {
310 return ((CountMessageId<Messages::message_id, Messages...>() > 1) || ...);
311}
312
313template <typename... Messages>
314concept UniqueMessageIds = !HasDuplicateMessageIds<Messages...>();
315
325template <typename Serdes, typename Integrity,
326 messages::CrunchMessage... Messages>
327 requires UniqueMessageIds<Messages...>
328class Decoder {
329 public:
330 using VariantType = std::variant<Messages...>;
331
332 [[nodiscard]] constexpr std::optional<Error> Decode(
333 std::span<const std::byte> buffer, VariantType& out_message) {
334 // Validate Header
335 const auto header = GetHeader(buffer);
336 if (!header) {
337 return header.error();
338 }
339
340 // Find message which matches header's message_id and deserialize
341 std::optional<Error> result = Error::invalid_message_id();
342 ([&]() -> bool {
343 if (Messages::message_id == header->message_id) {
344 Messages msg{};
345 auto err = Deserialize<Integrity, Serdes>(buffer, msg);
346 if (err) {
347 result = err;
348 } else {
349 out_message = std::move(msg);
350 result = std::nullopt;
351 }
352 return true;
353 }
354 return false;
355 }() || ...);
356
357 return result;
358 }
359
360 private:
361 std::variant<
362 // Variant holding all possible buffer configurations for the given
363 // message types, integrity, and serdes
364 Buffer<Messages, Integrity, Serdes,
365 GetBufferSize<Messages, Integrity, Serdes>()>...>
366 buffer;
367};
368
369} // namespace Crunch::detail
Decoder class for deserializing one of N possible message types from a buffer.
Definition: crunch_detail.hpp:328
Concept defining the interface for message integrity policies.
Definition: crunch_integrity.hpp:24
Concept defining a Serialization/Deserialization policy.
Definition: crunch_serdes.hpp:36
Definition: crunch_detail.hpp:63
Definition: crunch_detail.hpp:314
Concept ensuring a type is a fully valid CrunchMessage.
Definition: crunch_messages.hpp:119
Concept to detect if a type quacks like a Crunch Message.
Definition: crunch_field.hpp:226
Internal implementation details for Crunch's public API.
Definition: crunch_detail.hpp:16
auto Serialize(std::array< std::byte, N > &buffer, const Message &message) noexcept -> std::expected< std::size_t, Error >
implementation of Serialize.
Definition: crunch_detail.hpp:222
consteval bool HasDuplicateMessageIds()
Returns true if any message ID appears more than once.
Definition: crunch_detail.hpp:309
consteval std::size_t CountMessageId()
Counts how many messages have the given message ID.
Definition: crunch_detail.hpp:301
constexpr auto Validate(const Message &message) noexcept -> std::optional< Error >
Forward declaration of Validate to enable recursion in ValidateField.
Definition: crunch_detail.hpp:138
auto Deserialize(std::span< const std::byte > buffer, Message &message) noexcept -> std::optional< Error >
implementation of Deserialize.
Definition: crunch_detail.hpp:250
std::size_t SerializeWithoutValidation(std::array< std::byte, N > &buffer, const Message &message) noexcept
Serializes the message without any validation checks.
Definition: crunch_detail.hpp:175
constexpr std::optional< Error > ValidateField(const T &field)
Helper to validate a single field.
Definition: crunch_detail.hpp:105
consteval auto GetBufferSize() noexcept -> std::size_t
Compile-time calculation of the size of a buffer for a given message, integrity, and serdes combinati...
Definition: crunch_detail.hpp:76
int32_t MessageId
Unique identifier for a message type.
Definition: crunch_types.hpp:25
constexpr std::expected< CrunchHeader, Error > GetHeader(std::span< const std::byte > input) noexcept
Parses and returns a copy of the header from the input buffer.
Definition: crunch_header.hpp:31
static constexpr Error deserialization(std::string_view msg="deserialization error") noexcept
Creates an error representing a deserialization failure.
Definition: crunch_types.hpp:104
static constexpr Error invalid_message_id() noexcept
Creates an error representing an invalid message ID.
Definition: crunch_types.hpp:112
static constexpr Error integrity() noexcept
Creates an error representing an integrity check failure.
Definition: crunch_types.hpp:84
A lightweight wrapper around a std::array for serializing/deserializing messages.
Definition: crunch_detail.hpp:31
Definition: crunch_detail.hpp:56