Crunch
A Message Definition Language for Getting Things Right
Loading...
Searching...
No Matches
crunch_tlv_layout.hpp
1#pragma once
2
3#include <bit>
4#include <crunch/core/crunch_endian.hpp>
5#include <crunch/core/crunch_types.hpp>
6#include <crunch/fields/crunch_scalar.hpp>
7#include <crunch/fields/crunch_string.hpp>
8#include <crunch/messages/crunch_field.hpp>
9#include <crunch/messages/crunch_messages.hpp>
10#include <crunch/serdes/crunch_varint.hpp>
11#include <cstddef>
12#include <cstdint>
13#include <cstring>
14#include <expected>
15#include <numeric>
16#include <optional>
17#include <span>
18#include <tuple>
19#include <type_traits>
20#include <utility>
21
22namespace Crunch::serdes {
23
34namespace detail {
35
40template <typename T>
41struct ext {
42 using type = typename T::FieldType;
43};
44
45template <typename T>
46 requires Crunch::fields::is_scalar_v<T>
47struct ext<T> {
48 using type = T;
49};
50
51} // namespace detail
52
53struct TlvLayout {
57 enum class WireType : uint8_t {
58 Varint = 0,
59 LengthDelimited = 1,
60 };
61
62 static constexpr Crunch::Format GetFormat() { return Crunch::Format::TLV; }
63
67 static constexpr std::size_t WireTypeBits = 3;
68
73 static constexpr std::size_t MaxTagBits =
74 sizeof(FieldId) * 8 + WireTypeBits;
75
81 template <typename Message>
82 [[nodiscard]] static consteval std::size_t Size() noexcept {
83 return Crunch::StandardHeaderSize + sizeof(uint32_t) +
84 calculate_max_message_size<Message>();
85 }
86
94 template <typename Message>
95 [[nodiscard]] static constexpr std::size_t Serialize(
96 const Message& msg, std::span<std::byte> output) noexcept {
97 // Header (including MessageId) is written by top-level serializer
98 std::size_t offset = Crunch::StandardHeaderSize;
99
100 const std::size_t length_field_offset = offset;
101 offset += sizeof(uint32_t);
102
103 const std::size_t payload_start = offset;
104 offset = serialize_fields_helper(msg.get_fields(), output, offset);
105
106 const std::size_t payload_size = offset - payload_start;
107 const uint32_t le_len =
108 Crunch::LittleEndian(static_cast<uint32_t>(payload_size));
109 std::memcpy(output.data() + length_field_offset, &le_len,
110 sizeof(uint32_t));
111
112 return offset;
113 }
114
122 template <typename Message>
123 [[nodiscard]] static constexpr auto Deserialize(
124 std::span<const std::byte> input, Message& msg) noexcept
125 -> std::optional<Error> {
126 // Header (including MessageId) validated by top-level deserializer
127 std::size_t offset = Crunch::StandardHeaderSize;
128
129 if (offset + sizeof(uint32_t) > input.size()) {
130 return Error::deserialization("buffer too small for tlv length");
131 }
132
133 uint32_t le_len;
134 std::memcpy(&le_len, input.data() + offset, sizeof(uint32_t));
135 uint32_t payload_len = Crunch::LittleEndian(le_len);
136 offset += sizeof(uint32_t);
137
138 if (offset + payload_len > input.size()) {
139 return Error::deserialization("tlv length exceeds buffer");
140 }
141
142 return deserialize_message_payload(
143 input.subspan(0, offset + payload_len), msg, offset);
144 }
145
146 private:
155 [[nodiscard]] static constexpr std::size_t write_tag(
156 FieldId id, WireType wt, std::span<std::byte> output,
157 std::size_t offset) noexcept {
158 const uint32_t tag = (static_cast<uint32_t>(id) << WireTypeBits) |
159 static_cast<uint8_t>(wt);
160 return offset + Varint::encode(tag, output, offset);
161 }
162
166 template <typename Tuple, std::size_t... Is>
167 [[nodiscard]] static consteval std::size_t sum_fields_impl(
168 std::index_sequence<Is...>) noexcept {
169 return (calculate_max_field_size_type<
170 std::remove_cvref_t<std::tuple_element_t<Is, Tuple>>>() +
171 ... + 0);
172 }
173
174 template <typename Tuple>
175 [[nodiscard]] static consteval std::size_t sum_fields_helper() noexcept {
176 return sum_fields_impl<Tuple>(
177 std::make_index_sequence<
178 std::tuple_size_v<std::remove_cvref_t<Tuple>>>{});
179 }
180
186 template <typename Message>
187 [[nodiscard]] static consteval std::size_t
188 calculate_max_message_size() noexcept {
189 using FieldsTuple = decltype(std::declval<Message>().get_fields());
190 return sum_fields_helper<FieldsTuple>();
191 }
192
203 template <typename ElemT>
204 [[nodiscard]] static consteval std::size_t calculate_max_array_field_size(
205 std::size_t max_elements) noexcept {
206 constexpr std::size_t tag_size = Varint::max_varint_size(MaxTagBits);
207 constexpr std::size_t length_size = Varint::max_size;
208 constexpr std::size_t count_size = Varint::max_size;
209 constexpr std::size_t elem_size = calculate_max_value_size<ElemT>();
210
211 return tag_size + length_size + count_size + (max_elements * elem_size);
212 }
213
219 template <typename ScalarT>
220 [[nodiscard]] static consteval std::size_t
221 calculate_max_scalar_field_size() noexcept {
222 constexpr std::size_t tag_size = Varint::max_varint_size(MaxTagBits);
223 return tag_size + Varint::max_size;
224 }
225
231 template <typename StringT>
232 [[nodiscard]] static consteval std::size_t
233 calculate_max_string_field_size() noexcept {
234 constexpr std::size_t tag_size = Varint::max_varint_size(MaxTagBits);
235 return tag_size + Varint::max_size + StringT::max_size;
236 }
237
243 template <typename MsgT>
244 [[nodiscard]] static consteval std::size_t
245 calculate_max_nested_message_field_size() noexcept {
246 constexpr std::size_t tag_size = Varint::max_varint_size(MaxTagBits);
247 return tag_size + Varint::max_size + calculate_max_message_size<MsgT>();
248 }
249
255 template <typename T>
256 [[nodiscard]] static consteval std::size_t
257 calculate_max_value_size() noexcept {
258 if constexpr (Crunch::fields::is_scalar_v<T>) {
259 // Just the varint value, no tag
260 return Varint::max_size;
261 } else if constexpr (Crunch::fields::is_string_v<T>) {
262 // [Length][Data]
263 return Varint::max_size + T::max_size;
265 // [Length][NestedFields]
266 return Varint::max_size + calculate_max_message_size<T>();
267 } else if constexpr (Crunch::messages::is_array_field_v<T>) {
268 return calculate_max_array_field_size<typename T::ValueType>(
269 T::max_size);
270 } else if constexpr (Crunch::messages::is_map_field_v<T>) {
271 using KeyType = typename T::PairType::first_type;
272 using ValueType = typename T::PairType::second_type;
273 return calculate_max_map_field_size<KeyType, ValueType>(
274 T::max_size);
275 }
276 std::unreachable();
277 }
278
289 template <typename KeyT, typename ValueT>
290 [[nodiscard]] static consteval std::size_t calculate_max_map_field_size(
291 std::size_t max_elements) noexcept {
292 constexpr std::size_t tag_size = Varint::max_varint_size(MaxTagBits);
293 constexpr std::size_t length_size = Varint::max_size;
294 constexpr std::size_t count_size = Varint::max_size;
295 constexpr std::size_t key_size = calculate_max_value_size<KeyT>();
296 constexpr std::size_t value_size = calculate_max_value_size<ValueT>();
297
298 return tag_size + length_size + count_size +
299 max_elements * (key_size + value_size);
300 }
301
307 template <typename FieldT>
308 [[nodiscard]] static consteval std::size_t
309 calculate_max_field_size_type() noexcept {
310 if constexpr (Crunch::messages::is_array_field_v<FieldT>) {
311 return calculate_max_array_field_size<typename FieldT::ValueType>(
312 FieldT::max_size);
313 } else if constexpr (Crunch::messages::is_map_field_v<FieldT>) {
314 using KeyType = typename FieldT::PairType::first_type;
315 using ValueType = typename FieldT::PairType::second_type;
316 return calculate_max_map_field_size<KeyType, ValueType>(
317 FieldT::max_size);
318 } else {
319 using ValueType = typename detail::ext<FieldT>::type;
320 if constexpr (Crunch::fields::is_scalar_v<ValueType>) {
321 return calculate_max_scalar_field_size<ValueType>();
322 } else if constexpr (Crunch::fields::is_string_v<ValueType>) {
323 return calculate_max_string_field_size<ValueType>();
325 ValueType>) {
326 return calculate_max_nested_message_field_size<ValueType>();
327 }
328 }
329 return 0;
330 }
331
340 template <typename T>
341 [[nodiscard]] static constexpr std::size_t serialize_scalar_value(
342 const T& value, std::span<std::byte> output,
343 std::size_t offset) noexcept {
344 uint64_t encoded_val = 0;
345 if constexpr (std::is_same_v<T, bool>) {
346 encoded_val = value ? 1 : 0;
347 } else if constexpr (std::is_floating_point_v<T>) {
348 if constexpr (sizeof(T) == 4) {
349 encoded_val =
350 static_cast<uint64_t>(std::bit_cast<uint32_t>(value));
351 } else {
352 encoded_val = std::bit_cast<uint64_t>(value);
353 }
354 } else {
355 encoded_val = static_cast<uint64_t>(
356 std::bit_cast<std::make_unsigned_t<T>>(value));
357 }
358 return offset + Varint::encode(encoded_val, output, offset);
359 }
360
369 template <typename T>
370 [[nodiscard]] static constexpr std::size_t serialize_string_value(
371 const T& value, std::span<std::byte> output,
372 std::size_t offset) noexcept {
373 auto sv = value.get();
374 offset += Varint::encode(sv.size(), output, offset);
375 std::memcpy(output.data() + offset, sv.data(), sv.size());
376 return offset + sv.size();
377 }
378
393 [[nodiscard]] static constexpr std::size_t fixup_length_prefix(
394 std::span<std::byte> output, std::size_t len_offset,
395 std::size_t content_start, std::size_t offset) noexcept {
396 const std::size_t content_size = offset - content_start;
397 const std::size_t actual_len_varint_size = Varint::size(content_size);
398 if (actual_len_varint_size < Varint::max_size) {
399 const std::size_t shift = Varint::max_size - actual_len_varint_size;
400 std::memmove(output.data() + len_offset + actual_len_varint_size,
401 output.data() + content_start, content_size);
402 offset -= shift;
403 }
404 Varint::encode(content_size, output, len_offset);
405 return offset;
406 }
407
416 template <typename T>
417 [[nodiscard]] static constexpr std::size_t serialize_nested_message(
418 const T& value, std::span<std::byte> output,
419 std::size_t offset) noexcept {
420 const std::size_t len_offset = offset;
421 offset += Varint::max_size;
422
423 const std::size_t content_start = offset;
424 const std::size_t msg_size = serialize_fields_helper(
425 value.get_fields(), output.subspan(content_start), 0);
426 offset += msg_size;
427
428 return fixup_length_prefix(output, len_offset, content_start, offset);
429 }
430
439 template <typename FieldT>
440 [[nodiscard]] static constexpr std::size_t serialize_array_content(
441 const FieldT& field, std::span<std::byte> output,
442 std::size_t offset) noexcept {
443 using ElemT = typename FieldT::ValueType;
444
445 // Write element count
446 offset += Varint::encode(field.size(), output, offset);
447
448 // Serialize elements
449 for (const auto& item : field) {
450 if constexpr (Crunch::fields::is_scalar_v<ElemT>) {
451 offset = serialize_scalar_value(item.get(), output, offset);
452 } else if constexpr (Crunch::fields::is_string_v<ElemT>) {
453 offset = serialize_string_value(item, output, offset);
455 ElemT>) {
456 offset = serialize_nested_message(item, output, offset);
457 } else if constexpr (Crunch::messages::is_array_field_v<ElemT>) {
458 offset = serialize_array_value(item, output, offset);
459 } else if constexpr (Crunch::messages::is_map_field_v<ElemT>) {
460 offset = serialize_map_value(item, output, offset);
461 }
462 }
463 return offset;
464 }
465
474 template <typename FieldT>
475 [[nodiscard]] static constexpr std::size_t serialize_array_value(
476 const FieldT& field, std::span<std::byte> output,
477 std::size_t offset) noexcept {
478 const std::size_t len_offset = offset;
479 offset += Varint::max_size;
480 const std::size_t content_start = offset;
481
482 offset = serialize_array_content(field, output, offset);
483
484 return fixup_length_prefix(output, len_offset, content_start, offset);
485 }
486
502 template <typename FieldT>
503 [[nodiscard]] static constexpr std::size_t serialize_array_field(
504 const FieldT& field, std::span<std::byte> output,
505 std::size_t offset) noexcept {
506 const FieldId id = field.field_id;
507 offset = write_tag(id, WireType::LengthDelimited, output, offset);
508 return serialize_array_value(field, output, offset);
509 }
510
519 template <typename FieldT>
520 [[nodiscard]] static constexpr std::size_t serialize_value_without_tag(
521 const FieldT& field, std::span<std::byte> output,
522 std::size_t offset) noexcept {
523 if constexpr (Crunch::fields::is_scalar_v<FieldT>) {
524 return serialize_scalar_value(field.get(), output, offset);
525 } else if constexpr (Crunch::fields::is_string_v<FieldT>) {
526 return serialize_string_value(field, output, offset);
528 FieldT>) {
529 return serialize_nested_message(field, output, offset);
530 } else if constexpr (Crunch::messages::is_array_field_v<FieldT>) {
531 return serialize_array_value(field, output, offset);
532 } else if constexpr (Crunch::messages::is_map_field_v<FieldT>) {
533 return serialize_map_value(field, output, offset);
534 }
535 return offset;
536 }
537
546 template <typename FieldT>
547 [[nodiscard]] static constexpr std::size_t serialize_map_content(
548 const FieldT& field, std::span<std::byte> output,
549 std::size_t offset) noexcept {
550 using KeyFieldT = typename FieldT::PairType::first_type;
551 using ValueFieldT = typename FieldT::PairType::second_type;
552
553 // Write entry count
554 offset += Varint::encode(field.size(), output, offset);
555
556 // Serialize key-value pairs without tags
557 return std::accumulate(
558 field.begin(), field.end(), offset,
559 [&](std::size_t off, const auto& item) {
560 off = serialize_value_without_tag<KeyFieldT>(item.first, output,
561 off);
562 return serialize_value_without_tag<ValueFieldT>(item.second,
563 output, off);
564 });
565 }
566
575 template <typename FieldT>
576 [[nodiscard]] static constexpr std::size_t serialize_map_value(
577 const FieldT& field, std::span<std::byte> output,
578 std::size_t offset) noexcept {
579 const std::size_t len_offset = offset;
580 offset += Varint::max_size;
581 const std::size_t content_start = offset;
582
583 offset = serialize_map_content(field, output, offset);
584
585 return fixup_length_prefix(output, len_offset, content_start, offset);
586 }
587
603 template <typename FieldT>
604 [[nodiscard]] static constexpr std::size_t serialize_map_field(
605 const FieldT& field, std::span<std::byte> output,
606 std::size_t offset) noexcept {
607 const FieldId id = field.field_id;
608 offset = write_tag(id, WireType::LengthDelimited, output, offset);
609 return serialize_map_value(field, output, offset);
610 }
611
620 template <typename FieldT>
621 [[nodiscard]] static constexpr std::size_t serialize_field(
622 const FieldT& field, std::span<std::byte> output,
623 std::size_t offset) noexcept {
624 bool is_set = false;
625 if constexpr (Crunch::messages::is_array_field_v<FieldT> ||
626 Crunch::messages::is_map_field_v<FieldT>) {
627 is_set = !field.empty();
628 } else {
629 is_set = field.set_;
630 }
631
632 if (!is_set) {
633 return offset;
634 }
635
636 const FieldId id = field.field_id;
637
638 if constexpr (Crunch::messages::is_array_field_v<FieldT>) {
639 return serialize_array_field(field, output, offset);
640 } else if constexpr (Crunch::messages::is_map_field_v<FieldT>) {
641 return serialize_map_field(field, output, offset);
642 } else {
643 using ValueType = typename detail::ext<FieldT>::type;
644 if constexpr (Crunch::fields::is_scalar_v<ValueType>) {
645 offset = write_tag(id, WireType::Varint, output, offset);
646 return serialize_scalar_value(field.value_.get(), output,
647 offset);
648 } else if constexpr (Crunch::fields::is_string_v<ValueType>) {
649 offset =
650 write_tag(id, WireType::LengthDelimited, output, offset);
651 return serialize_string_value(field.value_, output, offset);
653 ValueType>) {
654 offset =
655 write_tag(id, WireType::LengthDelimited, output, offset);
656 return serialize_nested_message(field.value_, output, offset);
657 }
658 }
659 return offset;
660 }
661
671 template <typename Tuple, std::size_t I = 0>
672 [[nodiscard]] static constexpr std::size_t serialize_fields_helper(
673 const Tuple& t, std::span<std::byte> output,
674 std::size_t offset) noexcept {
675 if constexpr (I < std::tuple_size_v<std::remove_cvref_t<Tuple>>) {
676 offset = serialize_field(std::get<I>(t), output, offset);
677 return serialize_fields_helper<Tuple, I + 1>(t, output, offset);
678 }
679 return offset;
680 }
681
690 template <typename ElemT>
691 [[nodiscard]] static constexpr std::optional<Error>
692 deserialize_scalar_array(typename ElemT::ValueType& val_out,
693 std::span<const std::byte> input,
694 std::size_t& offset) noexcept {
695 using T = typename ElemT::ValueType;
696 const auto res = Varint::decode(input, offset);
697 if (!res) {
698 return Error::deserialization("invalid varint in packed");
699 }
700 offset += res->second;
701
702 if constexpr (std::is_same_v<T, bool>) {
703 val_out = (res->first != 0);
704 } else if constexpr (std::is_floating_point_v<T>) {
705 if constexpr (sizeof(T) == 4) {
706 val_out = std::bit_cast<T>(static_cast<uint32_t>(res->first));
707 } else {
708 val_out = std::bit_cast<T>(res->first);
709 }
710 } else {
711 val_out = std::bit_cast<T>(
712 static_cast<std::make_unsigned_t<T>>(res->first));
713 }
714 return std::nullopt;
715 }
716
724 [[nodiscard]] static constexpr std::expected<std::size_t, Error>
725 read_length_prefix(std::span<const std::byte> input, std::size_t& offset,
726 const char* error_msg) noexcept {
727 const auto len_res = Varint::decode(input, offset);
728 if (!len_res) {
729 return std::unexpected(Error::deserialization(error_msg));
730 }
731 offset += len_res->second;
732 std::size_t len = static_cast<std::size_t>(len_res->first);
733 if (offset + len > input.size()) {
734 return std::unexpected(Error::deserialization("buffer underflow"));
735 }
736 return len;
737 }
738
747 template <typename ElemT>
748 [[nodiscard]] static constexpr std::optional<Error>
749 deserialize_scalar_value(ElemT& val, std::span<const std::byte> input,
750 std::size_t& offset) noexcept {
751 typename ElemT::ValueType scalar_val;
752 if (const auto err =
753 deserialize_scalar_array<ElemT>(scalar_val, input, offset)) {
754 return err;
755 }
756 val.set_without_validation(scalar_val);
757 return std::nullopt;
758 }
759
768 template <typename ElemT>
769 [[nodiscard]] static constexpr std::optional<Error>
770 deserialize_string_value(ElemT& val, std::span<const std::byte> input,
771 std::size_t& offset) noexcept {
772 auto len_result =
773 read_length_prefix(input, offset, "invalid string length");
774 if (!len_result) {
775 return len_result.error();
776 }
777 std::size_t len = *len_result;
778 std::string_view sv(
779 reinterpret_cast<const char*>(input.data() + offset), len);
780 offset += len;
781 return val.set(sv);
782 }
783
792 template <typename ElemT>
793 [[nodiscard]] static constexpr std::optional<Error>
794 deserialize_message_value(ElemT& val, std::span<const std::byte> input,
795 std::size_t& offset) noexcept {
796 auto len_result =
797 read_length_prefix(input, offset, "invalid message length");
798 if (!len_result) {
799 return len_result.error();
800 }
801 std::size_t len = *len_result;
802 if (const auto err = deserialize_message_payload(
803 input.subspan(offset, len), val, 0)) {
804 return err;
805 }
806 offset += len;
807 return std::nullopt;
808 }
809
818 template <typename ElemT>
819 [[nodiscard]] static constexpr std::optional<Error> deserialize_array_value(
820 ElemT& val, std::span<const std::byte> input,
821 std::size_t& offset) noexcept {
822 auto len_result =
823 read_length_prefix(input, offset, "invalid array length");
824 if (!len_result) {
825 return len_result.error();
826 }
827 std::size_t len = *len_result;
828 auto subspan = input.subspan(offset, len);
829 std::size_t sub_offset = 0;
830 if (const auto err =
831 deserialize_array_elements(val, subspan, sub_offset, len)) {
832 return err;
833 }
834 offset += len;
835 return std::nullopt;
836 }
837
846 template <typename ElemT>
847 [[nodiscard]] static constexpr std::optional<Error> deserialize_map_value(
848 ElemT& val, std::span<const std::byte> input,
849 std::size_t& offset) noexcept {
850 auto len_result =
851 read_length_prefix(input, offset, "invalid map length");
852 if (!len_result) {
853 return len_result.error();
854 }
855 std::size_t len = *len_result;
856 auto subspan = input.subspan(offset, len);
857 std::size_t sub_offset = 0;
858 if (const auto err =
859 deserialize_map_elements(val, subspan, sub_offset, len)) {
860 return err;
861 }
862 offset += len;
863 return std::nullopt;
864 }
865
874 template <typename ElemT>
875 [[nodiscard]] static constexpr std::optional<Error>
876 deserialize_value_without_tag(ElemT& val, std::span<const std::byte> input,
877 std::size_t& offset) noexcept {
878 if constexpr (Crunch::fields::is_scalar_v<ElemT>) {
879 return deserialize_scalar_value<ElemT>(val, input, offset);
880 } else if constexpr (Crunch::fields::is_string_v<ElemT>) {
881 return deserialize_string_value<ElemT>(val, input, offset);
883 ElemT>) {
884 return deserialize_message_value<ElemT>(val, input, offset);
885 } else if constexpr (Crunch::messages::is_array_field_v<ElemT>) {
886 return deserialize_array_value<ElemT>(val, input, offset);
887 } else if constexpr (Crunch::messages::is_map_field_v<ElemT>) {
888 return deserialize_map_value<ElemT>(val, input, offset);
889 } else {
890 std::unreachable();
891 }
892 return std::nullopt;
893 }
894
904 template <typename FieldT>
905 [[nodiscard]] static constexpr std::optional<Error>
906 deserialize_array_elements(FieldT& field, std::span<const std::byte> input,
907 std::size_t& offset,
908 std::size_t end_offset) noexcept {
909 using ElemT = typename FieldT::ValueType;
910
911 // Read count
912 const auto count_res = Varint::decode(input, offset);
913 if (!count_res) {
914 return Error::deserialization("invalid array count");
915 }
916 offset += count_res->second;
917 const std::size_t count = static_cast<std::size_t>(count_res->first);
918
919 // Deserialize elements
920 for (std::size_t i = 0; i < count; ++i) {
921 ElemT elem{};
922 if (const auto err =
923 deserialize_value_without_tag<ElemT>(elem, input, offset)) {
924 return err;
925 }
926 if constexpr (Crunch::fields::is_scalar_v<ElemT>) {
927 if (const auto err = field.add(elem.get())) {
928 return err;
929 }
930 } else {
931 if (const auto err = field.add(elem)) {
932 return err;
933 }
934 }
935 }
936 return std::nullopt;
937 }
938
947 template <typename Message>
948 [[nodiscard]] static constexpr std::optional<Error>
949 deserialize_message_payload(std::span<const std::byte> input, Message& msg,
950 std::size_t offset) noexcept {
951 while (offset < input.size()) {
952 const auto tag_res = Varint::decode(input, offset);
953 if (!tag_res) {
954 return Error::deserialization("invalid tag varint");
955 }
956 const auto [tag, tag_bytes] = *tag_res;
957
958 offset += tag_bytes;
959
960 const uint32_t field_id =
961 static_cast<uint32_t>(tag >> WireTypeBits);
962 const WireType wire_type = static_cast<WireType>(tag & 0x07);
963
964 bool found = false;
965 std::optional<Error> err = visit_fields_tlv(
966 msg.get_fields(), static_cast<FieldId>(field_id), wire_type,
967 input, offset, found);
968
969 if (err) {
970 return err;
971 }
972 if (!found) {
973 err.emplace(Error::deserialization("unknown fields present"));
974 return err;
975 }
976 }
977 return std::nullopt;
978 }
979
992 template <typename FieldT>
993 [[nodiscard]] static constexpr std::optional<Error> deserialize_array_field(
994 FieldT& field, WireType wire_type, std::span<const std::byte> input,
995 std::size_t& offset) noexcept {
996 if (wire_type != WireType::LengthDelimited) {
997 return Error::deserialization("array must be length delimited");
998 }
999
1000 // Read total length
1001 const auto len_res = Varint::decode(input, offset);
1002 if (!len_res) {
1003 return Error::deserialization("invalid array length");
1004 }
1005 offset += len_res->second;
1006 const std::size_t len = static_cast<std::size_t>(len_res->first);
1007 if (offset + len > input.size()) {
1008 return Error::deserialization("array underflow");
1009 }
1010
1011 auto subspan = input.subspan(offset, len);
1012 std::size_t sub_offset = 0;
1013 if (const auto err =
1014 deserialize_array_elements(field, subspan, sub_offset, len)) {
1015 return err;
1016 }
1017
1018 offset += len;
1019 return std::nullopt;
1020 }
1021
1031 template <typename FieldT>
1032 [[nodiscard]] static constexpr std::optional<Error>
1033 deserialize_scalar_field(FieldT& field, WireType wire_type,
1034 std::span<const std::byte> input,
1035 std::size_t& offset) noexcept {
1036 using ValueType = typename detail::ext<FieldT>::type;
1037 using T = typename ValueType::ValueType;
1038
1039 if (wire_type != WireType::Varint) {
1040 return Error::deserialization("scalar must be varint");
1041 }
1042
1043 const auto res = Varint::decode(input, offset);
1044 if (!res) {
1045 return Error::deserialization("invalid varint");
1046 }
1047 offset += res->second;
1048
1049 T val;
1050 if constexpr (std::is_same_v<T, bool>) {
1051 val = (res->first != 0);
1052 } else if constexpr (std::is_floating_point_v<T>) {
1053 if constexpr (sizeof(T) == 4) {
1054 val = std::bit_cast<T>(static_cast<uint32_t>(res->first));
1055 } else {
1056 val = std::bit_cast<T>(res->first);
1057 }
1058 } else {
1059 val = std::bit_cast<T>(
1060 static_cast<std::make_unsigned_t<T>>(res->first));
1061 }
1062 // Deserialize will validate fields after the entire message is
1063 // deserialized
1064 field.set_without_validation(val);
1065 return std::nullopt;
1066 }
1067
1077 template <typename FieldT>
1078 [[nodiscard]] static constexpr std::optional<Error>
1079 deserialize_string_field(FieldT& field, WireType wire_type,
1080 std::span<const std::byte> input,
1081 std::size_t& offset) noexcept {
1082 if (wire_type != WireType::LengthDelimited) {
1083 return Error::deserialization("string requires length delimited");
1084 }
1085
1086 const auto len_res = Varint::decode(input, offset);
1087 if (!len_res) {
1088 return Error::deserialization("invalid length");
1089 }
1090 offset += len_res->second;
1091 const std::size_t len = static_cast<std::size_t>(len_res->first);
1092 if (offset + len > input.size()) {
1093 return Error::deserialization("underflow");
1094 }
1095
1096 const std::string_view sv(
1097 reinterpret_cast<const char*>(input.data() + offset), len);
1098 offset += len;
1099 if (const auto err = field.set(sv)) {
1100 return err;
1101 }
1102 return std::nullopt;
1103 }
1104
1114 template <typename FieldT>
1115 [[nodiscard]] static constexpr std::optional<Error>
1116 deserialize_nested_message_field(FieldT& field, WireType wire_type,
1117 std::span<const std::byte> input,
1118 std::size_t& offset) noexcept {
1119 if (wire_type != WireType::LengthDelimited) {
1121 "nested msg requires length delimited");
1122 }
1123
1124 const auto len_res = Varint::decode(input, offset);
1125 if (!len_res) {
1126 return Error::deserialization("invalid length");
1127 }
1128 offset += len_res->second;
1129 const std::size_t len = static_cast<std::size_t>(len_res->first);
1130 if (offset + len > input.size()) {
1131 return Error::deserialization("underflow");
1132 }
1133
1135 if (const auto err = deserialize_message_payload(
1136 input.subspan(offset, len), field, 0)) {
1137 return err;
1138 }
1139 } else {
1140 typename FieldT::FieldType temp;
1141 if (const auto err = deserialize_message_payload(
1142 input.subspan(offset, len), temp, 0)) {
1143 return err;
1144 }
1145 // Messages cannot fail on set.
1146 field.set(temp);
1147 }
1148 offset += len;
1149 return std::nullopt;
1150 }
1151
1158 template <typename T>
1159 [[nodiscard]] static constexpr auto extract_map_value(const T& f) noexcept
1160 -> decltype(auto) {
1161 if constexpr (Crunch::messages::is_array_field_v<T> ||
1162 Crunch::messages::is_map_field_v<T> ||
1164 return f;
1165 } else {
1166 return f.get();
1167 }
1168 }
1169
1179 template <typename FieldT>
1180 [[nodiscard]] static constexpr std::optional<Error>
1181 deserialize_map_elements(FieldT& field, std::span<const std::byte> input,
1182 std::size_t& offset,
1183 std::size_t end_offset) noexcept {
1184 using KeyFieldT = typename FieldT::PairType::first_type;
1185 using ValueFieldT = typename FieldT::PairType::second_type;
1186
1187 // Read count
1188 const auto count_res = Varint::decode(input, offset);
1189 if (!count_res) {
1190 return Error::deserialization("invalid map count");
1191 }
1192 offset += count_res->second;
1193 const std::size_t count = static_cast<std::size_t>(count_res->first);
1194
1195 // Deserialize key-value pairs
1196 for (std::size_t i = 0; i < count; ++i) {
1197 KeyFieldT key_field{};
1198 ValueFieldT val_field{};
1199
1200 if (const auto err = deserialize_value_without_tag<KeyFieldT>(
1201 key_field, input, offset)) {
1202 return err;
1203 }
1204 if (const auto err = deserialize_value_without_tag<ValueFieldT>(
1205 val_field, input, offset)) {
1206 return err;
1207 }
1208
1209 if (const auto err = field.insert(extract_map_value(key_field),
1210 extract_map_value(val_field))) {
1211 return err;
1212 }
1213 }
1214 return std::nullopt;
1215 }
1216
1229 template <typename FieldT>
1230 [[nodiscard]] static constexpr std::optional<Error> deserialize_map_field(
1231 FieldT& field, WireType wire_type, std::span<const std::byte> input,
1232 std::size_t& offset) noexcept {
1233 if (wire_type != WireType::LengthDelimited) {
1234 return Error::deserialization("map must be length delimited");
1235 }
1236
1237 // Read total length
1238 const auto len_res = Varint::decode(input, offset);
1239 if (!len_res) {
1240 return Error::deserialization("could not decode map length");
1241 }
1242 offset += len_res->second;
1243 const std::size_t len = static_cast<std::size_t>(len_res->first);
1244 if (offset + len > input.size()) {
1245 return Error::deserialization("map underflow");
1246 }
1247
1248 auto subspan = input.subspan(offset, len);
1249 std::size_t sub_offset = 0;
1250 if (const auto err =
1251 deserialize_map_elements(field, subspan, sub_offset, len)) {
1252 return err;
1253 }
1254
1255 offset += len;
1256 return std::nullopt;
1257 }
1258
1268 template <typename FieldT>
1269 [[nodiscard]] static constexpr std::optional<Error> deserialize_field_value(
1270 FieldT& field, WireType wire_type, std::span<const std::byte> input,
1271 std::size_t& offset) noexcept {
1272 if constexpr (Crunch::messages::is_array_field_v<FieldT>) {
1273 return deserialize_array_field(field, wire_type, input, offset);
1274 } else if constexpr (Crunch::messages::is_map_field_v<FieldT>) {
1275 return deserialize_map_field(field, wire_type, input, offset);
1276 } else {
1277 using ValueType = typename detail::ext<FieldT>::type;
1278 if constexpr (Crunch::fields::is_scalar_v<ValueType>) {
1279 return deserialize_scalar_field(field, wire_type, input,
1280 offset);
1281 } else if constexpr (Crunch::fields::is_string_v<ValueType>) {
1282 return deserialize_string_field(field, wire_type, input,
1283 offset);
1285 ValueType>) {
1286 return deserialize_nested_message_field(field, wire_type, input,
1287 offset);
1288 }
1289 }
1290 return std::nullopt;
1291 }
1292
1306 template <typename Tuple, std::size_t I = 0>
1307 [[nodiscard]] static constexpr std::optional<Error> visit_fields_tlv(
1308 Tuple&& fields_tuple, FieldId target_id, WireType wt,
1309 std::span<const std::byte> input, std::size_t& offset,
1310 bool& found) noexcept {
1311 using TupleT = std::remove_cvref_t<Tuple>;
1312 if constexpr (I < std::tuple_size_v<TupleT>) {
1313 auto& f = std::get<I>(fields_tuple);
1314 if (f.field_id == target_id) {
1315 found = true;
1316 return deserialize_field_value(f, wt, input, offset);
1317 }
1318 return visit_fields_tlv<Tuple, I + 1>(
1319 std::forward<Tuple>(fields_tuple), target_id, wt, input, offset,
1320 found);
1321 }
1322 return std::nullopt;
1323 }
1324};
1325
1326} // namespace Crunch::serdes
Concept to detect if a type quacks like a Crunch Message.
Definition: crunch_field.hpp:226
int32_t FieldId
Unique identifier for a field within a Crunch message.
Definition: crunch_types.hpp:11
constexpr T LittleEndian(T value) noexcept
Converts a value to/from Little Endian byte order.
Definition: crunch_endian.hpp:21
Format
Serialization format identifier stored in the message header.
Definition: crunch_types.hpp:30
@ TLV
Tag-Length-Value encoding.
static constexpr Error deserialization(std::string_view msg="deserialization error") noexcept
Creates an error representing a deserialization failure.
Definition: crunch_types.hpp:104
Definition: crunch_tlv_layout.hpp:53
static constexpr std::size_t Serialize(const Message &msg, std::span< std::byte > output) noexcept
Serializes a message into the output buffer.
Definition: crunch_tlv_layout.hpp:95
static constexpr auto Deserialize(std::span< const std::byte > input, Message &msg) noexcept -> std::optional< Error >
Deserializes a message from the input buffer.
Definition: crunch_tlv_layout.hpp:123
WireType
Wire types for TLV encoding.
Definition: crunch_tlv_layout.hpp:57
static consteval std::size_t Size() noexcept
Calculates the maximum possible serialized size of a message.
Definition: crunch_tlv_layout.hpp:82
static constexpr std::size_t WireTypeBits
Number of bits used for the wire type in a tag.
Definition: crunch_tlv_layout.hpp:67
static constexpr std::size_t MaxTagBits
The maximum number of bits required for a Tag (FieldID + WireType). Used for calculating the maximum ...
Definition: crunch_tlv_layout.hpp:73
Utility for Varint encoding/decoding.
Definition: crunch_varint.hpp:16
static constexpr std::optional< std::pair< uint64_t, std::size_t > > decode(std::span< const std::byte > input, std::size_t offset) noexcept
Decodes a Varint from a buffer.
Definition: crunch_varint.hpp:45
static constexpr std::size_t size(uint64_t value) noexcept
Calculates the size requirement for a value encoded as Varint.
Definition: crunch_varint.hpp:77
static consteval std::size_t max_varint_size(std::size_t value_bits)
Calculates the maximum varint size for a given number of bits.
Definition: crunch_varint.hpp:101
static constexpr std::size_t max_size
The maximum size required for a 64-bit integer encoded as Varint. ceil(64 / 7) = 10.
Definition: crunch_varint.hpp:94
static constexpr std::size_t encode(uint64_t value, std::span< std::byte > output, std::size_t offset) noexcept
Encodes a value as a Varint.
Definition: crunch_varint.hpp:25
Helper to extract the underlying scalar type from a field wrapper.
Definition: crunch_tlv_layout.hpp:41