Skip to content

Commit be43067

Browse files
committed
✨ Add message packing
Problem: - Message definitions can't easily be combined, especially when the locations of fields are fixed in definitions and it would be useful to pack two or more messages together. - See issue #653 Solution: - Add primitives for combining messages: - `shifted_by` on fields and messages to increment their location(s) - `msg::combine` to naively combine multiple messages together - `msg::pack` to concatenate messages according to desired alignment Notes: - `at` has been simplified
1 parent c1cf01d commit be43067

File tree

4 files changed

+213
-52
lines changed

4 files changed

+213
-52
lines changed

docs/message.adoc

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,54 @@ using msg1_defn = extend<header_defn, "msg", type_f::with_required<1>, payload_f
136136
using msg2_defn = extend<header_defn, "msg", type_f::with_required<2>, payload_f>;
137137
----
138138

139+
==== Combining and packing messages
140+
141+
It is sometimes useful to combine multiple message definitions, to avoid
142+
repetition. For example, adding a payload message to a header:
143+
144+
[source,cpp]
145+
----
146+
using type_f = field<"type", std::uint32_t>::located<at{0_dw, 7_msb, 0_lsb}>;
147+
using header_defn = message<"header", type_f>;
148+
149+
using data_f = field<"data">, std::uint32_t>::located<at{0_dw, 15_msb, 7_lsb}>;
150+
using payload_defn = message<"payload", data_f>;
151+
152+
using msg_defn = extend<
153+
combine<"msg", header_defn, payload_defn>,
154+
type_f::with_required<1>>;
155+
----
156+
157+
The combined definition incorporates all the fields of the messages. And as
158+
shown, the combination might typically be `extend`ed with a constraint on the
159+
header field.
160+
161+
Other times it is useful to automatically concatenate or `pack` messages
162+
together, where the field locations in each message start at 0.
163+
164+
[source,cpp]
165+
----
166+
using type_f = field<"type", std::uint32_t>::located<at{0_dw, 5_msb, 0_lsb}>;
167+
using header_defn = message<"header", type_f>;
168+
169+
// note: data_f collides with type_f under a naive combination
170+
using data_f = field<"data">, std::uint32_t>::located<at{0_dw, 7_msb, 0_lsb}>;
171+
using payload_defn = message<"payload", data_f>;
172+
173+
using msg_defn = extend<
174+
pack<"msg", std::uint8_t, header_defn, payload_defn>,
175+
type_f::with_required<1>>;
176+
177+
// resulting message layout:
178+
// byte 0 1
179+
// bit 01234567 01234567
180+
// field |type|xx |data |
181+
----
182+
183+
The second parameter to `pack` (`std::uint8_t` in the example above) defines how
184+
the messages are packed together - in this case, each subsequent message is
185+
byte-aligned.
186+
139187
==== Owning vs view types
140188

141189
An owning message uses underlying storage: by default, this is a `std::array` of

include/msg/field.hpp

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ concept field_location = requires(T const &t) {
7171
{ t.size() } -> std::same_as<std::uint32_t>;
7272
{ t.valid() } -> std::same_as<bool>;
7373
{ t.sort_key() } -> std::same_as<std::uint32_t>;
74+
{ t.shifted_by(0) } -> std::same_as<T>;
7475
};
7576

7677
namespace detail {
@@ -305,62 +306,33 @@ CONSTEVAL auto operator""_msb(unsigned long long int v) -> msb_t {
305306
}
306307
} // namespace literals
307308

308-
template <typename...> struct at;
309+
struct bit_unit {};
309310

