blob: 78f9eb28e0ea9f4dc830fdbf43cd114ae7c55a4f [file] [log] [blame]
John Park398c74a2018-10-20 21:17:39 -07001#include "aos/configuration.h"
Brian Silverman66f079a2013-08-26 16:24:30 -07002
Brian Silverman66f079a2013-08-26 16:24:30 -07003#include <arpa/inet.h>
4#include <ifaddrs.h>
Austin Schuhf1fff282020-03-28 16:57:32 -07005#include <netinet/in.h>
Austin Schuhf1fff282020-03-28 16:57:32 -07006#include <sys/types.h>
Brian Silverman66f079a2013-08-26 16:24:30 -07007#include <unistd.h>
Austin Schuhe84c3ed2019-12-14 15:29:48 -08008
Tyler Chatowbf0609c2021-07-31 16:13:27 -07009#include <cstdlib>
10#include <cstring>
Austin Schuh68d98592020-11-01 23:22:57 -080011#include <map>
Austin Schuhe84c3ed2019-12-14 15:29:48 -080012#include <set>
Austin Schuhef38cd22021-07-21 15:24:23 -070013#include <string>
James Kuszmaul3ae42262019-11-08 12:33:41 -080014#include <string_view>
Austin Schuhef38cd22021-07-21 15:24:23 -070015#include <vector>
Brian Silverman66f079a2013-08-26 16:24:30 -070016
Austin Schuhcb108412019-10-13 16:09:54 -070017#include "absl/container/btree_set.h"
Austin Schuha81454b2020-05-12 19:58:36 -070018#include "absl/strings/str_cat.h"
Austin Schuhef38cd22021-07-21 15:24:23 -070019#include "absl/strings/str_join.h"
20#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070021#include "gflags/gflags.h"
22#include "glog/logging.h"
23
Austin Schuhcb108412019-10-13 16:09:54 -070024#include "aos/configuration_generated.h"
25#include "aos/flatbuffer_merge.h"
Austin Schuh83cbb1e2023-06-23 12:59:02 -070026#include "aos/ipc_lib/index.h"
Austin Schuhcb108412019-10-13 16:09:54 -070027#include "aos/json_to_flatbuffer.h"
Austin Schuh217a9782019-12-21 23:02:50 -080028#include "aos/network/team_number.h"
Austin Schuhcb108412019-10-13 16:09:54 -070029#include "aos/unique_malloc_ptr.h"
30#include "aos/util/file.h"
Austin Schuh217a9782019-12-21 23:02:50 -080031
Austin Schuh83cbb1e2023-06-23 12:59:02 -070032DEFINE_uint32(max_queue_size_override, 0,
33 "If nonzero, this is the max number of elements in a queue to "
34 "enforce. If zero, use the number that the processor that this "
35 "application is compiled for can support. This is mostly useful "
36 "for config validation, and shouldn't be touched.");
37
Brian Silverman66f079a2013-08-26 16:24:30 -070038namespace aos {
Austin Schuh15182322020-10-10 15:25:21 -070039namespace {
Austin Schuhfb37c612022-08-11 15:24:51 -070040namespace chrono = std::chrono;
41
Austin Schuh15182322020-10-10 15:25:21 -070042bool EndsWith(std::string_view str, std::string_view end) {
43 if (str.size() < end.size()) {
44 return false;
45 }
46 if (str.substr(str.size() - end.size(), end.size()) != end) {
47 return false;
48 }
49 return true;
50}
51
52std::string MaybeReplaceExtension(std::string_view filename,
53 std::string_view extension,
54 std::string_view replacement) {
55 if (!EndsWith(filename, extension)) {
56 return std::string(filename);
57 }
58 filename.remove_suffix(extension.size());
59 return absl::StrCat(filename, replacement);
60}
61
62FlatbufferDetachedBuffer<Configuration> ReadConfigFile(std::string_view path,
63 bool binary) {
64 if (binary) {
65 FlatbufferVector<Configuration> config =
66 FileToFlatbuffer<Configuration>(path);
67 return CopySpanAsDetachedBuffer(config.span());
68 }
69
70 flatbuffers::DetachedBuffer buffer = JsonToFlatbuffer(
71 util::ReadFileToStringOrDie(path), ConfigurationTypeTable());
72
Austin Schuh84a039a2021-11-03 16:50:34 -070073 CHECK_GT(buffer.size(), 0u) << ": Failed to parse JSON file: " << path;
Austin Schuh15182322020-10-10 15:25:21 -070074
75 return FlatbufferDetachedBuffer<Configuration>(std::move(buffer));
76}
77
78} // namespace
Austin Schuh40485ed2019-10-26 21:51:44 -070079// Define the compare and equal operators for Channel and Application so we can
Austin Schuhcb108412019-10-13 16:09:54 -070080// insert them in the btree below.
Austin Schuh40485ed2019-10-26 21:51:44 -070081bool operator<(const FlatbufferDetachedBuffer<Channel> &lhs,
82 const FlatbufferDetachedBuffer<Channel> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -070083 int name_compare = lhs.message().name()->string_view().compare(
84 rhs.message().name()->string_view());
85 if (name_compare == 0) {
86 return lhs.message().type()->string_view() <
87 rhs.message().type()->string_view();
88 } else if (name_compare < 0) {
89 return true;
90 } else {
91 return false;
92 }
93}
94
Austin Schuh40485ed2019-10-26 21:51:44 -070095bool operator==(const FlatbufferDetachedBuffer<Channel> &lhs,
96 const FlatbufferDetachedBuffer<Channel> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -070097 return lhs.message().name()->string_view() ==
98 rhs.message().name()->string_view() &&
99 lhs.message().type()->string_view() ==
100 rhs.message().type()->string_view();
101}
102
Austin Schuha7996eb2021-10-11 19:03:24 -0700103bool operator<(const FlatbufferDetachedBuffer<Connection> &lhs,
104 const FlatbufferDetachedBuffer<Connection> &rhs) {
105 return lhs.message().name()->string_view() <
106 rhs.message().name()->string_view();
107}
108
109bool operator==(const FlatbufferDetachedBuffer<Connection> &lhs,
110 const FlatbufferDetachedBuffer<Connection> &rhs) {
111 return lhs.message().name()->string_view() ==
112 rhs.message().name()->string_view();
113}
114
Austin Schuh40485ed2019-10-26 21:51:44 -0700115bool operator==(const FlatbufferDetachedBuffer<Application> &lhs,
116 const FlatbufferDetachedBuffer<Application> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -0700117 return lhs.message().name()->string_view() ==
118 rhs.message().name()->string_view();
119}
120
Austin Schuh40485ed2019-10-26 21:51:44 -0700121bool operator<(const FlatbufferDetachedBuffer<Application> &lhs,
122 const FlatbufferDetachedBuffer<Application> &rhs) {
Austin Schuhcb108412019-10-13 16:09:54 -0700123 return lhs.message().name()->string_view() <
124 rhs.message().name()->string_view();
125}
126
Austin Schuh217a9782019-12-21 23:02:50 -0800127bool operator==(const FlatbufferDetachedBuffer<Node> &lhs,
128 const FlatbufferDetachedBuffer<Node> &rhs) {
129 return lhs.message().name()->string_view() ==
130 rhs.message().name()->string_view();
131}
132
133bool operator<(const FlatbufferDetachedBuffer<Node> &lhs,
134 const FlatbufferDetachedBuffer<Node> &rhs) {
135 return lhs.message().name()->string_view() <
136 rhs.message().name()->string_view();
137}
138
Brian Silverman66f079a2013-08-26 16:24:30 -0700139namespace configuration {
140namespace {
141
Austin Schuhcb108412019-10-13 16:09:54 -0700142// Extracts the folder part of a path. Returns ./ if there is no path.
Austin Schuhf1fff282020-03-28 16:57:32 -0700143std::string_view ExtractFolder(const std::string_view filename) {
Austin Schuhcb108412019-10-13 16:09:54 -0700144 auto last_slash_pos = filename.find_last_of("/\\");
145
James Kuszmaul3ae42262019-11-08 12:33:41 -0800146 return last_slash_pos == std::string_view::npos
147 ? std::string_view("./")
Austin Schuhcb108412019-10-13 16:09:54 -0700148 : filename.substr(0, last_slash_pos + 1);
149}
150
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700151std::string AbsolutePath(const std::string_view filename) {
152 // Uses an std::string so that we know the input will be null-terminated.
153 const std::string terminated_file(filename);
154 char buffer[PATH_MAX];
155 PCHECK(NULL != realpath(terminated_file.c_str(), buffer));
156 return buffer;
157}
158
Austin Schuhef38cd22021-07-21 15:24:23 -0700159std::string RemoveDotDots(const std::string_view filename) {
160 std::vector<std::string> split = absl::StrSplit(filename, '/');
161 auto iterator = split.begin();
162 while (iterator != split.end()) {
163 if (iterator->empty()) {
164 iterator = split.erase(iterator);
165 } else if (*iterator == ".") {
166 iterator = split.erase(iterator);
167 } else if (*iterator == "..") {
168 CHECK(iterator != split.begin())
169 << ": Import path may not start with ..: " << filename;
170 auto previous = iterator;
171 --previous;
172 split.erase(iterator);
173 iterator = split.erase(previous);
174 } else {
175 ++iterator;
176 }
177 }
178 return absl::StrJoin(split, "/");
179}
180
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700181std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700182 const std::string_view path, absl::btree_set<std::string> *visited_paths,
183 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh15182322020-10-10 15:25:21 -0700184 std::string binary_path = MaybeReplaceExtension(path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700185 VLOG(1) << "Looking up: " << path << ", starting with: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700186 bool binary_path_exists = util::PathExists(binary_path);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700187 std::string raw_path(path);
Austin Schuh15182322020-10-10 15:25:21 -0700188 // For each .json file, look and see if we can find a .bfbs file next to it
189 // with the same base name. If we can, assume it is the same and use it
190 // instead. It is much faster to load .bfbs files than .json files.
191 if (!binary_path_exists && !util::PathExists(raw_path)) {
192 const bool path_is_absolute = raw_path.size() > 0 && raw_path[0] == '/';
Brian Silvermand0588192022-07-26 00:35:22 -0700193 if (path_is_absolute) {
194 // Nowhere else to look up an absolute path, so fail now. Note that we
195 // always have at least one extra import path based on /proc/self/exe, so
196 // warning about those paths existing isn't helpful.
197 LOG(ERROR) << ": Failed to find file " << path << ".";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700198 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700199 }
200
201 bool found_path = false;
202 for (const auto &import_path : extra_import_paths) {
Austin Schuhef38cd22021-07-21 15:24:23 -0700203 raw_path = std::string(import_path) + "/" + RemoveDotDots(path);
Austin Schuh15182322020-10-10 15:25:21 -0700204 binary_path = MaybeReplaceExtension(raw_path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700205 VLOG(1) << "Checking: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700206 binary_path_exists = util::PathExists(binary_path);
207 if (binary_path_exists) {
208 found_path = true;
209 break;
210 }
Austin Schuhef38cd22021-07-21 15:24:23 -0700211 VLOG(1) << "Checking: " << raw_path;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700212 if (util::PathExists(raw_path)) {
213 found_path = true;
214 break;
215 }
216 }
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700217 if (!found_path) {
218 LOG(ERROR) << ": Failed to find file " << path << ".";
219 return std::nullopt;
220 }
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700221 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700222
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700223 std::optional<FlatbufferDetachedBuffer<Configuration>> config =
224 ReadConfigFile(binary_path_exists ? binary_path : raw_path,
225 binary_path_exists);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700226
Austin Schuhcb108412019-10-13 16:09:54 -0700227 // Depth first. Take the following example:
228 //
229 // config1.json:
230 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700231 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700232 // {
233 // "name": "/foo",
234 // "type": ".aos.bar",
235 // "max_size": 5
236 // }
237 // ],
238 // "imports": [
239 // "config2.json",
240 // ]
241 // }
242 //
243 // config2.json:
244 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700245 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700246 // {
247 // "name": "/foo",
248 // "type": ".aos.bar",
249 // "max_size": 7
250 // }
251 // ],
252 // }
253 //
254 // We want the main config (config1.json) to be able to override the imported
255 // config. That means that it needs to be merged into the imported configs,
256 // not the other way around.
257
Austin Schuh15182322020-10-10 15:25:21 -0700258 const std::string absolute_path =
259 AbsolutePath(binary_path_exists ? binary_path : raw_path);
260 // Track that we have seen this file before recursing. Track the path we
261 // actually loaded (which should be consistent if imported twice).
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700262 if (!visited_paths->insert(absolute_path).second) {
263 for (const auto &visited_path : *visited_paths) {
264 LOG(INFO) << "Already visited: " << visited_path;
265 }
266 LOG(FATAL)
267 << "Already imported " << path << " (i.e. " << absolute_path
268 << "). See above for the files that have already been processed.";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700269 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700270 }
Austin Schuhcb108412019-10-13 16:09:54 -0700271
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700272 if (config->message().has_imports()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700273 // Capture the imports.
274 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *v =
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700275 config->message().imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700276
277 // And then wipe them. This gets GCed when we merge later.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700278 config->mutable_message()->clear_imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700279
280 // Start with an empty configuration to merge into.
Austin Schuh40485ed2019-10-26 21:51:44 -0700281 FlatbufferDetachedBuffer<Configuration> merged_config =
282 FlatbufferDetachedBuffer<Configuration>::Empty();
Austin Schuhcb108412019-10-13 16:09:54 -0700283
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700284 const std::string path_folder(ExtractFolder(path));
Austin Schuhcb108412019-10-13 16:09:54 -0700285 for (const flatbuffers::String *str : *v) {
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700286 const std::string included_config =
287 path_folder + "/" + std::string(str->string_view());
Austin Schuhcb108412019-10-13 16:09:54 -0700288
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700289 const auto optional_config =
290 MaybeReadConfig(included_config, visited_paths, extra_import_paths);
291 if (!optional_config.has_value()) {
292 return std::nullopt;
293 }
Austin Schuhcb108412019-10-13 16:09:54 -0700294 // And them merge everything in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700295 merged_config = MergeFlatBuffers(merged_config, *optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -0700296 }
297
298 // Finally, merge this file in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700299 config = MergeFlatBuffers(merged_config, *config);
Austin Schuhcb108412019-10-13 16:09:54 -0700300 }
301 return config;
302}
303
Alex Perrycb7da4b2019-08-28 19:35:56 -0700304// Compares (c < p) a channel, and a name, type tuple.
305bool CompareChannels(const Channel *c,
James Kuszmaul3ae42262019-11-08 12:33:41 -0800306 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700307 int name_compare = c->name()->string_view().compare(p.first);
308 if (name_compare == 0) {
309 return c->type()->string_view() < p.second;
310 } else if (name_compare < 0) {
311 return true;
312 } else {
313 return false;
314 }
315};
316
317// Compares for equality (c == p) a channel, and a name, type tuple.
318bool EqualsChannels(const Channel *c,
Austin Schuhf1fff282020-03-28 16:57:32 -0700319 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700320 return c->name()->string_view() == p.first &&
321 c->type()->string_view() == p.second;
322}
323
324// Compares (c < p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800325bool CompareApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700326 return a->name()->string_view() < name;
327};
328
329// Compares for equality (c == p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800330bool EqualsApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700331 return a->name()->string_view() == name;
332}
333
Austin Schuh15182322020-10-10 15:25:21 -0700334void ValidateConfiguration(const Flatbuffer<Configuration> &config) {
335 // No imports should be left.
336 CHECK(!config.message().has_imports());
337
338 // Check that if there is a node list, all the source nodes are filled out and
339 // valid, and all the destination nodes are valid (and not the source). This
340 // is a basic consistency check.
341 if (config.message().has_channels()) {
342 const Channel *last_channel = nullptr;
343 for (const Channel *c : *config.message().channels()) {
344 CHECK(c->has_name());
345 CHECK(c->has_type());
346 if (c->name()->string_view().back() == '/') {
347 LOG(FATAL) << "Channel names can't end with '/'";
348 }
Austin Schuh47e382e2023-05-28 11:20:56 -0700349 if (c->name()->string_view().front() != '/') {
350 LOG(FATAL) << "Channel names must start with '/'";
351 }
Austin Schuh15182322020-10-10 15:25:21 -0700352 if (c->name()->string_view().find("//") != std::string_view::npos) {
353 LOG(FATAL) << ": Invalid channel name " << c->name()->string_view()
354 << ", can't use //.";
355 }
356 for (const char data : c->name()->string_view()) {
357 if (data >= '0' && data <= '9') {
358 continue;
359 }
360 if (data >= 'a' && data <= 'z') {
361 continue;
362 }
363 if (data >= 'A' && data <= 'Z') {
364 continue;
365 }
366 if (data == '-' || data == '_' || data == '/') {
367 continue;
368 }
369 LOG(FATAL) << "Invalid channel name " << c->name()->string_view()
370 << ", can only use [-a-zA-Z0-9_/]";
371 }
372
Austin Schuhfb37c612022-08-11 15:24:51 -0700373 CHECK_LT(QueueSize(&config.message(), c) + QueueScratchBufferSize(c),
Austin Schuh83cbb1e2023-06-23 12:59:02 -0700374 FLAGS_max_queue_size_override != 0
375 ? FLAGS_max_queue_size_override
376 : std::numeric_limits<
377 ipc_lib::QueueIndex::PackedIndexType>::max())
Austin Schuhfb37c612022-08-11 15:24:51 -0700378 << ": More messages/second configured than the queue can hold on "
379 << CleanedChannelToString(c) << ", " << c->frequency() << "hz for "
Austin Schuhfff9c3a2023-06-16 18:48:23 -0700380 << ChannelStorageDuration(&config.message(), c).count() << "ns";
Austin Schuhfb37c612022-08-11 15:24:51 -0700381
Austin Schuha156fb22021-10-11 19:23:21 -0700382 if (c->has_logger_nodes()) {
383 // Confirm that we don't have duplicate logger nodes.
384 absl::btree_set<std::string_view> logger_nodes;
385 for (const flatbuffers::String *s : *c->logger_nodes()) {
386 logger_nodes.insert(s->string_view());
387 }
388 CHECK_EQ(static_cast<size_t>(logger_nodes.size()),
389 c->logger_nodes()->size())
390 << ": Found duplicate logger_nodes in "
391 << CleanedChannelToString(c);
392 }
393
394 if (c->has_destination_nodes()) {
395 // Confirm that we don't have duplicate timestamp logger nodes.
Austin Schuh5e95bd62021-10-11 18:40:22 -0700396 for (const Connection *d : *c->destination_nodes()) {
Austin Schuha156fb22021-10-11 19:23:21 -0700397 if (d->has_timestamp_logger_nodes()) {
398 absl::btree_set<std::string_view> timestamp_logger_nodes;
399 for (const flatbuffers::String *s : *d->timestamp_logger_nodes()) {
400 timestamp_logger_nodes.insert(s->string_view());
401 }
402 CHECK_EQ(static_cast<size_t>(timestamp_logger_nodes.size()),
403 d->timestamp_logger_nodes()->size())
404 << ": Found duplicate timestamp_logger_nodes in "
405 << CleanedChannelToString(c);
406 }
407 }
408
409 // There is no good use case today for logging timestamps but not the
410 // corresponding data. Instead of plumbing through all of this on the
411 // reader side, let'd just disallow it for now.
412 if (c->logger() == LoggerConfig::NOT_LOGGED) {
413 for (const Connection *d : *c->destination_nodes()) {
414 CHECK(d->timestamp_logger() == LoggerConfig::NOT_LOGGED)
415 << ": Logging timestamps without data is not supported. If "
416 "you have a good use case, let's talk. "
417 << CleanedChannelToString(c);
418 }
Austin Schuh5e95bd62021-10-11 18:40:22 -0700419 }
420 }
421
Austin Schuh15182322020-10-10 15:25:21 -0700422 // Make sure everything is sorted while we are here... If this fails,
423 // there will be a bunch of weird errors.
424 if (last_channel != nullptr) {
425 CHECK(CompareChannels(
426 last_channel,
427 std::make_pair(c->name()->string_view(), c->type()->string_view())))
428 << ": Channels not sorted!";
429 }
430 last_channel = c;
431 }
432 }
433
434 if (config.message().has_nodes() && config.message().has_channels()) {
435 for (const Channel *c : *config.message().channels()) {
436 CHECK(c->has_source_node()) << ": Channel " << FlatbufferToJson(c)
437 << " is missing \"source_node\"";
438 CHECK(GetNode(&config.message(), c->source_node()->string_view()) !=
439 nullptr)
440 << ": Channel " << FlatbufferToJson(c)
441 << " has an unknown \"source_node\"";
442
443 if (c->has_destination_nodes()) {
444 for (const Connection *connection : *c->destination_nodes()) {
445 CHECK(connection->has_name());
446 CHECK(GetNode(&config.message(), connection->name()->string_view()) !=
447 nullptr)
448 << ": Channel " << FlatbufferToJson(c)
449 << " has an unknown \"destination_nodes\" "
450 << connection->name()->string_view();
451
452 switch (connection->timestamp_logger()) {
453 case LoggerConfig::LOCAL_LOGGER:
454 case LoggerConfig::NOT_LOGGED:
Austin Schuhb98d02d2022-08-16 13:27:25 -0700455 CHECK(!connection->has_timestamp_logger_nodes())
456 << ": " << CleanedChannelToString(c);
Austin Schuh15182322020-10-10 15:25:21 -0700457 break;
458 case LoggerConfig::REMOTE_LOGGER:
459 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
460 CHECK(connection->has_timestamp_logger_nodes());
461 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
462 for (const flatbuffers::String *timestamp_logger_node :
463 *connection->timestamp_logger_nodes()) {
464 CHECK(GetNode(&config.message(),
465 timestamp_logger_node->string_view()) != nullptr)
466 << ": Channel " << FlatbufferToJson(c)
467 << " has an unknown \"timestamp_logger_node\""
468 << connection->name()->string_view();
469 }
470 break;
471 }
472
473 CHECK_NE(connection->name()->string_view(),
474 c->source_node()->string_view())
475 << ": Channel " << FlatbufferToJson(c)
476 << " is forwarding data to itself";
477 }
478 }
479 }
480 }
481}
482
James Kuszmaulc8503f32022-06-25 16:17:12 -0700483void HandleReverseMaps(
484 const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
485 std::string_view type, const Node *node, std::set<std::string> *names) {
486 for (const Map *map : *maps) {
487 CHECK_NOTNULL(map);
488 const Channel *const match = CHECK_NOTNULL(map->match());
489 const Channel *const rename = CHECK_NOTNULL(map->rename());
490
491 // Handle type specific maps.
492 const flatbuffers::String *const match_type_string = match->type();
493 if (match_type_string != nullptr &&
494 match_type_string->string_view() != type) {
495 continue;
496 }
497
498 // Now handle node specific maps.
499 const flatbuffers::String *const match_source_node_string =
500 match->source_node();
501 if (node != nullptr && match_source_node_string != nullptr &&
502 match_source_node_string->string_view() !=
503 node->name()->string_view()) {
504 continue;
505 }
506
507 const flatbuffers::String *const match_name_string = match->name();
508 const flatbuffers::String *const rename_name_string = rename->name();
509 if (match_name_string == nullptr || rename_name_string == nullptr) {
510 continue;
511 }
512
513 const std::string rename_name = rename_name_string->str();
514 const std::string_view match_name = match_name_string->string_view();
515
516 std::set<std::string> possible_renames;
517
518 // Check if the current name(s) could have been reached using the provided
519 // rename.
520 if (match_name.back() == '*') {
521 for (const std::string &option : *names) {
522 if (option.substr(0, rename_name.size()) == rename_name) {
523 possible_renames.insert(
524 absl::StrCat(match_name.substr(0, match_name.size() - 1),
525 option.substr(rename_name.size())));
526 }
527 }
528 names->insert(possible_renames.begin(), possible_renames.end());
529 } else if (names->count(rename_name) != 0) {
530 names->insert(std::string(match_name));
531 }
532 }
533}
534
Alex Perrycb7da4b2019-08-28 19:35:56 -0700535} // namespace
536
Austin Schuh006a9f52021-04-07 16:24:18 -0700537// Maps name for the provided maps. Modifies name.
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800538//
539// This is called many times during startup, and it dereferences a lot of
540// pointers. These combine to make it a performance hotspot during many tests
541// under msan, so there is some optimizing around caching intermediates instead
542// of dereferencing the pointer multiple times.
James Kuszmaulc8503f32022-06-25 16:17:12 -0700543//
544// Deliberately not in an anonymous namespace so that the log-reading code can
545// reference it.
Austin Schuh006a9f52021-04-07 16:24:18 -0700546void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
547 std::string *name, std::string_view type, const Node *node) {
548 // For the same reason we merge configs in reverse order, we want to process
549 // maps in reverse order. That lets the outer config overwrite channels from
550 // the inner configs.
551 for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800552 const Channel *const match = i->match();
553 if (!match) {
Austin Schuh006a9f52021-04-07 16:24:18 -0700554 continue;
555 }
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800556 const flatbuffers::String *const match_name_string = match->name();
557 if (!match_name_string) {
558 continue;
559 }
560 const Channel *const rename = i->rename();
561 if (!rename) {
562 continue;
563 }
564 const flatbuffers::String *const rename_name_string = rename->name();
565 if (!rename_name_string) {
Austin Schuh006a9f52021-04-07 16:24:18 -0700566 continue;
567 }
568
569 // Handle normal maps (now that we know that match and rename are filled
570 // out).
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800571 const std::string_view match_name = match_name_string->string_view();
Austin Schuh006a9f52021-04-07 16:24:18 -0700572 if (match_name != *name) {
573 if (match_name.back() == '*' &&
574 std::string_view(*name).substr(
575 0, std::min(name->size(), match_name.size() - 1)) ==
576 match_name.substr(0, match_name.size() - 1)) {
577 CHECK_EQ(match_name.find('*'), match_name.size() - 1);
578 } else {
579 continue;
580 }
581 }
582
583 // Handle type specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800584 const flatbuffers::String *const match_type_string = match->type();
585 if (match_type_string && match_type_string->string_view() != type) {
Austin Schuh006a9f52021-04-07 16:24:18 -0700586 continue;
587 }
588
589 // Now handle node specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800590 const flatbuffers::String *const match_source_node_string =
591 match->source_node();
592 if (node && match_source_node_string &&
593 match_source_node_string->string_view() !=
Austin Schuh006a9f52021-04-07 16:24:18 -0700594 node->name()->string_view()) {
595 continue;
596 }
597
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800598 std::string new_name(rename_name_string->string_view());
Austin Schuh006a9f52021-04-07 16:24:18 -0700599 if (match_name.back() == '*') {
600 new_name += std::string(name->substr(match_name.size() - 1));
601 }
602 VLOG(1) << "Renamed \"" << *name << "\" to \"" << new_name << "\"";
603 *name = std::move(new_name);
604 }
605}
606
James Kuszmaulc8503f32022-06-25 16:17:12 -0700607std::set<std::string> GetChannelAliases(const Configuration *config,
608 std::string_view name,
609 std::string_view type,
610 const std::string_view application_name,
611 const Node *node) {
612 std::set<std::string> names{std::string(name)};
613 if (config->has_maps()) {
614 HandleReverseMaps(config->maps(), type, node, &names);
615 }
616 {
617 const Application *application =
618 GetApplication(config, node, application_name);
619 if (application != nullptr && application->has_maps()) {
620 HandleReverseMaps(application->maps(), type, node, &names);
621 }
622 }
623 return names;
624}
625
Austin Schuh40485ed2019-10-26 21:51:44 -0700626FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
Austin Schuhcb108412019-10-13 16:09:54 -0700627 const Flatbuffer<Configuration> &config) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700628 // auto_merge_config will contain all the fields of the Configuration that are
629 // to be passed through unmodified to the result of MergeConfiguration().
630 // In the processing below, we mutate auto_merge_config to remove any fields
631 // which we do need to alter (hence why we can't use the input config
632 // directly), and then merge auto_merge_config back in at the end.
633 aos::FlatbufferDetachedBuffer<aos::Configuration> auto_merge_config =
Austin Schuha4fc60f2020-11-01 23:06:47 -0800634 aos::RecursiveCopyFlatBuffer(&config.message());
James Kuszmaul3c998592020-07-27 21:04:47 -0700635
Austin Schuh40485ed2019-10-26 21:51:44 -0700636 // Store all the channels in a sorted set. This lets us track channels we
Austin Schuhcb108412019-10-13 16:09:54 -0700637 // have seen before and merge the updates in.
Austin Schuh40485ed2019-10-26 21:51:44 -0700638 absl::btree_set<FlatbufferDetachedBuffer<Channel>> channels;
Austin Schuhcb108412019-10-13 16:09:54 -0700639
Austin Schuh40485ed2019-10-26 21:51:44 -0700640 if (config.message().has_channels()) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700641 auto_merge_config.mutable_message()->clear_channels();
Austin Schuh40485ed2019-10-26 21:51:44 -0700642 for (const Channel *c : *config.message().channels()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700643 // Ignore malformed entries.
Austin Schuh40485ed2019-10-26 21:51:44 -0700644 if (!c->has_name()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700645 continue;
646 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700647 if (!c->has_type()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700648 continue;
649 }
650
Brian Silverman77162972020-08-12 19:52:40 -0700651 CHECK_EQ(c->read_method() == ReadMethod::PIN, c->num_readers() != 0)
652 << ": num_readers may be set if and only if read_method is PIN,"
653 " if you want 0 readers do not set PIN: "
654 << CleanedChannelToString(c);
655
Austin Schuh40485ed2019-10-26 21:51:44 -0700656 // Attempt to insert the channel.
Austin Schuha4fc60f2020-11-01 23:06:47 -0800657 auto result = channels.insert(RecursiveCopyFlatBuffer(c));
Austin Schuhcb108412019-10-13 16:09:54 -0700658 if (!result.second) {
659 // Already there, so merge the new table into the original.
Austin Schuh4a5f5d22021-10-12 15:09:35 -0700660 // Schemas merge poorly, so pick the newest one.
661 if (result.first->message().has_schema() && c->has_schema()) {
662 result.first->mutable_message()->clear_schema();
663 }
Austin Schuha7996eb2021-10-11 19:03:24 -0700664 auto merged =
Austin Schuha4fc60f2020-11-01 23:06:47 -0800665 MergeFlatBuffers(*result.first, RecursiveCopyFlatBuffer(c));
Austin Schuha7996eb2021-10-11 19:03:24 -0700666
667 if (merged.message().has_destination_nodes()) {
668 absl::btree_set<FlatbufferDetachedBuffer<Connection>> connections;
669 for (const Connection *connection :
670 *merged.message().destination_nodes()) {
671 auto connection_result =
672 connections.insert(RecursiveCopyFlatBuffer(connection));
673 if (!connection_result.second) {
674 *connection_result.first =
675 MergeFlatBuffers(*connection_result.first,
676 RecursiveCopyFlatBuffer(connection));
677 }
678 }
679 if (static_cast<size_t>(connections.size()) !=
680 merged.message().destination_nodes()->size()) {
681 merged.mutable_message()->clear_destination_nodes();
682 flatbuffers::FlatBufferBuilder fbb;
683 fbb.ForceDefaults(true);
684 std::vector<flatbuffers::Offset<Connection>> connection_offsets;
685 for (const FlatbufferDetachedBuffer<Connection> &connection :
686 connections) {
687 connection_offsets.push_back(
688 RecursiveCopyFlatBuffer(&connection.message(), &fbb));
689 }
690 flatbuffers::Offset<
691 flatbuffers::Vector<flatbuffers::Offset<Connection>>>
692 destination_nodes_offset = fbb.CreateVector(connection_offsets);
693 Channel::Builder channel_builder(fbb);
694 channel_builder.add_destination_nodes(destination_nodes_offset);
695 fbb.Finish(channel_builder.Finish());
696 FlatbufferDetachedBuffer<Channel> destinations_channel(
697 fbb.Release());
698 merged = MergeFlatBuffers(merged, destinations_channel);
699 }
700 }
701
702 *result.first = std::move(merged);
Austin Schuhcb108412019-10-13 16:09:54 -0700703 }
704 }
705 }
706
707 // Now repeat this for the application list.
Austin Schuh40485ed2019-10-26 21:51:44 -0700708 absl::btree_set<FlatbufferDetachedBuffer<Application>> applications;
Austin Schuhcb108412019-10-13 16:09:54 -0700709 if (config.message().has_applications()) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700710 auto_merge_config.mutable_message()->clear_applications();
Austin Schuhcb108412019-10-13 16:09:54 -0700711 for (const Application *a : *config.message().applications()) {
712 if (!a->has_name()) {
713 continue;
714 }
715
Austin Schuha4fc60f2020-11-01 23:06:47 -0800716 auto result = applications.insert(RecursiveCopyFlatBuffer(a));
Austin Schuhcb108412019-10-13 16:09:54 -0700717 if (!result.second) {
Austin Schuhf81c2522021-12-08 12:03:30 -0800718 if (a->has_args()) {
719 result.first->mutable_message()->clear_args();
720 }
Austin Schuha4fc60f2020-11-01 23:06:47 -0800721 *result.first =
722 MergeFlatBuffers(*result.first, RecursiveCopyFlatBuffer(a));
Austin Schuhcb108412019-10-13 16:09:54 -0700723 }
724 }
725 }
726
Austin Schuh217a9782019-12-21 23:02:50 -0800727 // Now repeat this for the node list.
728 absl::btree_set<FlatbufferDetachedBuffer<Node>> nodes;
729 if (config.message().has_nodes()) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700730 auto_merge_config.mutable_message()->clear_nodes();
Austin Schuh217a9782019-12-21 23:02:50 -0800731 for (const Node *n : *config.message().nodes()) {
732 if (!n->has_name()) {
733 continue;
734 }
735
Austin Schuha4fc60f2020-11-01 23:06:47 -0800736 auto result = nodes.insert(RecursiveCopyFlatBuffer(n));
Austin Schuh217a9782019-12-21 23:02:50 -0800737 if (!result.second) {
Austin Schuha4fc60f2020-11-01 23:06:47 -0800738 *result.first =
739 MergeFlatBuffers(*result.first, RecursiveCopyFlatBuffer(n));
Austin Schuh217a9782019-12-21 23:02:50 -0800740 }
741 }
742 }
743
Austin Schuhcb108412019-10-13 16:09:54 -0700744 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -0800745 fbb.ForceDefaults(true);
Austin Schuhcb108412019-10-13 16:09:54 -0700746
747 // Start by building the vectors. They need to come before the final table.
Austin Schuh40485ed2019-10-26 21:51:44 -0700748 // Channels
749 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
750 channels_offset;
Austin Schuhcb108412019-10-13 16:09:54 -0700751 {
Austin Schuh40485ed2019-10-26 21:51:44 -0700752 ::std::vector<flatbuffers::Offset<Channel>> channel_offsets;
753 for (const FlatbufferDetachedBuffer<Channel> &c : channels) {
Austin Schuha4fc60f2020-11-01 23:06:47 -0800754 channel_offsets.emplace_back(
755 RecursiveCopyFlatBuffer<Channel>(&c.message(), &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -0700756 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700757 channels_offset = fbb.CreateVector(channel_offsets);
Austin Schuhcb108412019-10-13 16:09:54 -0700758 }
759
760 // Applications
761 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
762 applications_offset;
763 {
764 ::std::vector<flatbuffers::Offset<Application>> applications_offsets;
Austin Schuh40485ed2019-10-26 21:51:44 -0700765 for (const FlatbufferDetachedBuffer<Application> &a : applications) {
Austin Schuhcb108412019-10-13 16:09:54 -0700766 applications_offsets.emplace_back(
Austin Schuha4fc60f2020-11-01 23:06:47 -0800767 RecursiveCopyFlatBuffer<Application>(&a.message(), &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -0700768 }
769 applications_offset = fbb.CreateVector(applications_offsets);
770 }
771
Austin Schuh217a9782019-12-21 23:02:50 -0800772 // Nodes
773 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
774 nodes_offset;
775 {
776 ::std::vector<flatbuffers::Offset<Node>> node_offsets;
777 for (const FlatbufferDetachedBuffer<Node> &n : nodes) {
Austin Schuha4fc60f2020-11-01 23:06:47 -0800778 node_offsets.emplace_back(
779 RecursiveCopyFlatBuffer<Node>(&n.message(), &fbb));
Austin Schuh217a9782019-12-21 23:02:50 -0800780 }
781 nodes_offset = fbb.CreateVector(node_offsets);
782 }
783
Austin Schuhcb108412019-10-13 16:09:54 -0700784 // And then build a Configuration with them all.
785 ConfigurationBuilder configuration_builder(fbb);
Austin Schuh40485ed2019-10-26 21:51:44 -0700786 configuration_builder.add_channels(channels_offset);
Austin Schuh217a9782019-12-21 23:02:50 -0800787 if (config.message().has_applications()) {
788 configuration_builder.add_applications(applications_offset);
789 }
790 if (config.message().has_nodes()) {
791 configuration_builder.add_nodes(nodes_offset);
792 }
Austin Schuhcb108412019-10-13 16:09:54 -0700793
794 fbb.Finish(configuration_builder.Finish());
Austin Schuh217a9782019-12-21 23:02:50 -0800795
James Kuszmaul3c998592020-07-27 21:04:47 -0700796 aos::FlatbufferDetachedBuffer<aos::Configuration> modified_config(
797 fbb.Release());
798
Austin Schuh217a9782019-12-21 23:02:50 -0800799 // Now, validate that if there is a node list, every channel has a source
800 // node.
James Kuszmaul3c998592020-07-27 21:04:47 -0700801 FlatbufferDetachedBuffer<Configuration> result =
802 MergeFlatBuffers(modified_config, auto_merge_config);
Austin Schuh217a9782019-12-21 23:02:50 -0800803
Austin Schuh15182322020-10-10 15:25:21 -0700804 ValidateConfiguration(result);
Austin Schuh217a9782019-12-21 23:02:50 -0800805
806 return result;
Austin Schuhcb108412019-10-13 16:09:54 -0700807}
808
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700809std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700810 const std::string_view path,
Austin Schuhef38cd22021-07-21 15:24:23 -0700811 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh89026f52022-02-25 14:24:04 -0800812 // Add the executable directory to the search path. That makes it so that
813 // tools can be run from any directory without hard-coding an absolute path to
814 // the config into all binaries.
815 std::vector<std::string_view> extra_import_paths_with_exe =
816 extra_import_paths;
817 char proc_self_exec_buffer[PATH_MAX + 1];
818 std::memset(proc_self_exec_buffer, 0, sizeof(proc_self_exec_buffer));
819 ssize_t s = readlink("/proc/self/exe", proc_self_exec_buffer, PATH_MAX);
820 if (s > 0) {
821 // If the readlink call fails, the worst thing that happens is that we don't
822 // automatically find the config next to the binary. VLOG to make it easier
823 // to debug.
824 std::string_view proc_self_exec(proc_self_exec_buffer);
825
826 extra_import_paths_with_exe.emplace_back(
827 proc_self_exec.substr(0, proc_self_exec.rfind("/")));
828 } else {
829 VLOG(1) << "Failed to read /proc/self/exe";
830 }
831
Austin Schuhcb108412019-10-13 16:09:54 -0700832 // We only want to read a file once. So track the visited files in a set.
833 absl::btree_set<std::string> visited_paths;
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700834 std::optional<FlatbufferDetachedBuffer<Configuration>> read_config =
835 MaybeReadConfig(path, &visited_paths, extra_import_paths_with_exe);
836
837 if (read_config == std::nullopt) {
838 return read_config;
839 }
Austin Schuh15182322020-10-10 15:25:21 -0700840
841 // If we only read one file, and it had a .bfbs extension, it has to be a
842 // fully formatted config. Do a quick verification and return it.
843 if (visited_paths.size() == 1 && EndsWith(*visited_paths.begin(), ".bfbs")) {
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700844 ValidateConfiguration(*read_config);
Austin Schuh15182322020-10-10 15:25:21 -0700845 return read_config;
846 }
847
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700848 return MergeConfiguration(*read_config);
849}
850
851FlatbufferDetachedBuffer<Configuration> ReadConfig(
852 const std::string_view path,
853 const std::vector<std::string_view> &extra_import_paths) {
854 auto optional_config = MaybeReadConfig(path, extra_import_paths);
855 CHECK(optional_config) << "Could not read config. See above errors";
856 return std::move(*optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -0700857}
858
Austin Schuh8d6cea82020-02-28 12:17:16 -0800859FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Brian Silverman24f5aa82020-06-23 16:21:28 -0700860 const Configuration *config, const Flatbuffer<Configuration> &addition) {
861 return MergeConfiguration(MergeFlatBuffers(config, &addition.message()));
862}
863
864FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Austin Schuh8d6cea82020-02-28 12:17:16 -0800865 const Configuration *config, std::string_view json) {
866 FlatbufferDetachedBuffer<Configuration> addition =
867 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
868
Brian Silverman24f5aa82020-06-23 16:21:28 -0700869 return MergeWithConfig(config, addition);
Austin Schuh8d6cea82020-02-28 12:17:16 -0800870}
871
James Kuszmaul3ae42262019-11-08 12:33:41 -0800872const Channel *GetChannel(const Configuration *config, std::string_view name,
873 std::string_view type,
Austin Schuh0de30f32020-12-06 12:44:28 -0800874 std::string_view application_name, const Node *node,
875 bool quiet) {
Brian Silverman9fcf2c72020-12-21 18:30:58 -0800876 if (!config->has_channels()) {
877 return nullptr;
878 }
879
Austin Schuhbca6cf02019-12-22 17:28:34 -0800880 const std::string_view original_name = name;
Austin Schuhf1fff282020-03-28 16:57:32 -0700881 std::string mutable_name;
Austin Schuh4c3b9702020-08-30 11:34:55 -0700882 if (node != nullptr) {
883 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
884 << "\" } on " << aos::FlatbufferToJson(node);
885 } else {
886 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
887 << "\" }";
888 }
Austin Schuhcb108412019-10-13 16:09:54 -0700889
890 // First handle application specific maps. Only do this if we have a matching
891 // application name, and it has maps.
Austin Schuhd2e2f6a2021-02-07 20:46:16 -0800892 {
893 const Application *application =
894 GetApplication(config, node, application_name);
895 if (application != nullptr && application->has_maps()) {
896 mutable_name = std::string(name);
897 HandleMaps(application->maps(), &mutable_name, type, node);
898 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -0700899 }
900 }
901
902 // Now do global maps.
Austin Schuh40485ed2019-10-26 21:51:44 -0700903 if (config->has_maps()) {
Austin Schuhf1fff282020-03-28 16:57:32 -0700904 mutable_name = std::string(name);
905 HandleMaps(config->maps(), &mutable_name, type, node);
906 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -0700907 }
908
Austin Schuhbca6cf02019-12-22 17:28:34 -0800909 if (original_name != name) {
910 VLOG(1) << "Remapped to { \"name\": \"" << name << "\", \"type\": \""
911 << type << "\" }";
912 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700913
James Kuszmaul71a81932020-12-15 21:08:01 -0800914 // Then look for the channel (note that this relies on the channels being
915 // sorted in the config).
Austin Schuh40485ed2019-10-26 21:51:44 -0700916 auto channel_iterator =
Austin Schuhf1fff282020-03-28 16:57:32 -0700917 std::lower_bound(config->channels()->cbegin(), config->channels()->cend(),
Austin Schuh40485ed2019-10-26 21:51:44 -0700918 std::make_pair(name, type), CompareChannels);
Austin Schuhcb108412019-10-13 16:09:54 -0700919
920 // Make sure we actually found it, and it matches.
Austin Schuh40485ed2019-10-26 21:51:44 -0700921 if (channel_iterator != config->channels()->cend() &&
922 EqualsChannels(*channel_iterator, std::make_pair(name, type))) {
Austin Schuhbca6cf02019-12-22 17:28:34 -0800923 if (VLOG_IS_ON(2)) {
924 VLOG(2) << "Found: " << FlatbufferToJson(*channel_iterator);
925 } else if (VLOG_IS_ON(1)) {
926 VLOG(1) << "Found: " << CleanedChannelToString(*channel_iterator);
927 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700928 return *channel_iterator;
Austin Schuhcb108412019-10-13 16:09:54 -0700929 } else {
930 VLOG(1) << "No match for { \"name\": \"" << name << "\", \"type\": \""
931 << type << "\" }";
Austin Schuh0de30f32020-12-06 12:44:28 -0800932 if (original_name != name && !quiet) {
Austin Schuh4b42b252020-10-19 11:35:20 -0700933 LOG(WARNING) << "Remapped from {\"name\": \"" << original_name
934 << "\", \"type\": \"" << type << "\"}, to {\"name\": \""
935 << name << "\", \"type\": \"" << type
936 << "\"}, but no channel by that name exists.";
937 }
Austin Schuhcb108412019-10-13 16:09:54 -0700938 return nullptr;
939 }
940}
941
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800942size_t ChannelIndex(const Configuration *configuration,
943 const Channel *channel) {
944 CHECK(configuration->channels() != nullptr) << ": No channels";
945
Brian Silvermana9698c92021-11-10 12:27:04 -0800946 const auto c = std::lower_bound(
947 configuration->channels()->cbegin(), configuration->channels()->cend(),
948 std::make_pair(channel->name()->string_view(),
949 channel->type()->string_view()),
950 CompareChannels);
951 CHECK(c != configuration->channels()->cend())
952 << ": Channel pointer not found in configuration()->channels()";
953 CHECK(*c == channel)
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800954 << ": Channel pointer not found in configuration()->channels()";
955
Brian Silvermana9698c92021-11-10 12:27:04 -0800956 return std::distance(configuration->channels()->cbegin(), c);
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800957}
958
Austin Schuhbca6cf02019-12-22 17:28:34 -0800959std::string CleanedChannelToString(const Channel *channel) {
960 FlatbufferDetachedBuffer<Channel> cleaned_channel = CopyFlatBuffer(channel);
961 cleaned_channel.mutable_message()->clear_schema();
962 return FlatbufferToJson(cleaned_channel);
963}
964
Austin Schuha81454b2020-05-12 19:58:36 -0700965std::string StrippedChannelToString(const Channel *channel) {
966 return absl::StrCat("{ \"name\": \"", channel->name()->string_view(),
967 "\", \"type\": \"", channel->type()->string_view(),
968 "\" }");
969}
970
Alex Perrycb7da4b2019-08-28 19:35:56 -0700971FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
972 const Flatbuffer<Configuration> &config,
Austin Schuh0de30f32020-12-06 12:44:28 -0800973 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700974 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -0800975 fbb.ForceDefaults(true);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700976
Austin Schuh68d98592020-11-01 23:22:57 -0800977 // Cache for holding already inserted schemas.
978 std::map<std::string_view, flatbuffers::Offset<reflection::Schema>>
979 schema_cache;
980
Austin Schuhdda6db72023-06-21 17:02:34 -0700981 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
Austin Schuh68d98592020-11-01 23:22:57 -0800982 << ": Merging logic needs to be updated when the number of channel "
983 "fields changes.";
James Kuszmaul3c998592020-07-27 21:04:47 -0700984
Alex Perrycb7da4b2019-08-28 19:35:56 -0700985 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
986 channels_offset;
987 if (config.message().has_channels()) {
988 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
989 for (const Channel *c : *config.message().channels()) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700990 // Search for a schema with a matching type.
Austin Schuh0de30f32020-12-06 12:44:28 -0800991 const aos::FlatbufferVector<reflection::Schema> *found_schema = nullptr;
992 for (const aos::FlatbufferVector<reflection::Schema> &schema : schemas) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700993 if (schema.message().root_table() != nullptr) {
994 if (schema.message().root_table()->name()->string_view() ==
995 c->type()->string_view()) {
996 found_schema = &schema;
997 }
998 }
999 }
1000
Maxwell Gumley65d06582023-03-17 09:13:50 -06001001 if (found_schema == nullptr) {
1002 std::stringstream ss;
1003 for (const aos::FlatbufferVector<reflection::Schema> &schema :
1004 schemas) {
1005 if (schema.message().root_table() == nullptr) {
1006 continue;
1007 }
1008 auto name = schema.message().root_table()->name()->string_view();
1009 ss << "\n\tname: " << name;
1010 }
1011 LOG(FATAL) << ": Failed to find schema for " << FlatbufferToJson(c)
1012 << "\n\tThe following schemas were found:\n"
1013 << ss.str();
1014 }
Alex Perrycb7da4b2019-08-28 19:35:56 -07001015
Austin Schuh68d98592020-11-01 23:22:57 -08001016 // Now copy the message manually.
1017 auto cached_schema = schema_cache.find(c->type()->string_view());
1018 flatbuffers::Offset<reflection::Schema> schema_offset;
1019 if (cached_schema != schema_cache.end()) {
1020 schema_offset = cached_schema->second;
1021 } else {
Austin Schuha4fc60f2020-11-01 23:06:47 -08001022 schema_offset = RecursiveCopyFlatBuffer<reflection::Schema>(
1023 &found_schema->message(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001024 schema_cache.emplace(c->type()->string_view(), schema_offset);
1025 }
1026
1027 flatbuffers::Offset<flatbuffers::String> name_offset =
1028 fbb.CreateSharedString(c->name()->str());
1029 flatbuffers::Offset<flatbuffers::String> type_offset =
1030 fbb.CreateSharedString(c->type()->str());
1031 flatbuffers::Offset<flatbuffers::String> source_node_offset =
Austin Schuha4fc60f2020-11-01 23:06:47 -08001032 c->has_source_node() ? fbb.CreateSharedString(c->source_node()->str())
1033 : 0;
Austin Schuh68d98592020-11-01 23:22:57 -08001034
1035 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Connection>>>
1036 destination_nodes_offset =
Austin Schuh5c255aa2020-11-05 18:32:46 -08001037 aos::RecursiveCopyVectorTable(c->destination_nodes(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001038
1039 flatbuffers::Offset<
1040 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
1041 logger_nodes_offset =
1042 aos::CopyVectorSharedString(c->logger_nodes(), &fbb);
1043
1044 Channel::Builder channel_builder(fbb);
1045 channel_builder.add_name(name_offset);
1046 channel_builder.add_type(type_offset);
1047 if (c->has_frequency()) {
1048 channel_builder.add_frequency(c->frequency());
1049 }
1050 if (c->has_max_size()) {
1051 channel_builder.add_max_size(c->max_size());
1052 }
1053 if (c->has_num_senders()) {
1054 channel_builder.add_num_senders(c->num_senders());
1055 }
1056 if (c->has_num_watchers()) {
1057 channel_builder.add_num_watchers(c->num_watchers());
1058 }
Alex Perrycb7da4b2019-08-28 19:35:56 -07001059 channel_builder.add_schema(schema_offset);
Austin Schuh68d98592020-11-01 23:22:57 -08001060 if (!source_node_offset.IsNull()) {
1061 channel_builder.add_source_node(source_node_offset);
1062 }
1063 if (!destination_nodes_offset.IsNull()) {
1064 channel_builder.add_destination_nodes(destination_nodes_offset);
1065 }
1066 if (c->has_logger()) {
1067 channel_builder.add_logger(c->logger());
1068 }
1069 if (!logger_nodes_offset.IsNull()) {
1070 channel_builder.add_logger_nodes(logger_nodes_offset);
1071 }
1072 if (c->has_read_method()) {
1073 channel_builder.add_read_method(c->read_method());
1074 }
1075 if (c->has_num_readers()) {
1076 channel_builder.add_num_readers(c->num_readers());
1077 }
Austin Schuhdda6db72023-06-21 17:02:34 -07001078 if (c->has_channel_storage_duration()) {
1079 channel_builder.add_channel_storage_duration(
1080 c->channel_storage_duration());
1081 }
Austin Schuh68d98592020-11-01 23:22:57 -08001082 channel_offsets.emplace_back(channel_builder.Finish());
Alex Perrycb7da4b2019-08-28 19:35:56 -07001083 }
1084 channels_offset = fbb.CreateVector(channel_offsets);
1085 }
1086
Austin Schuh68d98592020-11-01 23:22:57 -08001087 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
Austin Schuh5c255aa2020-11-05 18:32:46 -08001088 maps_offset =
1089 aos::RecursiveCopyVectorTable(config.message().maps(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001090
1091 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
Austin Schuh5c255aa2020-11-05 18:32:46 -08001092 nodes_offset =
1093 aos::RecursiveCopyVectorTable(config.message().nodes(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001094
1095 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
1096 applications_offset =
Austin Schuh5c255aa2020-11-05 18:32:46 -08001097 aos::RecursiveCopyVectorTable(config.message().applications(), &fbb);
Austin Schuh217a9782019-12-21 23:02:50 -08001098
1099 // Now insert everything else in unmodified.
Alex Perrycb7da4b2019-08-28 19:35:56 -07001100 ConfigurationBuilder configuration_builder(fbb);
1101 if (config.message().has_channels()) {
1102 configuration_builder.add_channels(channels_offset);
1103 }
Austin Schuh68d98592020-11-01 23:22:57 -08001104 if (!maps_offset.IsNull()) {
1105 configuration_builder.add_maps(maps_offset);
1106 }
1107 if (!nodes_offset.IsNull()) {
1108 configuration_builder.add_nodes(nodes_offset);
1109 }
1110 if (!applications_offset.IsNull()) {
1111 configuration_builder.add_applications(applications_offset);
1112 }
1113
1114 if (config.message().has_channel_storage_duration()) {
1115 configuration_builder.add_channel_storage_duration(
1116 config.message().channel_storage_duration());
1117 }
1118
1119 CHECK_EQ(Configuration::MiniReflectTypeTable()->num_elems, 6u)
1120 << ": Merging logic needs to be updated when the number of configuration "
1121 "fields changes.";
1122
Alex Perrycb7da4b2019-08-28 19:35:56 -07001123 fbb.Finish(configuration_builder.Finish());
James Kuszmaul3c998592020-07-27 21:04:47 -07001124 aos::FlatbufferDetachedBuffer<aos::Configuration> modified_config(
1125 fbb.Release());
1126
Austin Schuh68d98592020-11-01 23:22:57 -08001127 return modified_config;
Alex Perrycb7da4b2019-08-28 19:35:56 -07001128}
1129
Austin Schuh217a9782019-12-21 23:02:50 -08001130const Node *GetNodeFromHostname(const Configuration *config,
1131 std::string_view hostname) {
1132 for (const Node *node : *config->nodes()) {
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001133 if (node->has_hostname() && node->hostname()->string_view() == hostname) {
Austin Schuh217a9782019-12-21 23:02:50 -08001134 return node;
1135 }
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001136 if (node->has_hostnames()) {
1137 for (const auto &candidate : *node->hostnames()) {
1138 if (candidate->string_view() == hostname) {
1139 return node;
1140 }
1141 }
1142 }
Austin Schuh217a9782019-12-21 23:02:50 -08001143 }
1144 return nullptr;
1145}
Austin Schuhac0771c2020-01-07 18:36:30 -08001146
Austin Schuh63097262023-08-16 17:04:29 -07001147std::string_view NodeName(const Configuration *config, size_t node_index) {
1148 if (!configuration::MultiNode(config)) {
1149 return "(singlenode)";
1150 }
1151 return config->nodes()->Get(node_index)->name()->string_view();
1152}
1153
Austin Schuh217a9782019-12-21 23:02:50 -08001154const Node *GetMyNode(const Configuration *config) {
1155 const std::string hostname = (FLAGS_override_hostname.size() > 0)
1156 ? FLAGS_override_hostname
1157 : network::GetHostname();
1158 const Node *node = GetNodeFromHostname(config, hostname);
1159 if (node != nullptr) return node;
1160
1161 LOG(FATAL) << "Unknown node for host: " << hostname
1162 << ". Consider using --override_hostname if hostname detection "
1163 "is wrong.";
1164 return nullptr;
1165}
1166
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001167const Node *GetNode(const Configuration *config, const Node *node) {
1168 if (!MultiNode(config)) {
1169 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1170 return nullptr;
1171 } else {
1172 CHECK(node != nullptr);
1173 CHECK(node->has_name());
1174 return GetNode(config, node->name()->string_view());
1175 }
1176}
1177
Austin Schuh217a9782019-12-21 23:02:50 -08001178const Node *GetNode(const Configuration *config, std::string_view name) {
Alexei Strots4b1e1442023-05-01 22:11:05 -07001179 if (!MultiNode(config)) {
1180 if (name.empty()) {
1181 return nullptr;
1182 }
1183 LOG(FATAL) << ": Asking for a named node from a single node configuration.";
1184 }
Austin Schuh217a9782019-12-21 23:02:50 -08001185 for (const Node *node : *config->nodes()) {
Austin Schuhfd960622020-01-01 13:22:55 -08001186 CHECK(node->has_name()) << ": Malformed node " << FlatbufferToJson(node);
Austin Schuh217a9782019-12-21 23:02:50 -08001187 if (node->name()->string_view() == name) {
1188 return node;
1189 }
1190 }
1191 return nullptr;
1192}
1193
Austin Schuh0ca1fd32020-12-18 22:53:05 -08001194const Node *GetNode(const Configuration *config, size_t node_index) {
1195 if (!MultiNode(config)) {
1196 CHECK_EQ(node_index, 0u) << ": Invalid node in a single node world.";
1197 return nullptr;
1198 } else {
1199 CHECK_LT(node_index, config->nodes()->size());
1200 return config->nodes()->Get(node_index);
1201 }
1202}
1203
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001204const Node *GetNodeOrDie(const Configuration *config, const Node *node) {
1205 if (!MultiNode(config)) {
1206 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1207 return nullptr;
1208 } else {
1209 const Node *config_node = GetNode(config, node);
1210 if (config_node == nullptr) {
1211 LOG(FATAL) << "Couldn't find node matching " << FlatbufferToJson(node);
1212 }
1213 return config_node;
1214 }
1215}
1216
Austin Schuh8bd96322020-02-13 21:18:22 -08001217namespace {
1218int GetNodeIndexFromConfig(const Configuration *config, const Node *node) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001219 int node_index = 0;
1220 for (const Node *iterated_node : *config->nodes()) {
1221 if (iterated_node == node) {
1222 return node_index;
1223 }
1224 ++node_index;
1225 }
Austin Schuh8bd96322020-02-13 21:18:22 -08001226 return -1;
1227}
1228} // namespace
1229
Austin Schuha9df9ad2021-06-16 14:49:39 -07001230aos::FlatbufferDetachedBuffer<aos::Configuration> AddSchema(
1231 std::string_view json,
1232 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
1233 FlatbufferDetachedBuffer<Configuration> addition =
1234 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1235 return MergeConfiguration(addition, schemas);
1236}
1237
Austin Schuh8bd96322020-02-13 21:18:22 -08001238int GetNodeIndex(const Configuration *config, const Node *node) {
1239 if (!MultiNode(config)) {
1240 return 0;
1241 }
1242
1243 {
1244 int node_index = GetNodeIndexFromConfig(config, node);
1245 if (node_index != -1) {
1246 return node_index;
1247 }
1248 }
1249
1250 const Node *result = GetNode(config, node);
1251 CHECK(result != nullptr);
1252
1253 {
Austin Schuh04408fc2020-02-16 21:48:54 -08001254 int node_index = GetNodeIndexFromConfig(config, result);
Austin Schuh8bd96322020-02-13 21:18:22 -08001255 if (node_index != -1) {
1256 return node_index;
1257 }
1258 }
1259
1260 LOG(FATAL) << "Node " << FlatbufferToJson(node)
1261 << " not found in the configuration.";
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001262}
1263
Austin Schuh04408fc2020-02-16 21:48:54 -08001264int GetNodeIndex(const Configuration *config, std::string_view name) {
1265 if (!MultiNode(config)) {
1266 return 0;
1267 }
1268
1269 {
1270 int node_index = 0;
1271 for (const Node *iterated_node : *config->nodes()) {
1272 if (iterated_node->name()->string_view() == name) {
1273 return node_index;
1274 }
1275 ++node_index;
1276 }
1277 }
1278 LOG(FATAL) << "Node " << name << " not found in the configuration.";
1279}
1280
Austin Schuh681a2472020-12-31 23:55:40 -08001281size_t NodesCount(const Configuration *config) {
1282 if (!MultiNode(config)) {
1283 return 1u;
1284 }
1285
1286 return config->nodes()->size();
1287}
1288
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001289std::vector<const Node *> GetNodes(const Configuration *config) {
1290 std::vector<const Node *> nodes;
Austin Schuh8bd96322020-02-13 21:18:22 -08001291 if (MultiNode(config)) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001292 for (const Node *node : *config->nodes()) {
1293 nodes.emplace_back(node);
1294 }
1295 } else {
1296 nodes.emplace_back(nullptr);
1297 }
1298 return nodes;
1299}
1300
Austin Schuh65465332020-11-05 17:36:53 -08001301std::vector<const Node *> GetNodesWithTag(const Configuration *config,
1302 std::string_view tag) {
1303 std::vector<const Node *> nodes;
1304 if (!MultiNode(config)) {
1305 nodes.emplace_back(nullptr);
1306 } else {
1307 for (const Node *node : *config->nodes()) {
1308 if (!node->has_tags()) {
1309 continue;
1310 }
1311 bool did_found_tag = false;
1312 for (const flatbuffers::String *found_tag : *node->tags()) {
1313 if (found_tag->string_view() == tag) {
1314 did_found_tag = true;
1315 break;
1316 }
1317 }
1318 if (did_found_tag) {
1319 nodes.emplace_back(node);
1320 }
1321 }
1322 }
1323 return nodes;
1324}
1325
Brian Silverman631b6262021-11-10 12:25:08 -08001326bool NodeHasTag(const Node *node, std::string_view tag) {
1327 if (node == nullptr) {
1328 return true;
1329 }
1330
Austin Schuh97a52432022-08-17 15:02:59 -07001331 if (!node->has_tags()) {
1332 return false;
1333 }
1334
Brian Silverman631b6262021-11-10 12:25:08 -08001335 const auto *const tags = node->tags();
1336 return std::find_if(tags->begin(), tags->end(),
1337 [tag](const flatbuffers::String *candidate) {
1338 return candidate->string_view() == tag;
1339 }) != tags->end();
1340}
1341
Austin Schuhac0771c2020-01-07 18:36:30 -08001342bool MultiNode(const Configuration *config) { return config->has_nodes(); }
1343
Austin Schuh217a9782019-12-21 23:02:50 -08001344bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001345 if (node == nullptr) {
1346 return true;
1347 }
Austin Schuh3c5dae52020-10-06 18:55:18 -07001348 CHECK(channel->has_source_node()) << FlatbufferToJson(channel);
1349 CHECK(node->has_name()) << FlatbufferToJson(node);
Austin Schuh196a4452020-03-15 23:12:03 -07001350 return (CHECK_NOTNULL(channel)->source_node()->string_view() ==
1351 node->name()->string_view());
Austin Schuh217a9782019-12-21 23:02:50 -08001352}
1353
1354bool ChannelIsReadableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001355 if (node == nullptr) {
1356 return true;
1357 }
1358
Austin Schuh217a9782019-12-21 23:02:50 -08001359 if (channel->source_node()->string_view() == node->name()->string_view()) {
1360 return true;
1361 }
1362
1363 if (!channel->has_destination_nodes()) {
1364 return false;
1365 }
1366
Austin Schuh719946b2019-12-28 14:51:01 -08001367 for (const Connection *connection : *channel->destination_nodes()) {
1368 CHECK(connection->has_name());
1369 if (connection->name()->string_view() == node->name()->string_view()) {
Austin Schuh217a9782019-12-21 23:02:50 -08001370 return true;
1371 }
1372 }
1373
1374 return false;
1375}
1376
James Kuszmaul24db2d32023-05-26 11:40:12 -07001377bool ChannelIsForwardedFromNode(const Channel *channel, const Node *node) {
1378 if (node == nullptr) {
1379 return false;
1380 }
1381 return ChannelIsSendableOnNode(channel, node) &&
1382 channel->has_destination_nodes() &&
1383 channel->destination_nodes()->size() > 0u;
1384}
1385
Austin Schuh719946b2019-12-28 14:51:01 -08001386bool ChannelMessageIsLoggedOnNode(const Channel *channel, const Node *node) {
Austin Schuh48e94502021-06-18 18:35:53 -07001387 if (node == nullptr) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001388 // Single node world. If there is a local logger, then we want to use
1389 // it.
Austin Schuh48e94502021-06-18 18:35:53 -07001390 if (channel->logger() == LoggerConfig::LOCAL_LOGGER) {
1391 return true;
1392 } else if (channel->logger() == LoggerConfig::NOT_LOGGED) {
1393 return false;
1394 }
1395 LOG(FATAL) << "Unsupported logging configuration in a single node world: "
1396 << CleanedChannelToString(channel);
Austin Schuh2bb80e02021-03-20 21:46:17 -07001397 }
1398 return ChannelMessageIsLoggedOnNode(
1399 channel, CHECK_NOTNULL(node)->name()->string_view());
1400}
1401
1402bool ChannelMessageIsLoggedOnNode(const Channel *channel,
1403 std::string_view node_name) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001404 switch (channel->logger()) {
Austin Schuh719946b2019-12-28 14:51:01 -08001405 case LoggerConfig::LOCAL_LOGGER:
Austin Schuh2bb80e02021-03-20 21:46:17 -07001406 return channel->source_node()->string_view() == node_name;
Austin Schuh719946b2019-12-28 14:51:01 -08001407 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001408 CHECK(channel->has_logger_nodes())
1409 << "Missing logger nodes on " << StrippedChannelToString(channel);
1410 CHECK_GT(channel->logger_nodes()->size(), 0u)
1411 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuh719946b2019-12-28 14:51:01 -08001412
Austin Schuh2bb80e02021-03-20 21:46:17 -07001413 if (channel->source_node()->string_view() == node_name) {
Austin Schuh719946b2019-12-28 14:51:01 -08001414 return true;
1415 }
Austin Schuhda40e472020-03-28 15:15:29 -07001416
1417 [[fallthrough]];
1418 case LoggerConfig::REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001419 CHECK(channel->has_logger_nodes())
1420 << "Missing logger nodes on " << StrippedChannelToString(channel);
1421 CHECK_GT(channel->logger_nodes()->size(), 0u)
1422 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuhda40e472020-03-28 15:15:29 -07001423 for (const flatbuffers::String *logger_node : *channel->logger_nodes()) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001424 if (logger_node->string_view() == node_name) {
Austin Schuhda40e472020-03-28 15:15:29 -07001425 return true;
1426 }
Austin Schuh719946b2019-12-28 14:51:01 -08001427 }
1428
1429 return false;
1430 case LoggerConfig::NOT_LOGGED:
1431 return false;
1432 }
1433
1434 LOG(FATAL) << "Unknown logger config " << static_cast<int>(channel->logger());
1435}
1436
Austin Schuh58646e22021-08-23 23:51:46 -07001437size_t ConnectionCount(const Channel *channel) {
1438 if (!channel->has_destination_nodes()) {
1439 return 0;
1440 }
1441 return channel->destination_nodes()->size();
1442}
1443
Austin Schuh719946b2019-12-28 14:51:01 -08001444const Connection *ConnectionToNode(const Channel *channel, const Node *node) {
1445 if (!channel->has_destination_nodes()) {
1446 return nullptr;
1447 }
1448 for (const Connection *connection : *channel->destination_nodes()) {
1449 if (connection->name()->string_view() == node->name()->string_view()) {
1450 return connection;
1451 }
1452 }
1453 return nullptr;
1454}
1455
1456bool ConnectionDeliveryTimeIsLoggedOnNode(const Channel *channel,
1457 const Node *node,
1458 const Node *logger_node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001459 return ConnectionDeliveryTimeIsLoggedOnNode(ConnectionToNode(channel, node),
1460 logger_node);
Austin Schuh719946b2019-12-28 14:51:01 -08001461}
1462
1463bool ConnectionDeliveryTimeIsLoggedOnNode(const Connection *connection,
1464 const Node *node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001465 if (connection == nullptr) {
1466 return false;
1467 }
Austin Schuh719946b2019-12-28 14:51:01 -08001468 switch (connection->timestamp_logger()) {
1469 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
Austin Schuhda40e472020-03-28 15:15:29 -07001470 CHECK(connection->has_timestamp_logger_nodes());
1471 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
Austin Schuh719946b2019-12-28 14:51:01 -08001472 if (connection->name()->string_view() == node->name()->string_view()) {
1473 return true;
1474 }
1475
Austin Schuhda40e472020-03-28 15:15:29 -07001476 [[fallthrough]];
1477 case LoggerConfig::REMOTE_LOGGER:
1478 CHECK(connection->has_timestamp_logger_nodes());
1479 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1480 for (const flatbuffers::String *timestamp_logger_node :
1481 *connection->timestamp_logger_nodes()) {
1482 if (timestamp_logger_node->string_view() ==
1483 node->name()->string_view()) {
1484 return true;
1485 }
Austin Schuh719946b2019-12-28 14:51:01 -08001486 }
1487
1488 return false;
1489 case LoggerConfig::LOCAL_LOGGER:
1490 return connection->name()->string_view() == node->name()->string_view();
Austin Schuh719946b2019-12-28 14:51:01 -08001491 case LoggerConfig::NOT_LOGGED:
1492 return false;
1493 }
1494
1495 LOG(FATAL) << "Unknown logger config "
1496 << static_cast<int>(connection->timestamp_logger());
1497}
1498
Austin Schuhe84c3ed2019-12-14 15:29:48 -08001499std::vector<std::string_view> SourceNodeNames(const Configuration *config,
1500 const Node *my_node) {
1501 std::set<std::string_view> result_set;
1502
1503 for (const Channel *channel : *config->channels()) {
1504 if (channel->has_destination_nodes()) {
1505 for (const Connection *connection : *channel->destination_nodes()) {
1506 if (connection->name()->string_view() ==
1507 my_node->name()->string_view()) {
1508 result_set.insert(channel->source_node()->string_view());
1509 }
1510 }
1511 }
1512 }
1513
1514 std::vector<std::string_view> result;
1515 for (const std::string_view source : result_set) {
1516 VLOG(1) << "Found a source node of " << source;
1517 result.emplace_back(source);
1518 }
1519 return result;
1520}
1521
1522std::vector<std::string_view> DestinationNodeNames(const Configuration *config,
1523 const Node *my_node) {
1524 std::vector<std::string_view> result;
1525
1526 for (const Channel *channel : *config->channels()) {
1527 if (channel->has_source_node() && channel->source_node()->string_view() ==
1528 my_node->name()->string_view()) {
1529 if (!channel->has_destination_nodes()) continue;
1530
1531 if (channel->source_node()->string_view() !=
1532 my_node->name()->string_view()) {
1533 continue;
1534 }
1535
1536 for (const Connection *connection : *channel->destination_nodes()) {
1537 if (std::find(result.begin(), result.end(),
1538 connection->name()->string_view()) == result.end()) {
1539 result.emplace_back(connection->name()->string_view());
1540 }
1541 }
1542 }
1543 }
1544
1545 for (const std::string_view destination : result) {
1546 VLOG(1) << "Found a destination node of " << destination;
1547 }
1548 return result;
1549}
1550
Austin Schuh8d7e0bb2020-10-02 17:57:00 -07001551std::vector<const Node *> TimestampNodes(const Configuration *config,
1552 const Node *my_node) {
1553 if (!configuration::MultiNode(config)) {
1554 CHECK(my_node == nullptr);
1555 return std::vector<const Node *>{};
1556 }
1557
1558 std::set<const Node *> timestamp_logger_nodes;
1559 for (const Channel *channel : *config->channels()) {
1560 if (!configuration::ChannelIsSendableOnNode(channel, my_node)) {
1561 continue;
1562 }
1563 if (!channel->has_destination_nodes()) {
1564 continue;
1565 }
1566 for (const Connection *connection : *channel->destination_nodes()) {
1567 const Node *other_node =
1568 configuration::GetNode(config, connection->name()->string_view());
1569
1570 if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(connection,
1571 my_node)) {
1572 VLOG(1) << "Timestamps are logged from "
1573 << FlatbufferToJson(other_node);
1574 timestamp_logger_nodes.insert(other_node);
1575 }
1576 }
1577 }
1578
1579 std::vector<const Node *> result;
1580 for (const Node *node : timestamp_logger_nodes) {
1581 result.emplace_back(node);
1582 }
1583 return result;
1584}
1585
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001586bool ApplicationShouldStart(const Configuration *config, const Node *my_node,
1587 const Application *application) {
1588 if (MultiNode(config)) {
1589 // Ok, we need
1590 CHECK(application->has_nodes());
1591 CHECK(my_node != nullptr);
1592 for (const flatbuffers::String *str : *application->nodes()) {
1593 if (str->string_view() == my_node->name()->string_view()) {
1594 return true;
1595 }
1596 }
1597 return false;
1598 } else {
1599 return true;
1600 }
1601}
1602
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001603const Application *GetApplication(const Configuration *config,
1604 const Node *my_node,
1605 std::string_view application_name) {
1606 if (config->has_applications()) {
1607 auto application_iterator = std::lower_bound(
1608 config->applications()->cbegin(), config->applications()->cend(),
1609 application_name, CompareApplications);
1610 if (application_iterator != config->applications()->cend() &&
1611 EqualsApplications(*application_iterator, application_name)) {
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001612 if (ApplicationShouldStart(config, my_node, *application_iterator)) {
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001613 return *application_iterator;
1614 }
1615 }
1616 }
1617 return nullptr;
1618}
1619
Austin Schuhfc7b6a02021-07-12 21:19:07 -07001620std::vector<size_t> SourceNodeIndex(const Configuration *config) {
1621 CHECK(config->has_channels());
1622 std::vector<size_t> result;
1623 result.resize(config->channels()->size(), 0u);
1624 if (MultiNode(config)) {
1625 for (size_t i = 0; i < config->channels()->size(); ++i) {
1626 result[i] = GetNodeIndex(
1627 config, config->channels()->Get(i)->source_node()->string_view());
1628 }
1629 }
1630 return result;
1631}
1632
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001633chrono::nanoseconds ChannelStorageDuration(const Configuration *config,
1634 const Channel *channel) {
1635 CHECK(channel != nullptr);
Austin Schuhdda6db72023-06-21 17:02:34 -07001636 if (channel->has_channel_storage_duration()) {
1637 return chrono::nanoseconds(channel->channel_storage_duration());
1638 }
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001639 return chrono::nanoseconds(config->channel_storage_duration());
1640}
1641
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001642size_t QueueSize(const Configuration *config, const Channel *channel) {
Austin Schuhfb37c612022-08-11 15:24:51 -07001643 return QueueSize(channel->frequency(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001644 ChannelStorageDuration(config, channel));
Austin Schuhfb37c612022-08-11 15:24:51 -07001645}
1646
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001647size_t QueueSize(size_t frequency,
1648 chrono::nanoseconds channel_storage_duration) {
Austin Schuhfb37c612022-08-11 15:24:51 -07001649 // Use integer arithmetic and round up at all cost.
1650 return static_cast<int>(
Philipp Schrader0434f902023-09-06 08:41:32 -07001651 (999'999'999 +
1652 static_cast<int64_t>(frequency) *
1653 static_cast<int64_t>(channel_storage_duration.count())) /
1654 static_cast<int64_t>(1'000'000'000));
Austin Schuhfb37c612022-08-11 15:24:51 -07001655}
1656
1657int QueueScratchBufferSize(const Channel *channel) {
1658 return channel->num_readers() + channel->num_senders();
1659}
1660
Nathan Leong307c9692022-10-08 15:25:03 -07001661// Searches through configurations for schemas that include a certain type
1662const reflection::Schema *GetSchema(const Configuration *config,
1663 std::string_view schema_type) {
1664 if (config->has_channels()) {
1665 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
1666 for (const Channel *c : *config->channels()) {
1667 if (schema_type == c->type()->string_view()) {
1668 return c->schema();
1669 }
1670 }
1671 }
1672 return nullptr;
1673}
1674
1675// Copy schema reflection into detached flatbuffer
1676std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
1677GetSchemaDetachedBuffer(const Configuration *config,
1678 std::string_view schema_type) {
1679 const reflection::Schema *found_schema = GetSchema(config, schema_type);
1680 if (found_schema == nullptr) {
1681 return std::nullopt;
1682 }
1683 return RecursiveCopyFlatBuffer(found_schema);
1684}
1685
James Kuszmaul741a4d02023-01-05 14:59:21 -08001686aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
1687 const Configuration *config, std::string_view name,
1688 aos::FlatbufferVector<reflection::Schema> schema, const aos::Node *node,
1689 ChannelT overrides) {
1690 overrides.name = name;
James Kuszmaul80d6c422023-01-06 14:16:04 -08001691 CHECK(schema.message().has_root_table());
James Kuszmaul741a4d02023-01-05 14:59:21 -08001692 overrides.type = schema.message().root_table()->name()->string_view();
1693 if (node != nullptr) {
1694 CHECK(node->has_name());
1695 overrides.source_node = node->name()->string_view();
1696 }
1697 flatbuffers::FlatBufferBuilder fbb;
1698 // Don't populate fields from overrides that the user doesn't explicitly
1699 // override.
1700 fbb.ForceDefaults(false);
1701 const flatbuffers::Offset<Channel> channel_offset =
1702 Channel::Pack(fbb, &overrides);
1703 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
1704 channels_offset = fbb.CreateVector({channel_offset});
1705 Configuration::Builder config_builder(fbb);
1706 config_builder.add_channels(channels_offset);
1707 fbb.Finish(config_builder.Finish());
1708 FlatbufferDetachedBuffer<Configuration> new_channel_config = fbb.Release();
1709 new_channel_config = MergeConfiguration(new_channel_config, {schema});
1710 return MergeWithConfig(config, new_channel_config);
1711}
1712
Brian Silverman66f079a2013-08-26 16:24:30 -07001713} // namespace configuration
1714} // namespace aos