Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
msg_impl.cpp
Go to the documentation of this file.
1/* Flow-IPC: Structured Transport
2 * Copyright (c) 2023 Akamai Technologies, Inc.; and other contributors.
3 * Each commit is copyright by its respective author or author's employer.
4 *
5 * Licensed under the MIT License:
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 * THE SOFTWARE. */
24
25/// @file
26
31#include <flow/error/error.hpp>
32#include <boost/endian.hpp>
33#include <boost/integer.hpp>
34#include <cstring>
35#include <limits>
36#include <utility>
37
39{
40
41// Free function implementations.
42
45 Error_code* err_code,
46 const Session_token& session_token, msg_id_t originating_msg_id_or_none, msg_id_t id_or_none,
47 size_t n_serialization_segments_or_none, const Split_segments* split_segs)
48{
49 using boost::endian::native_to_little;
50 using flow::error::Runtime_error;
51 /* capnp doesn't put capnp-`using` aliases into generated code, so to get at SplitSegment (a scalar type)
52 * grab the element type of the splitSegmentsOrNull list via its getter's return.
53 * Careful: This is the thing storing *both* m_start_idx and m_n_cont_subsegs, so each of those has
54 * 1/2 the bit width of split_seg_t. */
55 using split_seg_t
56 = decltype(std::declval<schema::detail::StructuredMessage::BodySerializationInfo::Reader>()
57 .getSplitSegmentsOrNull()[0]);
58 /* This, then, is the C type corresponding to the capnp-type we use to store each of the aforementioned m_*.
59 *
60 * Please read the .capnp doc comment for `using SplitSegment`; it explains the strategy and tactics behind
61 * (1) storing the two m_ in a single UInt??? and (2) the width ??? chosen for that type. */
62 using split_seg_sz_t = boost::uint_t<std::numeric_limits<split_seg_t>::digits / 2>::exact;
63 static_assert((sizeof(split_seg_sz_t) * 2) == sizeof(split_seg_t), "Gotta be a bug in the last 2 statements.");
64
65 assert(err_code && "We are not public and are free to decide (and decided) not to have a throwing mode.");
66
67 try
68 {
69 /* Typically one uses initRoot<>(), but here it would be wrong. getRoot<X>() will be equivalent to
70 * initRoot<X>(), if X has not yet been init-ed; or return the existing X, if it has. We promised we would
71 * try to work in either case.
72 * (Though in that case it's their responsibility to ensure `id_or_none != 0`; and to be ready if we return
73 * `false` indicating reuse is impossible in a certain other situation.) */
74 auto msg_root = builder->getRoot<schema::detail::StructuredMessage>();
75
76 /* Refer to structured_msg.capnp StructuredMessage. It is *now* send time, so we fill in all the metadata
77 * needed. Let's go over it:
78 * - authHeader.sessionToken: We set it here.
79 * - id: It is used as a sequence #. We set it here; it may be zero indicating internal message.
80 * - originatingMessageIdOrNone: We set it here; it is non-zero if and only if this is a response to a past
81 * incoming message.
82 * - bodySerializationInfo: We initialize it and fill it out if so directed.
83 * - numBodySerializationSegments: Comes from n_serialization_segments_or_none.
84 * - splitSegments: Shall be left null if !split_segs; else contains a direct transcription of
85 * *split_segs, which is a sequence of integer-bearing simple structs. (Spoiler: They describe which
86 * of the following numBodySerializationSegments must be joined together to obtain segment(s) that are
87 * each too large to fit into an IPC message.)
88 * - internalMessageBody: Similarly we initialize it (and return via out-arg its builder) if so directed.
89 *
90 * As noted in structured_msg.capnp docs and our own doc header, splitSegments is touchy in that *if* reusing
91 * a previously load_mdt()ed *builder, *and* splitSegments size must change (including going to or from 0, or
92 * more accurately splitSegments but go non-null to null or vice versa), then we are unable to reuse *builder
93 * and shall return `false` ASAP, probably causing caller to re-create a fresh *builder and call us again.
94 * Reminder: This is assumed (and measures are taken elsewhere) to be rare, which is why we don't balk at
95 * doing this.
96 *
97 * Note: changing size from non-zero to <other non-zero>, or from non-zero to zero, leaks serialization space
98 * (capnp does not reclaim the orphaned list); hence those cases return `false`. Going from zero (null node)
99 * to non-zero would be capnp-legal (a fresh allocation; nothing orphaned) -- but the mdt header is a
100 * fixed-size area, so we conservatively treat any size change alike. It's a corner case of a corner case;
101 * requiring a re-call with fresh *builder is not too bad.
102 *
103 * Similarly if internalMessageBody is relevant (this describes an internal message), it'll need to be
104 * re-.init...()ed, but we classify this as a pre-condition by contract (assert() on their trying to
105 * reuse *builder). */
106
107 /* Since caller may be relying on our diagnostic of needing to re-create *builder, check for those conditions
108 * first so as to make it snappy. */
109 const bool inited_already = msg_root.hasAuthHeader(); // Telltale sign that .getRoot() returned existing root.
110 const bool want_internal_else_user_msg = id_or_none == 0;
111 if (inited_already)
112 {
113 assert((!want_internal_else_user_msg) && "Contract is not to try to load internal msg into reused *builder.");
114
115 auto cur_body_serialization_info = msg_root.getBodySerializationInfo();
116 const size_t cur_n_split_segs = cur_body_serialization_info.hasSplitSegmentsOrNull()
117 ? cur_body_serialization_info.getSplitSegmentsOrNull().size()
118 : 0;
119 if (cur_n_split_segs != (split_segs ? split_segs->size() : size_t{0}))
120 {
121 return false;
122 }
123 /* else { Happens to be the same number of split_segs before and after (possibly 0).
124 So can no-op or modify list elements in-place (see below). OK to reuse *builder. } */
125 }
126 // else { *builder is fresh anyway; we will set everything up below. }
127
128 // Deal with sessionToken. Encode as mandated in .capnp Uuid doc header. @todo Factor out into a util method.
129
130 auto capnp_uuid = inited_already ? msg_root.getAuthHeader().getSessionToken()
131 : msg_root.initAuthHeader().initSessionToken();
132 static_assert(std::remove_reference_t<decltype(session_token)>::static_size() == 2 * sizeof(uint64_t),
133 "World is broken: UUIDs expected to be 16 bytes!");
134 uint64_t first8;
135 uint64_t last8;
136 std::memcpy(&first8, session_token.data(), sizeof(first8));
137 std::memcpy(&last8, session_token.data() + sizeof(first8), sizeof(last8));
138 capnp_uuid.setFirst8(native_to_little(first8)); // Reminder: Likely no-op + copy of uint64_t.
139 capnp_uuid.setLast8(native_to_little(last8)); // Ditto.
140
141 msg_root.setId(id_or_none);
142 msg_root.setOriginatingMessageIdOrNone(originating_msg_id_or_none);
143
144 if (want_internal_else_user_msg)
145 {
146 assert(internal_msg_builder_or_null && "Did not follow contract.");
147 assert((n_serialization_segments_or_none == 0) && "Did not follow contract.");
148 assert((!split_segs) && "Did not follow contract.");
149
150 *internal_msg_builder_or_null = msg_root.initInternalMessageBody(); // OK to .init...(): fresh *builder.
151 }
152 else // if (user message)
153 {
154 /* Little subtlety: If `inited_already`, contract requires that existing *builder already describes a user
155 * message, not an internal message. We could probably assert() on this but choose not to. Let's trust
156 * 'em this time. */
157
158 assert((!internal_msg_builder_or_null) && "Did not follow contract.");
159 assert((n_serialization_segments_or_none != 0)
160 && "Did not follow contract: If serializing a user message, you have to have some segs for it.");
161
162 auto body_serialization_info = inited_already ? msg_root.getBodySerializationInfo()
163 : msg_root.initBodySerializationInfo();
164 body_serialization_info.setNumBodySerializationSegments(n_serialization_segments_or_none);
165 if (split_segs)
166 {
167 const auto n_split_segs = split_segs->size(); // We've ensured this equals existing list's (if any) size.
168 assert((n_split_segs != 0) && "By convention if no split_segs then pass-in null.");
169
170 auto split_segs_list = inited_already ? body_serialization_info.getSplitSegmentsOrNull()
171 : body_serialization_info.initSplitSegmentsOrNull(n_split_segs);
172 for (size_t idx = 0; idx != n_split_segs; ++idx)
173 {
174 const auto& split_seg = (*split_segs)[idx];
175
176 if ((split_seg.m_start_idx > std::numeric_limits<split_seg_sz_t>::max())
177 ||
178 (split_seg.m_n_cont_subsegs > std::numeric_limits<split_seg_sz_t>::max()))
179 {
180 // Split-segment record does not fit the wire type; a message this fragmented is not supported.
182 return true;
183 }
184 // else
185
186 /* Again: See doc header for `using SplitSegment` in .capnp for full strategy/tactics here.
187 * Bottom line is we encode it not as a struct but as a single double-width integer containing the two
188 * equally-sized integers within it. */
189 split_segs_list.set(idx,
190 static_cast<split_seg_t>(split_seg.m_start_idx)
191 | (static_cast<split_seg_t>(split_seg.m_n_cont_subsegs)
192 << (4 * sizeof(split_seg_t)))); // Or: 8 * sizeof(split_seg_sz_t).
193 }
194 }
195 // else if (!split_segs): We've guaranteed .getSplitSegmentsOrNull() is null already; so it shall remain.
196 } // else // if (user message)
197
198 err_code->clear();
199 } // try
200 catch (const Runtime_error& exc)
201 {
202 if (exc.code() != error::Code::S_INVALID_ARGUMENT)
203 {
204 throw;
205 }
206 // else:
207
208 /* Our Capped_sz_capnp_message_builder ran out of its fixed-size scratch area -- the mdt header -- meaning
209 * the mdt does not fit; in practice possible only due to a sufficiently long splitSegmentsOrNull list.
210 * (See its allocateSegment() which throws exactly this.) Note this detection is adaptive by design:
211 * no formula tracking the schema layout; the capped builder itself is the authority on what fits. */
213 }
214 return true;
215} // load_mdt()
216
217} // namespace ipc::transport::struc
Simple utility: a capnp MessageBuilder operating over a single, contiguous, user-supplied area in vir...
Definition: msg.hpp:68
@ S_INVALID_ARGUMENT
User called an API with 1 or more arguments against the API spec.
@ S_SERIALIZE_FAILED_SPLIT_ENCODING_TOO_BIG
The combination of serialized structured-message size and large individual leaves (list/blob/string) ...
@ S_SERIALIZE_FAILED_SPLIT_SEGS_TOO_MANY
The serialized structured message consists of so many large individual leaves (list/blob/string),...
Builder< ipc::shm::classic::Pool_arena > Builder
Convenience alias: transport::struc::shm::Builder that works with boost.ipc.shm pools from ipc::shm::...
Definition: classic_fwd.hpp:36
Sub-module of Flow-IPC module ipc::transport providing transmission of structured messages specifical...
Definition: channel.cpp:31
std::vector< Split_segment > Split_segments
A simple data structure describing the segments (if any) in a Segment_bufs (or similar) structure whi...
Definition: struc_fwd.hpp:185
boost::uuids::uuid Session_token
A type used by struc::Channel for internal safety/security/auth needs.
Definition: struc_fwd.hpp:121
bool load_mdt(Capped_sz_capnp_message_builder *builder, schema::detail::StructuredMessage::InternalMessageBody::Builder *internal_msg_builder_or_null, Error_code *err_code, const Session_token &session_token, msg_id_t originating_msg_id_or_none, msg_id_t id_or_none, size_t n_serialization_segments_or_none, const Split_segments *split_segs)
Given user- or internal-message-describing metadata values as arguments, loads (via the provided capn...
Definition: msg_impl.cpp:43
uint64_t msg_id_t
Message ID uniquely identifying outgoing message (Msg_out, among all other Msg_outs),...
Definition: struc_fwd.hpp:215
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313