310-
template <> struct at<dword_index_t, msb_t, lsb_t> {
311-
dword_index_t index_{};
312-
msb_t msb_{};
313-
lsb_t lsb_{};
314-
315-
[[nodiscard]] constexpr auto
316-
index() const -> std::underlying_type_t<dword_index_t> {
317-
return stdx::to_underlying(index_);
318-
}
319-
[[nodiscard]] constexpr auto lsb() const -> std::underlying_type_t<lsb_t> {
320-
return stdx::to_underlying(lsb_);
321-
}
322-
[[nodiscard]] constexpr auto size() const -> std::underlying_type_t<lsb_t> {
323-
return stdx::to_underlying(msb_) - lsb() + 1;
324-
}
325-
[[nodiscard]] constexpr auto valid() const -> bool {
326-
return size() <= 64 and
327-
stdx::to_underlying(msb_) >= stdx::to_underlying(lsb_);
328-
}
329-
[[nodiscard]] constexpr auto
330-
sort_key() const -> std::underlying_type_t<lsb_t> {
331-
return index() * 32u + stdx::to_underlying(lsb_);
311+
template <typename Unit, typename T>
312+
constexpr auto unit_bit_size(T t) -> std::uint32_t {
313+
if constexpr (std::same_as<Unit, bit_unit>) {
314+
return static_cast<std::uint32_t>(t);
315+
} else {
316+
return static_cast<std::uint32_t>(t) * sizeof(Unit) * CHAR_BIT;
332317
}
333-
};
318+
}
334319

335-
template <> struct at<byte_index_t, msb_t, lsb_t> {
336-
byte_index_t index_{};
320+
struct at {
337321
msb_t msb_{};
338322
lsb_t lsb_{};
339323

340-
[[nodiscard]] constexpr auto
341-
index() const -> std::underlying_type_t<byte_index_t> {
342-
return stdx::to_underlying(index_) / 4;
343-
}
344-
[[nodiscard]] constexpr auto lsb() const -> std::underlying_type_t<lsb_t> {
345-
return (stdx::to_underlying(index_) * 8) % 32 +
346-
stdx::to_underlying(lsb_);
347-
}
348-
[[nodiscard]] constexpr auto size() const -> std::underlying_type_t<lsb_t> {
349-
return stdx::to_underlying(msb_) - stdx::to_underlying(lsb_) + 1;
350-
}
351-
[[nodiscard]] constexpr auto valid() const -> bool {
352-
return size() <= 8 and
353-
stdx::to_underlying(msb_) >= stdx::to_underlying(lsb_);
354-
}
355-
[[nodiscard]] constexpr auto
356-
sort_key() const -> std::underlying_type_t<lsb_t> {
357-
return index() * 32u + stdx::to_underlying(lsb_);
358-
}
359-
};
360-
361-
template <> struct at<msb_t, lsb_t> {
362-
msb_t msb_{};
363-
lsb_t lsb_{};
324+
constexpr at() = default;
325+
constexpr at(msb_t m, lsb_t l) : msb_{m}, lsb_{l} {}
326+
constexpr at(dword_index_t di, msb_t m, lsb_t l)
327+
: msb_{unit_bit_size<std::uint32_t>(stdx::to_underlying(di)) +
328+
stdx::to_underlying(m)},
329+
lsb_{unit_bit_size<std::uint32_t>(stdx::to_underlying(di)) +
330+
stdx::to_underlying(l)} {}
331+
constexpr at(byte_index_t bi, msb_t m, lsb_t l)
332+
: msb_{unit_bit_size<std::uint8_t>(stdx::to_underlying(bi)) +
333+
stdx::to_underlying(m)},
334+
lsb_{unit_bit_size<std::uint8_t>(stdx::to_underlying(bi)) +
335+
stdx::to_underlying(l)} {}
364336

365337
[[nodiscard]] constexpr auto
366338
index() const -> std::underlying_type_t<lsb_t> {
@@ -380,10 +352,12 @@ template <> struct at<msb_t, lsb_t> {
380352
sort_key() const -> std::underlying_type_t<lsb_t> {
381353
return stdx::to_underlying(lsb_);
382354
}
355+
[[nodiscard]] constexpr auto shifted_by(auto n) const -> at {
356+
return {msb_t{stdx::to_underlying(msb_) + n},
357+
lsb_t{stdx::to_underlying(lsb_) + n}};
358+
}
383359
};
384360

385-
template <typename... Ts> at(Ts...) -> at<Ts...>;
386-
387361
namespace detail {
388362
template <auto... Ats>
389363
requires(... and field_location<decltype(Ats)>)
@@ -543,6 +517,12 @@ class field_t : public field_spec_t<Name, T, detail::field_size<Ats...>>,
543517
using with_required = field_t<Name, T, detail::with_const_default<T, V>,
544518
msg::equal_to_t<field_t, V>, Ats...>;
545519

520+
// ======================================================================
521+
// shift a field
522+
template <auto N, typename Unit = bit_unit>
523+
using shifted_by =
524+
field_t<Name, T, Default, M, Ats.shifted_by(unit_bit_size<Unit>(N))...>;
525+
546526
// ======================================================================
547527
// legacy aliases
548528
template <typename X> using WithMatch = with_matcher<X>;

include/msg/message.hpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <stdx/utility.hpp>
2020

2121
#include <boost/mp11/algorithm.hpp>
22+
#include <boost/mp11/function.hpp>
2223
#include <boost/mp11/list.hpp>
2324
#include <boost/mp11/set.hpp>
2425

@@ -339,6 +340,10 @@ template <stdx::ct_string Name, typename... Fields> struct message {
339340
template <template <typename, std::size_t> typename C, typename T>
340341
using custom_storage_t = typename access_t::template storage_t<C, T>;
341342

343+
template <auto N, typename Unit = bit_unit>
344+
using shifted_by =
345+
message<Name, typename Fields::template shifted_by<N, Unit>...>;
346+
342347
template <typename S>
343348
constexpr static auto fits_inside =
344349
(... and Fields::template fits_inside<S>());
@@ -652,4 +657,49 @@ call_with_message(F &&f, S &&s, Args &&...args) -> decltype(auto) {
652657
std::forward<Args>(args)...);
653658
}
654659
}
660+
661+
namespace detail {
662+
template <typename AlignTo, typename... Msgs>
663+
using msg_sizes = stdx::type_list<typename Msgs::template size<AlignTo>...>;
664+
665+
template <typename S>
666+
using negated_size = std::integral_constant<std::size_t, -S::value>;
667+
668+
template <typename AlignTo, typename... Msgs>
669+
using msg_offsets = boost::mp11::mp_partial_sum<
670+
msg_sizes<AlignTo, Msgs...>,
671+
negated_size<typename boost::mp11::mp_first<
672+
stdx::type_list<Msgs...>>::template size<AlignTo>>,
673+
boost::mp11::mp_plus>;
674+
675+
template <typename AlignTo> struct shift_msg_q {
676+
template <typename Offset, typename Msg>
677+
using fn = typename Msg::template shifted_by<Offset::value, AlignTo>;
678+
};
679+
680+
template <typename AlignTo, typename... Msgs>
681+
using shifted_msgs = boost::mp11::mp_transform_q<shift_msg_q<AlignTo>,
682+
msg_offsets<AlignTo, Msgs...>,
683+
stdx::type_list<Msgs...>>;
684+
685+
template <stdx::ct_string Name> struct combiner {
686+
template <typename... Fields> using fn = msg::message<Name, Fields...>;
687+
};
688+
689+
template <stdx::ct_string Name> struct combine_q {
690+
template <typename... Msgs>
691+
requires(sizeof...(Msgs) > 0)
692+
using fn = boost::mp11::mp_apply_q<
693+
combiner<Name>, boost::mp11::mp_append<typename Msgs::fields_t...>>;
694+
};
695+
} // namespace detail
696+
697+
template <stdx::ct_string Name, typename... Msgs>
698+
requires(sizeof...(Msgs) > 0)
699+
using combine = typename detail::combine_q<Name>::template fn<Msgs...>;
700+
701+
template <stdx::ct_string Name, typename AlignTo, typename... Msgs>
702+
requires(sizeof...(Msgs) > 0)
703+
using pack = boost::mp11::mp_apply_q<detail::combine_q<Name>,
704+
detail::shifted_msgs<AlignTo, Msgs...>>;
655705
} // namespace msg

test/msg/message.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,86 @@ TEST_CASE("message equivalence matcher", "[message]") {
538538
"f3"_field = 0xd00d};
539539
CHECK_THAT(m2, not MsgEquiv(m));
540540
}
541+
542+
TEST_CASE("message reports size", "[message]") {
543+
static_assert(msg_defn::size<std::uint32_t>::value == 2);
544+
static_assert(msg_defn::size<std::uint16_t>::value == 4);
545+
static_assert(msg_defn::size<std::uint8_t>::value == 7);
546+
}
547+
548+
TEST_CASE("shift a field by bit offset", "[message]") {
549+
using new_field = id_field::shifted_by<3>;
550+
using expected_field =
551+
field<"id", std::uint32_t>::located<at{34_msb, 27_lsb}>;
552+
static_assert(std::is_same_v<new_field, expected_field>);
553+
}
554+
555+
TEST_CASE("shift a field by byte offset", "[message]") {
556+
using new_field = id_field::shifted_by<1, std::uint8_t>;
557+
using expected_field =
558+
field<"id", std::uint32_t>::located<at{39_msb, 32_lsb}>;
559+
static_assert(std::is_same_v<new_field, expected_field>);
560+
}
561+
562+
TEST_CASE("shift all fields in a message", "[message]") {
563+
using base_defn = message<"msg", id_field, field1>;
564+
using defn = base_defn::shifted_by<1, std::uint8_t>;
565+
566+
using shifted_id = id_field::shifted_by<1, std::uint8_t>;
567+
using shifted_field1 = field1::shifted_by<1, std::uint8_t>;
568+
using expected_defn = message<"msg", shifted_id, shifted_field1>;
569+
static_assert(std::is_same_v<defn, expected_defn>);
570+
}
571+
572+
TEST_CASE("combine messages", "[message]") {
573+
using f1 = field<"f1", std::uint32_t>::located<at{23_msb, 16_lsb}>;
574+
using f2 = field<"f2", std::uint32_t>::located<at{15_msb, 0_lsb}>;
575+
using m1 = message<"m1", f1, f2>;
576+
577+
using f3 = field<"f3", std::uint32_t>::located<at{23_msb, 16_lsb}>;
578+
using f4 = field<"f4", std::uint32_t>::located<at{15_msb, 0_lsb}>;
579+
using m2 = message<"m2", f3, f4>;
580+
581+
using defn = combine<"defn", m1, m2::shifted_by<1, std::uint32_t>>;
582+
using expected_defn =
583+
message<"defn", f1, f2, f3::shifted_by<1, std::uint32_t>,
584+
f4::shifted_by<1, std::uint32_t>>;
585+
static_assert(std::is_same_v<defn, expected_defn>);
586+
}
587+
588+
TEST_CASE("combine 1 message", "[message]") {
589+
using f1 = field<"f1", std::uint32_t>::located<at{15_msb, 0_lsb}>;
590+
using f2 = field<"f2", std::uint32_t>::located<at{23_msb, 16_lsb}>;
591+
using m1 = message<"m1", f1, f2>;
592+
593+
using defn = combine<"defn", m1>;
594+
using expected_defn = message<"defn", f1, f2>;
595+
static_assert(std::is_same_v<defn, expected_defn>);
596+
}
597+
598+
TEST_CASE("pack messages", "[message]") {
599+
using f1 = field<"f1", std::uint32_t>::located<at{15_msb, 0_lsb}>;
600+
using f2 = field<"f2", std::uint32_t>::located<at{23_msb, 16_lsb}>;
601+
using m1 = message<"m1", f1, f2>;
602+
603+
using f3 = field<"f3", std::uint32_t>::located<at{15_msb, 0_lsb}>;
604+
using f4 = field<"f4", std::uint32_t>::located<at{23_msb, 16_lsb}>;
605+
using m2 = message<"m2", f3, f4>;
606+
607+
using defn = pack<"defn", std::uint8_t, m1, m2>;
608+
using expected_defn =
609+
message<"defn", f1, f2,
610+
f3::shifted_by<m1::size<std::uint8_t>::value, std::uint8_t>,
611+
f4::shifted_by<m1::size<std::uint8_t>::value, std::uint8_t>>;
612+
static_assert(std::is_same_v<defn, expected_defn>);
613+
}
614+
615+
TEST_CASE("pack 1 message", "[message]") {
616+
using f1 = field<"f1", std::uint32_t>::located<at{15_msb, 0_lsb}>;
617+
using f2 = field<"f2", std::uint32_t>::located<at{23_msb, 16_lsb}>;
618+
using m1 = message<"m1", f1, f2>;
619+
620+
using defn = pack<"defn", std::uint8_t, m1>;
621+
using expected_defn = message<"defn", f1, f2>;
622+
static_assert(std::is_same_v<defn, expected_defn>);
623+
}

0 commit comments

Comments
 (0)