blob: f2488a5f9767b18ad1a6cc8c46da4f7533b43d06 [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
Adam Snaider13d48d92023-08-03 12:20:15 -0700142template <typename T>
143struct FbsContainer {
144 FbsContainer(aos::FlatbufferDetachedBuffer<T> table) {
145 this->table =
146 std::make_unique<FlatbufferDetachedBuffer<T>>(std::move(table));
147 }
148 std::unique_ptr<FlatbufferDetachedBuffer<T>> table;
149 bool operator==(const FbsContainer<T> &other) const {
150 return *this->table == *other.table;
151 }
152 bool operator<(const FbsContainer<T> &other) const {
153 return *this->table < *other.table;
154 }
155};
156
157typedef FbsContainer<Channel> ChannelContainer;
158typedef FbsContainer<Connection> ConnectionContainer;
159typedef FbsContainer<Application> ApplicationContainer;
160typedef FbsContainer<Node> NodeContainer;
161
Austin Schuhcb108412019-10-13 16:09:54 -0700162// Extracts the folder part of a path. Returns ./ if there is no path.
Austin Schuhf1fff282020-03-28 16:57:32 -0700163std::string_view ExtractFolder(const std::string_view filename) {
Austin Schuhcb108412019-10-13 16:09:54 -0700164 auto last_slash_pos = filename.find_last_of("/\\");
165
James Kuszmaul3ae42262019-11-08 12:33:41 -0800166 return last_slash_pos == std::string_view::npos
167 ? std::string_view("./")
Austin Schuhcb108412019-10-13 16:09:54 -0700168 : filename.substr(0, last_slash_pos + 1);
169}
170
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700171std::string AbsolutePath(const std::string_view filename) {
172 // Uses an std::string so that we know the input will be null-terminated.
173 const std::string terminated_file(filename);
174 char buffer[PATH_MAX];
175 PCHECK(NULL != realpath(terminated_file.c_str(), buffer));
176 return buffer;
177}
178
Austin Schuhef38cd22021-07-21 15:24:23 -0700179std::string RemoveDotDots(const std::string_view filename) {
180 std::vector<std::string> split = absl::StrSplit(filename, '/');
181 auto iterator = split.begin();
182 while (iterator != split.end()) {
183 if (iterator->empty()) {
184 iterator = split.erase(iterator);
185 } else if (*iterator == ".") {
186 iterator = split.erase(iterator);
187 } else if (*iterator == "..") {
188 CHECK(iterator != split.begin())
189 << ": Import path may not start with ..: " << filename;
190 auto previous = iterator;
191 --previous;
192 split.erase(iterator);
193 iterator = split.erase(previous);
194 } else {
195 ++iterator;
196 }
197 }
198 return absl::StrJoin(split, "/");
199}
200
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700201std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700202 const std::string_view path, absl::btree_set<std::string> *visited_paths,
203 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh15182322020-10-10 15:25:21 -0700204 std::string binary_path = MaybeReplaceExtension(path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700205 VLOG(1) << "Looking up: " << path << ", starting with: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700206 bool binary_path_exists = util::PathExists(binary_path);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700207 std::string raw_path(path);
Austin Schuh15182322020-10-10 15:25:21 -0700208 // For each .json file, look and see if we can find a .bfbs file next to it
209 // with the same base name. If we can, assume it is the same and use it
210 // instead. It is much faster to load .bfbs files than .json files.
211 if (!binary_path_exists && !util::PathExists(raw_path)) {
212 const bool path_is_absolute = raw_path.size() > 0 && raw_path[0] == '/';
Brian Silvermand0588192022-07-26 00:35:22 -0700213 if (path_is_absolute) {
214 // Nowhere else to look up an absolute path, so fail now. Note that we
215 // always have at least one extra import path based on /proc/self/exe, so
216 // warning about those paths existing isn't helpful.
217 LOG(ERROR) << ": Failed to find file " << path << ".";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700218 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700219 }
220
221 bool found_path = false;
222 for (const auto &import_path : extra_import_paths) {
Austin Schuhef38cd22021-07-21 15:24:23 -0700223 raw_path = std::string(import_path) + "/" + RemoveDotDots(path);
Austin Schuh15182322020-10-10 15:25:21 -0700224 binary_path = MaybeReplaceExtension(raw_path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700225 VLOG(1) << "Checking: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700226 binary_path_exists = util::PathExists(binary_path);
227 if (binary_path_exists) {
228 found_path = true;
229 break;
230 }
Austin Schuhef38cd22021-07-21 15:24:23 -0700231 VLOG(1) << "Checking: " << raw_path;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700232 if (util::PathExists(raw_path)) {
233 found_path = true;
234 break;
235 }
236 }
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700237 if (!found_path) {
238 LOG(ERROR) << ": Failed to find file " << path << ".";
239 return std::nullopt;
240 }
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700241 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700242
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700243 std::optional<FlatbufferDetachedBuffer<Configuration>> config =
244 ReadConfigFile(binary_path_exists ? binary_path : raw_path,
245 binary_path_exists);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700246
Austin Schuhcb108412019-10-13 16:09:54 -0700247 // Depth first. Take the following example:
248 //
249 // config1.json:
250 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700251 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700252 // {
253 // "name": "/foo",
254 // "type": ".aos.bar",
255 // "max_size": 5
256 // }
257 // ],
258 // "imports": [
259 // "config2.json",
260 // ]
261 // }
262 //
263 // config2.json:
264 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700265 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700266 // {
267 // "name": "/foo",
268 // "type": ".aos.bar",
269 // "max_size": 7
270 // }
271 // ],
272 // }
273 //
274 // We want the main config (config1.json) to be able to override the imported
275 // config. That means that it needs to be merged into the imported configs,
276 // not the other way around.
277
Austin Schuh15182322020-10-10 15:25:21 -0700278 const std::string absolute_path =
279 AbsolutePath(binary_path_exists ? binary_path : raw_path);
280 // Track that we have seen this file before recursing. Track the path we
281 // actually loaded (which should be consistent if imported twice).
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700282 if (!visited_paths->insert(absolute_path).second) {
283 for (const auto &visited_path : *visited_paths) {
284 LOG(INFO) << "Already visited: " << visited_path;
285 }
286 LOG(FATAL)
287 << "Already imported " << path << " (i.e. " << absolute_path
288 << "). See above for the files that have already been processed.";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700289 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700290 }
Austin Schuhcb108412019-10-13 16:09:54 -0700291
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700292 if (config->message().has_imports()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700293 // Capture the imports.
294 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *v =
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700295 config->message().imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700296
297 // And then wipe them. This gets GCed when we merge later.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700298 config->mutable_message()->clear_imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700299
300 // Start with an empty configuration to merge into.
Austin Schuh40485ed2019-10-26 21:51:44 -0700301 FlatbufferDetachedBuffer<Configuration> merged_config =
302 FlatbufferDetachedBuffer<Configuration>::Empty();
Austin Schuhcb108412019-10-13 16:09:54 -0700303
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700304 const std::string path_folder(ExtractFolder(path));
Austin Schuhcb108412019-10-13 16:09:54 -0700305 for (const flatbuffers::String *str : *v) {
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700306 const std::string included_config =
307 path_folder + "/" + std::string(str->string_view());
Austin Schuhcb108412019-10-13 16:09:54 -0700308
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700309 const auto optional_config =
310 MaybeReadConfig(included_config, visited_paths, extra_import_paths);
311 if (!optional_config.has_value()) {
312 return std::nullopt;
313 }
Austin Schuhcb108412019-10-13 16:09:54 -0700314 // And them merge everything in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700315 merged_config = MergeFlatBuffers(merged_config, *optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -0700316 }
317
318 // Finally, merge this file in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700319 config = MergeFlatBuffers(merged_config, *config);
Austin Schuhcb108412019-10-13 16:09:54 -0700320 }
321 return config;
322}
323
Alex Perrycb7da4b2019-08-28 19:35:56 -0700324// Compares (c < p) a channel, and a name, type tuple.
325bool CompareChannels(const Channel *c,
James Kuszmaul3ae42262019-11-08 12:33:41 -0800326 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700327 int name_compare = c->name()->string_view().compare(p.first);
328 if (name_compare == 0) {
329 return c->type()->string_view() < p.second;
330 } else if (name_compare < 0) {
331 return true;
332 } else {
333 return false;
334 }
335};
336
337// Compares for equality (c == p) a channel, and a name, type tuple.
338bool EqualsChannels(const Channel *c,
Austin Schuhf1fff282020-03-28 16:57:32 -0700339 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700340 return c->name()->string_view() == p.first &&
341 c->type()->string_view() == p.second;
342}
343
344// Compares (c < p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800345bool CompareApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700346 return a->name()->string_view() < name;
347};
348
349// Compares for equality (c == p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800350bool EqualsApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700351 return a->name()->string_view() == name;
352}
353
Austin Schuh15182322020-10-10 15:25:21 -0700354void ValidateConfiguration(const Flatbuffer<Configuration> &config) {
355 // No imports should be left.
356 CHECK(!config.message().has_imports());
357
358 // Check that if there is a node list, all the source nodes are filled out and
359 // valid, and all the destination nodes are valid (and not the source). This
360 // is a basic consistency check.
361 if (config.message().has_channels()) {
362 const Channel *last_channel = nullptr;
363 for (const Channel *c : *config.message().channels()) {
364 CHECK(c->has_name());
365 CHECK(c->has_type());
366 if (c->name()->string_view().back() == '/') {
367 LOG(FATAL) << "Channel names can't end with '/'";
368 }
Austin Schuh47e382e2023-05-28 11:20:56 -0700369 if (c->name()->string_view().front() != '/') {
370 LOG(FATAL) << "Channel names must start with '/'";
371 }
Austin Schuh15182322020-10-10 15:25:21 -0700372 if (c->name()->string_view().find("//") != std::string_view::npos) {
373 LOG(FATAL) << ": Invalid channel name " << c->name()->string_view()
374 << ", can't use //.";
375 }
376 for (const char data : c->name()->string_view()) {
377 if (data >= '0' && data <= '9') {
378 continue;
379 }
380 if (data >= 'a' && data <= 'z') {
381 continue;
382 }
383 if (data >= 'A' && data <= 'Z') {
384 continue;
385 }
386 if (data == '-' || data == '_' || data == '/') {
387 continue;
388 }
389 LOG(FATAL) << "Invalid channel name " << c->name()->string_view()
390 << ", can only use [-a-zA-Z0-9_/]";
391 }
392
Austin Schuhfb37c612022-08-11 15:24:51 -0700393 CHECK_LT(QueueSize(&config.message(), c) + QueueScratchBufferSize(c),
Austin Schuh83cbb1e2023-06-23 12:59:02 -0700394 FLAGS_max_queue_size_override != 0
395 ? FLAGS_max_queue_size_override
396 : std::numeric_limits<
397 ipc_lib::QueueIndex::PackedIndexType>::max())
Austin Schuhfb37c612022-08-11 15:24:51 -0700398 << ": More messages/second configured than the queue can hold on "
399 << CleanedChannelToString(c) << ", " << c->frequency() << "hz for "
Austin Schuhfff9c3a2023-06-16 18:48:23 -0700400 << ChannelStorageDuration(&config.message(), c).count() << "ns";
Austin Schuhfb37c612022-08-11 15:24:51 -0700401
Austin Schuha156fb22021-10-11 19:23:21 -0700402 if (c->has_logger_nodes()) {
403 // Confirm that we don't have duplicate logger nodes.
404 absl::btree_set<std::string_view> logger_nodes;
405 for (const flatbuffers::String *s : *c->logger_nodes()) {
406 logger_nodes.insert(s->string_view());
407 }
408 CHECK_EQ(static_cast<size_t>(logger_nodes.size()),
409 c->logger_nodes()->size())
410 << ": Found duplicate logger_nodes in "
411 << CleanedChannelToString(c);
412 }
413
414 if (c->has_destination_nodes()) {
415 // Confirm that we don't have duplicate timestamp logger nodes.
Austin Schuh5e95bd62021-10-11 18:40:22 -0700416 for (const Connection *d : *c->destination_nodes()) {
Austin Schuha156fb22021-10-11 19:23:21 -0700417 if (d->has_timestamp_logger_nodes()) {
418 absl::btree_set<std::string_view> timestamp_logger_nodes;
419 for (const flatbuffers::String *s : *d->timestamp_logger_nodes()) {
420 timestamp_logger_nodes.insert(s->string_view());
421 }
422 CHECK_EQ(static_cast<size_t>(timestamp_logger_nodes.size()),
423 d->timestamp_logger_nodes()->size())
424 << ": Found duplicate timestamp_logger_nodes in "
425 << CleanedChannelToString(c);
426 }
427 }
428
429 // There is no good use case today for logging timestamps but not the
430 // corresponding data. Instead of plumbing through all of this on the
431 // reader side, let'd just disallow it for now.
432 if (c->logger() == LoggerConfig::NOT_LOGGED) {
433 for (const Connection *d : *c->destination_nodes()) {
434 CHECK(d->timestamp_logger() == LoggerConfig::NOT_LOGGED)
435 << ": Logging timestamps without data is not supported. If "
436 "you have a good use case, let's talk. "
437 << CleanedChannelToString(c);
438 }
Austin Schuh5e95bd62021-10-11 18:40:22 -0700439 }
440 }
441
Austin Schuh15182322020-10-10 15:25:21 -0700442 // Make sure everything is sorted while we are here... If this fails,
443 // there will be a bunch of weird errors.
444 if (last_channel != nullptr) {
445 CHECK(CompareChannels(
446 last_channel,
447 std::make_pair(c->name()->string_view(), c->type()->string_view())))
448 << ": Channels not sorted!";
449 }
450 last_channel = c;
451 }
452 }
453
454 if (config.message().has_nodes() && config.message().has_channels()) {
455 for (const Channel *c : *config.message().channels()) {
456 CHECK(c->has_source_node()) << ": Channel " << FlatbufferToJson(c)
457 << " is missing \"source_node\"";
458 CHECK(GetNode(&config.message(), c->source_node()->string_view()) !=
459 nullptr)
460 << ": Channel " << FlatbufferToJson(c)
461 << " has an unknown \"source_node\"";
462
463 if (c->has_destination_nodes()) {
464 for (const Connection *connection : *c->destination_nodes()) {
465 CHECK(connection->has_name());
466 CHECK(GetNode(&config.message(), connection->name()->string_view()) !=
467 nullptr)
468 << ": Channel " << FlatbufferToJson(c)
469 << " has an unknown \"destination_nodes\" "
470 << connection->name()->string_view();
471
472 switch (connection->timestamp_logger()) {
473 case LoggerConfig::LOCAL_LOGGER:
474 case LoggerConfig::NOT_LOGGED:
Austin Schuhb98d02d2022-08-16 13:27:25 -0700475 CHECK(!connection->has_timestamp_logger_nodes())
476 << ": " << CleanedChannelToString(c);
Austin Schuh15182322020-10-10 15:25:21 -0700477 break;
478 case LoggerConfig::REMOTE_LOGGER:
479 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
480 CHECK(connection->has_timestamp_logger_nodes());
481 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
482 for (const flatbuffers::String *timestamp_logger_node :
483 *connection->timestamp_logger_nodes()) {
484 CHECK(GetNode(&config.message(),
485 timestamp_logger_node->string_view()) != nullptr)
486 << ": Channel " << FlatbufferToJson(c)
487 << " has an unknown \"timestamp_logger_node\""
488 << connection->name()->string_view();
489 }
490 break;
491 }
492
493 CHECK_NE(connection->name()->string_view(),
494 c->source_node()->string_view())
495 << ": Channel " << FlatbufferToJson(c)
496 << " is forwarding data to itself";
497 }
498 }
499 }
500 }
501}
502
James Kuszmaulc8503f32022-06-25 16:17:12 -0700503void HandleReverseMaps(
504 const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
505 std::string_view type, const Node *node, std::set<std::string> *names) {
506 for (const Map *map : *maps) {
507 CHECK_NOTNULL(map);
508 const Channel *const match = CHECK_NOTNULL(map->match());
509 const Channel *const rename = CHECK_NOTNULL(map->rename());
510
511 // Handle type specific maps.
512 const flatbuffers::String *const match_type_string = match->type();
513 if (match_type_string != nullptr &&
514 match_type_string->string_view() != type) {
515 continue;
516 }
517
518 // Now handle node specific maps.
519 const flatbuffers::String *const match_source_node_string =
520 match->source_node();
521 if (node != nullptr && match_source_node_string != nullptr &&
522 match_source_node_string->string_view() !=
523 node->name()->string_view()) {
524 continue;
525 }
526
527 const flatbuffers::String *const match_name_string = match->name();
528 const flatbuffers::String *const rename_name_string = rename->name();
529 if (match_name_string == nullptr || rename_name_string == nullptr) {
530 continue;
531 }
532
533 const std::string rename_name = rename_name_string->str();
534 const std::string_view match_name = match_name_string->string_view();
535
536 std::set<std::string> possible_renames;
537
538 // Check if the current name(s) could have been reached using the provided
539 // rename.
540 if (match_name.back() == '*') {
541 for (const std::string &option : *names) {
542 if (option.substr(0, rename_name.size()) == rename_name) {
543 possible_renames.insert(
544 absl::StrCat(match_name.substr(0, match_name.size() - 1),
545 option.substr(rename_name.size())));
546 }
547 }
548 names->insert(possible_renames.begin(), possible_renames.end());
549 } else if (names->count(rename_name) != 0) {
550 names->insert(std::string(match_name));
551 }
552 }
553}
554
Alex Perrycb7da4b2019-08-28 19:35:56 -0700555} // namespace
556
Austin Schuh006a9f52021-04-07 16:24:18 -0700557// Maps name for the provided maps. Modifies name.
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800558//
559// This is called many times during startup, and it dereferences a lot of
560// pointers. These combine to make it a performance hotspot during many tests
561// under msan, so there is some optimizing around caching intermediates instead
562// of dereferencing the pointer multiple times.
James Kuszmaulc8503f32022-06-25 16:17:12 -0700563//
564// Deliberately not in an anonymous namespace so that the log-reading code can
565// reference it.
Austin Schuh006a9f52021-04-07 16:24:18 -0700566void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
567 std::string *name, std::string_view type, const Node *node) {
568 // For the same reason we merge configs in reverse order, we want to process
569 // maps in reverse order. That lets the outer config overwrite channels from
570 // the inner configs.
571 for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800572 const Channel *const match = i->match();
573 if (!match) {
Austin Schuh006a9f52021-04-07 16:24:18 -0700574 continue;
575 }
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800576 const flatbuffers::String *const match_name_string = match->name();
577 if (!match_name_string) {
578 continue;
579 }
580 const Channel *const rename = i->rename();
581 if (!rename) {
582 continue;
583 }
584 const flatbuffers::String *const rename_name_string = rename->name();
585 if (!rename_name_string) {
Austin Schuh006a9f52021-04-07 16:24:18 -0700586 continue;
587 }
588
589 // Handle normal maps (now that we know that match and rename are filled
590 // out).
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800591 const std::string_view match_name = match_name_string->string_view();
Austin Schuh006a9f52021-04-07 16:24:18 -0700592 if (match_name != *name) {
593 if (match_name.back() == '*' &&
594 std::string_view(*name).substr(
595 0, std::min(name->size(), match_name.size() - 1)) ==
596 match_name.substr(0, match_name.size() - 1)) {
597 CHECK_EQ(match_name.find('*'), match_name.size() - 1);
598 } else {
599 continue;
600 }
601 }
602
603 // Handle type specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800604 const flatbuffers::String *const match_type_string = match->type();
605 if (match_type_string && match_type_string->string_view() != type) {
Austin Schuh006a9f52021-04-07 16:24:18 -0700606 continue;
607 }
608
609 // Now handle node specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800610 const flatbuffers::String *const match_source_node_string =
611 match->source_node();
612 if (node && match_source_node_string &&
613 match_source_node_string->string_view() !=
Austin Schuh006a9f52021-04-07 16:24:18 -0700614 node->name()->string_view()) {
615 continue;
616 }
617
Brian Silvermanf3798cb2021-11-10 12:26:34 -0800618 std::string new_name(rename_name_string->string_view());
Austin Schuh006a9f52021-04-07 16:24:18 -0700619 if (match_name.back() == '*') {
620 new_name += std::string(name->substr(match_name.size() - 1));
621 }
622 VLOG(1) << "Renamed \"" << *name << "\" to \"" << new_name << "\"";
623 *name = std::move(new_name);
624 }
625}
626
James Kuszmaulc8503f32022-06-25 16:17:12 -0700627std::set<std::string> GetChannelAliases(const Configuration *config,
628 std::string_view name,
629 std::string_view type,
630 const std::string_view application_name,
631 const Node *node) {
632 std::set<std::string> names{std::string(name)};
633 if (config->has_maps()) {
634 HandleReverseMaps(config->maps(), type, node, &names);
635 }
636 {
637 const Application *application =
638 GetApplication(config, node, application_name);
639 if (application != nullptr && application->has_maps()) {
640 HandleReverseMaps(application->maps(), type, node, &names);
641 }
642 }
643 return names;
644}
645
Austin Schuh40485ed2019-10-26 21:51:44 -0700646FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
Austin Schuhcb108412019-10-13 16:09:54 -0700647 const Flatbuffer<Configuration> &config) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700648 // auto_merge_config will contain all the fields of the Configuration that are
649 // to be passed through unmodified to the result of MergeConfiguration().
650 // In the processing below, we mutate auto_merge_config to remove any fields
651 // which we do need to alter (hence why we can't use the input config
652 // directly), and then merge auto_merge_config back in at the end.
653 aos::FlatbufferDetachedBuffer<aos::Configuration> auto_merge_config =
Austin Schuha4fc60f2020-11-01 23:06:47 -0800654 aos::RecursiveCopyFlatBuffer(&config.message());
James Kuszmaul3c998592020-07-27 21:04:47 -0700655
Austin Schuh40485ed2019-10-26 21:51:44 -0700656 // Store all the channels in a sorted set. This lets us track channels we
Austin Schuhcb108412019-10-13 16:09:54 -0700657 // have seen before and merge the updates in.
Adam Snaider13d48d92023-08-03 12:20:15 -0700658 absl::btree_set<ChannelContainer> channels;
Austin Schuhcb108412019-10-13 16:09:54 -0700659
Austin Schuh40485ed2019-10-26 21:51:44 -0700660 if (config.message().has_channels()) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700661 auto_merge_config.mutable_message()->clear_channels();
Austin Schuh40485ed2019-10-26 21:51:44 -0700662 for (const Channel *c : *config.message().channels()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700663 // Ignore malformed entries.
Austin Schuh40485ed2019-10-26 21:51:44 -0700664 if (!c->has_name()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700665 continue;
666 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700667 if (!c->has_type()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700668 continue;
669 }
670
Brian Silverman77162972020-08-12 19:52:40 -0700671 CHECK_EQ(c->read_method() == ReadMethod::PIN, c->num_readers() != 0)
672 << ": num_readers may be set if and only if read_method is PIN,"
673 " if you want 0 readers do not set PIN: "
674 << CleanedChannelToString(c);
675
Austin Schuh40485ed2019-10-26 21:51:44 -0700676 // Attempt to insert the channel.
Austin Schuha4fc60f2020-11-01 23:06:47 -0800677 auto result = channels.insert(RecursiveCopyFlatBuffer(c));
Austin Schuhcb108412019-10-13 16:09:54 -0700678 if (!result.second) {
679 // Already there, so merge the new table into the original.
Austin Schuh4a5f5d22021-10-12 15:09:35 -0700680 // Schemas merge poorly, so pick the newest one.
Adam Snaider13d48d92023-08-03 12:20:15 -0700681 if (result.first->table->message().has_schema() && c->has_schema()) {
682 result.first->table->mutable_message()->clear_schema();
Austin Schuh4a5f5d22021-10-12 15:09:35 -0700683 }
Austin Schuha7996eb2021-10-11 19:03:24 -0700684 auto merged =
Adam Snaider13d48d92023-08-03 12:20:15 -0700685 MergeFlatBuffers(*result.first->table, RecursiveCopyFlatBuffer(c));
Austin Schuha7996eb2021-10-11 19:03:24 -0700686
687 if (merged.message().has_destination_nodes()) {
Adam Snaider13d48d92023-08-03 12:20:15 -0700688 absl::btree_set<ConnectionContainer> connections;
Austin Schuha7996eb2021-10-11 19:03:24 -0700689 for (const Connection *connection :
690 *merged.message().destination_nodes()) {
691 auto connection_result =
692 connections.insert(RecursiveCopyFlatBuffer(connection));
693 if (!connection_result.second) {
Adam Snaider13d48d92023-08-03 12:20:15 -0700694 *connection_result.first->table =
695 MergeFlatBuffers(*connection_result.first->table,
Austin Schuha7996eb2021-10-11 19:03:24 -0700696 RecursiveCopyFlatBuffer(connection));
697 }
698 }
699 if (static_cast<size_t>(connections.size()) !=
700 merged.message().destination_nodes()->size()) {
701 merged.mutable_message()->clear_destination_nodes();
702 flatbuffers::FlatBufferBuilder fbb;
703 fbb.ForceDefaults(true);
704 std::vector<flatbuffers::Offset<Connection>> connection_offsets;
Adam Snaider13d48d92023-08-03 12:20:15 -0700705 for (const ConnectionContainer &connection : connections) {
Austin Schuha7996eb2021-10-11 19:03:24 -0700706 connection_offsets.push_back(
Adam Snaider13d48d92023-08-03 12:20:15 -0700707 RecursiveCopyFlatBuffer(&connection.table->message(), &fbb));
Austin Schuha7996eb2021-10-11 19:03:24 -0700708 }
709 flatbuffers::Offset<
710 flatbuffers::Vector<flatbuffers::Offset<Connection>>>
711 destination_nodes_offset = fbb.CreateVector(connection_offsets);
712 Channel::Builder channel_builder(fbb);
713 channel_builder.add_destination_nodes(destination_nodes_offset);
714 fbb.Finish(channel_builder.Finish());
715 FlatbufferDetachedBuffer<Channel> destinations_channel(
716 fbb.Release());
717 merged = MergeFlatBuffers(merged, destinations_channel);
718 }
719 }
720
Adam Snaider13d48d92023-08-03 12:20:15 -0700721 *result.first->table = std::move(merged);
Austin Schuhcb108412019-10-13 16:09:54 -0700722 }
723 }
724 }
725
726 // Now repeat this for the application list.
Adam Snaider13d48d92023-08-03 12:20:15 -0700727 absl::btree_set<ApplicationContainer> applications;
Austin Schuhcb108412019-10-13 16:09:54 -0700728 if (config.message().has_applications()) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700729 auto_merge_config.mutable_message()->clear_applications();
Austin Schuhcb108412019-10-13 16:09:54 -0700730 for (const Application *a : *config.message().applications()) {
731 if (!a->has_name()) {
732 continue;
733 }
734
Austin Schuha4fc60f2020-11-01 23:06:47 -0800735 auto result = applications.insert(RecursiveCopyFlatBuffer(a));
Austin Schuhcb108412019-10-13 16:09:54 -0700736 if (!result.second) {
Austin Schuhf81c2522021-12-08 12:03:30 -0800737 if (a->has_args()) {
Adam Snaider13d48d92023-08-03 12:20:15 -0700738 result.first->table->mutable_message()->clear_args();
Austin Schuhf81c2522021-12-08 12:03:30 -0800739 }
Adam Snaider13d48d92023-08-03 12:20:15 -0700740 *result.first->table =
741 MergeFlatBuffers(*result.first->table, RecursiveCopyFlatBuffer(a));
Austin Schuhcb108412019-10-13 16:09:54 -0700742 }
743 }
744 }
745
Austin Schuh217a9782019-12-21 23:02:50 -0800746 // Now repeat this for the node list.
Adam Snaider13d48d92023-08-03 12:20:15 -0700747 absl::btree_set<NodeContainer> nodes;
Austin Schuh217a9782019-12-21 23:02:50 -0800748 if (config.message().has_nodes()) {
James Kuszmaul3c998592020-07-27 21:04:47 -0700749 auto_merge_config.mutable_message()->clear_nodes();
Austin Schuh217a9782019-12-21 23:02:50 -0800750 for (const Node *n : *config.message().nodes()) {
751 if (!n->has_name()) {
752 continue;
753 }
754
Austin Schuha4fc60f2020-11-01 23:06:47 -0800755 auto result = nodes.insert(RecursiveCopyFlatBuffer(n));
Austin Schuh217a9782019-12-21 23:02:50 -0800756 if (!result.second) {
Adam Snaider13d48d92023-08-03 12:20:15 -0700757 *result.first->table =
758 MergeFlatBuffers(*result.first->table, RecursiveCopyFlatBuffer(n));
Austin Schuh217a9782019-12-21 23:02:50 -0800759 }
760 }
761 }
762
Austin Schuhcb108412019-10-13 16:09:54 -0700763 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -0800764 fbb.ForceDefaults(true);
Austin Schuhcb108412019-10-13 16:09:54 -0700765
766 // Start by building the vectors. They need to come before the final table.
Austin Schuh40485ed2019-10-26 21:51:44 -0700767 // Channels
768 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
769 channels_offset;
Austin Schuhcb108412019-10-13 16:09:54 -0700770 {
Austin Schuh40485ed2019-10-26 21:51:44 -0700771 ::std::vector<flatbuffers::Offset<Channel>> channel_offsets;
Adam Snaider13d48d92023-08-03 12:20:15 -0700772 for (const ChannelContainer &c : channels) {
Austin Schuha4fc60f2020-11-01 23:06:47 -0800773 channel_offsets.emplace_back(
Adam Snaider13d48d92023-08-03 12:20:15 -0700774 RecursiveCopyFlatBuffer<Channel>(&c.table->message(), &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -0700775 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700776 channels_offset = fbb.CreateVector(channel_offsets);
Austin Schuhcb108412019-10-13 16:09:54 -0700777 }
778
779 // Applications
780 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
781 applications_offset;
782 {
783 ::std::vector<flatbuffers::Offset<Application>> applications_offsets;
Adam Snaider13d48d92023-08-03 12:20:15 -0700784 for (const ApplicationContainer &a : applications) {
Austin Schuhcb108412019-10-13 16:09:54 -0700785 applications_offsets.emplace_back(
Adam Snaider13d48d92023-08-03 12:20:15 -0700786 RecursiveCopyFlatBuffer<Application>(&a.table->message(), &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -0700787 }
788 applications_offset = fbb.CreateVector(applications_offsets);
789 }
790
Austin Schuh217a9782019-12-21 23:02:50 -0800791 // Nodes
792 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
793 nodes_offset;
794 {
795 ::std::vector<flatbuffers::Offset<Node>> node_offsets;
Adam Snaider13d48d92023-08-03 12:20:15 -0700796 for (const NodeContainer &n : nodes) {
Austin Schuha4fc60f2020-11-01 23:06:47 -0800797 node_offsets.emplace_back(
Adam Snaider13d48d92023-08-03 12:20:15 -0700798 RecursiveCopyFlatBuffer<Node>(&n.table->message(), &fbb));
Austin Schuh217a9782019-12-21 23:02:50 -0800799 }
800 nodes_offset = fbb.CreateVector(node_offsets);
801 }
802
Austin Schuhcb108412019-10-13 16:09:54 -0700803 // And then build a Configuration with them all.
804 ConfigurationBuilder configuration_builder(fbb);
Austin Schuh40485ed2019-10-26 21:51:44 -0700805 configuration_builder.add_channels(channels_offset);
Austin Schuh217a9782019-12-21 23:02:50 -0800806 if (config.message().has_applications()) {
807 configuration_builder.add_applications(applications_offset);
808 }
809 if (config.message().has_nodes()) {
810 configuration_builder.add_nodes(nodes_offset);
811 }
Austin Schuhcb108412019-10-13 16:09:54 -0700812
813 fbb.Finish(configuration_builder.Finish());
Austin Schuh217a9782019-12-21 23:02:50 -0800814
James Kuszmaul3c998592020-07-27 21:04:47 -0700815 aos::FlatbufferDetachedBuffer<aos::Configuration> modified_config(
816 fbb.Release());
817
Austin Schuh217a9782019-12-21 23:02:50 -0800818 // Now, validate that if there is a node list, every channel has a source
819 // node.
James Kuszmaul3c998592020-07-27 21:04:47 -0700820 FlatbufferDetachedBuffer<Configuration> result =
821 MergeFlatBuffers(modified_config, auto_merge_config);
Austin Schuh217a9782019-12-21 23:02:50 -0800822
Austin Schuh15182322020-10-10 15:25:21 -0700823 ValidateConfiguration(result);
Austin Schuh217a9782019-12-21 23:02:50 -0800824
825 return result;
Austin Schuhcb108412019-10-13 16:09:54 -0700826}
827
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700828std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700829 const std::string_view path,
Austin Schuhef38cd22021-07-21 15:24:23 -0700830 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh89026f52022-02-25 14:24:04 -0800831 // Add the executable directory to the search path. That makes it so that
832 // tools can be run from any directory without hard-coding an absolute path to
833 // the config into all binaries.
834 std::vector<std::string_view> extra_import_paths_with_exe =
835 extra_import_paths;
836 char proc_self_exec_buffer[PATH_MAX + 1];
837 std::memset(proc_self_exec_buffer, 0, sizeof(proc_self_exec_buffer));
838 ssize_t s = readlink("/proc/self/exe", proc_self_exec_buffer, PATH_MAX);
839 if (s > 0) {
840 // If the readlink call fails, the worst thing that happens is that we don't
841 // automatically find the config next to the binary. VLOG to make it easier
842 // to debug.
843 std::string_view proc_self_exec(proc_self_exec_buffer);
844
845 extra_import_paths_with_exe.emplace_back(
846 proc_self_exec.substr(0, proc_self_exec.rfind("/")));
847 } else {
848 VLOG(1) << "Failed to read /proc/self/exe";
849 }
850
Austin Schuhcb108412019-10-13 16:09:54 -0700851 // We only want to read a file once. So track the visited files in a set.
852 absl::btree_set<std::string> visited_paths;
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700853 std::optional<FlatbufferDetachedBuffer<Configuration>> read_config =
854 MaybeReadConfig(path, &visited_paths, extra_import_paths_with_exe);
855
856 if (read_config == std::nullopt) {
857 return read_config;
858 }
Austin Schuh15182322020-10-10 15:25:21 -0700859
860 // If we only read one file, and it had a .bfbs extension, it has to be a
861 // fully formatted config. Do a quick verification and return it.
862 if (visited_paths.size() == 1 && EndsWith(*visited_paths.begin(), ".bfbs")) {
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700863 ValidateConfiguration(*read_config);
Austin Schuh15182322020-10-10 15:25:21 -0700864 return read_config;
865 }
866
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700867 return MergeConfiguration(*read_config);
868}
869
870FlatbufferDetachedBuffer<Configuration> ReadConfig(
871 const std::string_view path,
872 const std::vector<std::string_view> &extra_import_paths) {
873 auto optional_config = MaybeReadConfig(path, extra_import_paths);
874 CHECK(optional_config) << "Could not read config. See above errors";
875 return std::move(*optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -0700876}
877
Austin Schuh8d6cea82020-02-28 12:17:16 -0800878FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Brian Silverman24f5aa82020-06-23 16:21:28 -0700879 const Configuration *config, const Flatbuffer<Configuration> &addition) {
880 return MergeConfiguration(MergeFlatBuffers(config, &addition.message()));
881}
882
883FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Austin Schuh8d6cea82020-02-28 12:17:16 -0800884 const Configuration *config, std::string_view json) {
885 FlatbufferDetachedBuffer<Configuration> addition =
886 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
887
Brian Silverman24f5aa82020-06-23 16:21:28 -0700888 return MergeWithConfig(config, addition);
Austin Schuh8d6cea82020-02-28 12:17:16 -0800889}
890
James Kuszmaul3ae42262019-11-08 12:33:41 -0800891const Channel *GetChannel(const Configuration *config, std::string_view name,
892 std::string_view type,
Austin Schuh0de30f32020-12-06 12:44:28 -0800893 std::string_view application_name, const Node *node,
894 bool quiet) {
Brian Silverman9fcf2c72020-12-21 18:30:58 -0800895 if (!config->has_channels()) {
896 return nullptr;
897 }
898
Austin Schuhbca6cf02019-12-22 17:28:34 -0800899 const std::string_view original_name = name;
Austin Schuhf1fff282020-03-28 16:57:32 -0700900 std::string mutable_name;
Austin Schuh4c3b9702020-08-30 11:34:55 -0700901 if (node != nullptr) {
902 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
903 << "\" } on " << aos::FlatbufferToJson(node);
904 } else {
905 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
906 << "\" }";
907 }
Austin Schuhcb108412019-10-13 16:09:54 -0700908
909 // First handle application specific maps. Only do this if we have a matching
910 // application name, and it has maps.
Austin Schuhd2e2f6a2021-02-07 20:46:16 -0800911 {
912 const Application *application =
913 GetApplication(config, node, application_name);
914 if (application != nullptr && application->has_maps()) {
915 mutable_name = std::string(name);
916 HandleMaps(application->maps(), &mutable_name, type, node);
917 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -0700918 }
919 }
920
921 // Now do global maps.
Austin Schuh40485ed2019-10-26 21:51:44 -0700922 if (config->has_maps()) {
Austin Schuhf1fff282020-03-28 16:57:32 -0700923 mutable_name = std::string(name);
924 HandleMaps(config->maps(), &mutable_name, type, node);
925 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -0700926 }
927
Austin Schuhbca6cf02019-12-22 17:28:34 -0800928 if (original_name != name) {
929 VLOG(1) << "Remapped to { \"name\": \"" << name << "\", \"type\": \""
930 << type << "\" }";
931 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700932
James Kuszmaul71a81932020-12-15 21:08:01 -0800933 // Then look for the channel (note that this relies on the channels being
934 // sorted in the config).
Austin Schuh40485ed2019-10-26 21:51:44 -0700935 auto channel_iterator =
Austin Schuhf1fff282020-03-28 16:57:32 -0700936 std::lower_bound(config->channels()->cbegin(), config->channels()->cend(),
Austin Schuh40485ed2019-10-26 21:51:44 -0700937 std::make_pair(name, type), CompareChannels);
Austin Schuhcb108412019-10-13 16:09:54 -0700938
939 // Make sure we actually found it, and it matches.
Austin Schuh40485ed2019-10-26 21:51:44 -0700940 if (channel_iterator != config->channels()->cend() &&
941 EqualsChannels(*channel_iterator, std::make_pair(name, type))) {
Austin Schuhbca6cf02019-12-22 17:28:34 -0800942 if (VLOG_IS_ON(2)) {
943 VLOG(2) << "Found: " << FlatbufferToJson(*channel_iterator);
944 } else if (VLOG_IS_ON(1)) {
945 VLOG(1) << "Found: " << CleanedChannelToString(*channel_iterator);
946 }
Austin Schuh40485ed2019-10-26 21:51:44 -0700947 return *channel_iterator;
Austin Schuhcb108412019-10-13 16:09:54 -0700948 } else {
949 VLOG(1) << "No match for { \"name\": \"" << name << "\", \"type\": \""
950 << type << "\" }";
Austin Schuh0de30f32020-12-06 12:44:28 -0800951 if (original_name != name && !quiet) {
Maxwell Gumleya28d6502023-11-17 10:43:36 -0700952 VLOG(1) << "Remapped from {\"name\": \"" << original_name
953 << "\", \"type\": \"" << type << "\"}, to {\"name\": \"" << name
954 << "\", \"type\": \"" << type
955 << "\"}, but no channel by that name exists.";
Austin Schuh4b42b252020-10-19 11:35:20 -0700956 }
Austin Schuhcb108412019-10-13 16:09:54 -0700957 return nullptr;
958 }
959}
960
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800961size_t ChannelIndex(const Configuration *configuration,
962 const Channel *channel) {
963 CHECK(configuration->channels() != nullptr) << ": No channels";
964
Brian Silvermana9698c92021-11-10 12:27:04 -0800965 const auto c = std::lower_bound(
966 configuration->channels()->cbegin(), configuration->channels()->cend(),
967 std::make_pair(channel->name()->string_view(),
968 channel->type()->string_view()),
969 CompareChannels);
970 CHECK(c != configuration->channels()->cend())
971 << ": Channel pointer not found in configuration()->channels()";
972 CHECK(*c == channel)
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800973 << ": Channel pointer not found in configuration()->channels()";
974
Brian Silvermana9698c92021-11-10 12:27:04 -0800975 return std::distance(configuration->channels()->cbegin(), c);
Austin Schuhc9e10ec2020-01-26 16:08:28 -0800976}
977
Austin Schuhbca6cf02019-12-22 17:28:34 -0800978std::string CleanedChannelToString(const Channel *channel) {
979 FlatbufferDetachedBuffer<Channel> cleaned_channel = CopyFlatBuffer(channel);
980 cleaned_channel.mutable_message()->clear_schema();
981 return FlatbufferToJson(cleaned_channel);
982}
983
Austin Schuha81454b2020-05-12 19:58:36 -0700984std::string StrippedChannelToString(const Channel *channel) {
985 return absl::StrCat("{ \"name\": \"", channel->name()->string_view(),
986 "\", \"type\": \"", channel->type()->string_view(),
987 "\" }");
988}
989
Alex Perrycb7da4b2019-08-28 19:35:56 -0700990FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
991 const Flatbuffer<Configuration> &config,
Austin Schuh0de30f32020-12-06 12:44:28 -0800992 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700993 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -0800994 fbb.ForceDefaults(true);
Alex Perrycb7da4b2019-08-28 19:35:56 -0700995
Austin Schuh68d98592020-11-01 23:22:57 -0800996 // Cache for holding already inserted schemas.
997 std::map<std::string_view, flatbuffers::Offset<reflection::Schema>>
998 schema_cache;
999
Austin Schuhdda6db72023-06-21 17:02:34 -07001000 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
Austin Schuh68d98592020-11-01 23:22:57 -08001001 << ": Merging logic needs to be updated when the number of channel "
1002 "fields changes.";
James Kuszmaul3c998592020-07-27 21:04:47 -07001003
Alex Perrycb7da4b2019-08-28 19:35:56 -07001004 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
1005 channels_offset;
1006 if (config.message().has_channels()) {
1007 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
1008 for (const Channel *c : *config.message().channels()) {
Alex Perrycb7da4b2019-08-28 19:35:56 -07001009 // Search for a schema with a matching type.
Austin Schuh0de30f32020-12-06 12:44:28 -08001010 const aos::FlatbufferVector<reflection::Schema> *found_schema = nullptr;
1011 for (const aos::FlatbufferVector<reflection::Schema> &schema : schemas) {
Alex Perrycb7da4b2019-08-28 19:35:56 -07001012 if (schema.message().root_table() != nullptr) {
1013 if (schema.message().root_table()->name()->string_view() ==
1014 c->type()->string_view()) {
1015 found_schema = &schema;
1016 }
1017 }
1018 }
1019
Maxwell Gumley65d06582023-03-17 09:13:50 -06001020 if (found_schema == nullptr) {
1021 std::stringstream ss;
1022 for (const aos::FlatbufferVector<reflection::Schema> &schema :
1023 schemas) {
1024 if (schema.message().root_table() == nullptr) {
1025 continue;
1026 }
1027 auto name = schema.message().root_table()->name()->string_view();
1028 ss << "\n\tname: " << name;
1029 }
1030 LOG(FATAL) << ": Failed to find schema for " << FlatbufferToJson(c)
1031 << "\n\tThe following schemas were found:\n"
1032 << ss.str();
1033 }
Alex Perrycb7da4b2019-08-28 19:35:56 -07001034
Austin Schuh68d98592020-11-01 23:22:57 -08001035 // Now copy the message manually.
1036 auto cached_schema = schema_cache.find(c->type()->string_view());
1037 flatbuffers::Offset<reflection::Schema> schema_offset;
1038 if (cached_schema != schema_cache.end()) {
1039 schema_offset = cached_schema->second;
1040 } else {
Austin Schuha4fc60f2020-11-01 23:06:47 -08001041 schema_offset = RecursiveCopyFlatBuffer<reflection::Schema>(
1042 &found_schema->message(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001043 schema_cache.emplace(c->type()->string_view(), schema_offset);
1044 }
1045
1046 flatbuffers::Offset<flatbuffers::String> name_offset =
1047 fbb.CreateSharedString(c->name()->str());
1048 flatbuffers::Offset<flatbuffers::String> type_offset =
1049 fbb.CreateSharedString(c->type()->str());
1050 flatbuffers::Offset<flatbuffers::String> source_node_offset =
Austin Schuha4fc60f2020-11-01 23:06:47 -08001051 c->has_source_node() ? fbb.CreateSharedString(c->source_node()->str())
1052 : 0;
Austin Schuh68d98592020-11-01 23:22:57 -08001053
1054 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Connection>>>
1055 destination_nodes_offset =
Austin Schuh5c255aa2020-11-05 18:32:46 -08001056 aos::RecursiveCopyVectorTable(c->destination_nodes(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001057
1058 flatbuffers::Offset<
1059 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
1060 logger_nodes_offset =
1061 aos::CopyVectorSharedString(c->logger_nodes(), &fbb);
1062
1063 Channel::Builder channel_builder(fbb);
1064 channel_builder.add_name(name_offset);
1065 channel_builder.add_type(type_offset);
1066 if (c->has_frequency()) {
1067 channel_builder.add_frequency(c->frequency());
1068 }
1069 if (c->has_max_size()) {
1070 channel_builder.add_max_size(c->max_size());
1071 }
1072 if (c->has_num_senders()) {
1073 channel_builder.add_num_senders(c->num_senders());
1074 }
1075 if (c->has_num_watchers()) {
1076 channel_builder.add_num_watchers(c->num_watchers());
1077 }
Alex Perrycb7da4b2019-08-28 19:35:56 -07001078 channel_builder.add_schema(schema_offset);
Austin Schuh68d98592020-11-01 23:22:57 -08001079 if (!source_node_offset.IsNull()) {
1080 channel_builder.add_source_node(source_node_offset);
1081 }
1082 if (!destination_nodes_offset.IsNull()) {
1083 channel_builder.add_destination_nodes(destination_nodes_offset);
1084 }
1085 if (c->has_logger()) {
1086 channel_builder.add_logger(c->logger());
1087 }
1088 if (!logger_nodes_offset.IsNull()) {
1089 channel_builder.add_logger_nodes(logger_nodes_offset);
1090 }
1091 if (c->has_read_method()) {
1092 channel_builder.add_read_method(c->read_method());
1093 }
1094 if (c->has_num_readers()) {
1095 channel_builder.add_num_readers(c->num_readers());
1096 }
Austin Schuhdda6db72023-06-21 17:02:34 -07001097 if (c->has_channel_storage_duration()) {
1098 channel_builder.add_channel_storage_duration(
1099 c->channel_storage_duration());
1100 }
Austin Schuh68d98592020-11-01 23:22:57 -08001101 channel_offsets.emplace_back(channel_builder.Finish());
Alex Perrycb7da4b2019-08-28 19:35:56 -07001102 }
1103 channels_offset = fbb.CreateVector(channel_offsets);
1104 }
1105
Austin Schuh68d98592020-11-01 23:22:57 -08001106 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
Austin Schuh5c255aa2020-11-05 18:32:46 -08001107 maps_offset =
1108 aos::RecursiveCopyVectorTable(config.message().maps(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001109
1110 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
Austin Schuh5c255aa2020-11-05 18:32:46 -08001111 nodes_offset =
1112 aos::RecursiveCopyVectorTable(config.message().nodes(), &fbb);
Austin Schuh68d98592020-11-01 23:22:57 -08001113
1114 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
1115 applications_offset =
Austin Schuh5c255aa2020-11-05 18:32:46 -08001116 aos::RecursiveCopyVectorTable(config.message().applications(), &fbb);
Austin Schuh217a9782019-12-21 23:02:50 -08001117
1118 // Now insert everything else in unmodified.
Alex Perrycb7da4b2019-08-28 19:35:56 -07001119 ConfigurationBuilder configuration_builder(fbb);
1120 if (config.message().has_channels()) {
1121 configuration_builder.add_channels(channels_offset);
1122 }
Austin Schuh68d98592020-11-01 23:22:57 -08001123 if (!maps_offset.IsNull()) {
1124 configuration_builder.add_maps(maps_offset);
1125 }
1126 if (!nodes_offset.IsNull()) {
1127 configuration_builder.add_nodes(nodes_offset);
1128 }
1129 if (!applications_offset.IsNull()) {
1130 configuration_builder.add_applications(applications_offset);
1131 }
1132
1133 if (config.message().has_channel_storage_duration()) {
1134 configuration_builder.add_channel_storage_duration(
1135 config.message().channel_storage_duration());
1136 }
1137
1138 CHECK_EQ(Configuration::MiniReflectTypeTable()->num_elems, 6u)
1139 << ": Merging logic needs to be updated when the number of configuration "
1140 "fields changes.";
1141
Alex Perrycb7da4b2019-08-28 19:35:56 -07001142 fbb.Finish(configuration_builder.Finish());
James Kuszmaul3c998592020-07-27 21:04:47 -07001143 aos::FlatbufferDetachedBuffer<aos::Configuration> modified_config(
1144 fbb.Release());
1145
Austin Schuh68d98592020-11-01 23:22:57 -08001146 return modified_config;
Alex Perrycb7da4b2019-08-28 19:35:56 -07001147}
1148
Austin Schuh217a9782019-12-21 23:02:50 -08001149const Node *GetNodeFromHostname(const Configuration *config,
1150 std::string_view hostname) {
1151 for (const Node *node : *config->nodes()) {
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001152 if (node->has_hostname() && node->hostname()->string_view() == hostname) {
Austin Schuh217a9782019-12-21 23:02:50 -08001153 return node;
1154 }
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001155 if (node->has_hostnames()) {
1156 for (const auto &candidate : *node->hostnames()) {
1157 if (candidate->string_view() == hostname) {
1158 return node;
1159 }
1160 }
1161 }
Austin Schuh217a9782019-12-21 23:02:50 -08001162 }
1163 return nullptr;
1164}
Austin Schuhac0771c2020-01-07 18:36:30 -08001165
Maxwell Gumleyf84aca32024-05-10 13:51:59 -06001166bool IsNodeFromConfiguration(const Configuration *config, const Node *node) {
1167 if (config == nullptr) {
1168 return false;
1169 }
1170
1171 // Check if is multinode
1172 if (MultiNode(config)) {
1173 if (node == nullptr) {
1174 return false;
1175 }
1176 for (const Node *node_from_config : *config->nodes()) {
1177 if (node_from_config == node) {
1178 return true;
1179 }
1180 }
1181 return false;
1182 } else {
1183 // nullptr is the node for all single node configurations. Return true so
1184 // this function can be used in the same way for single or multinode
1185 // configurations.
1186 return node == nullptr;
1187 }
1188}
1189
Austin Schuh63097262023-08-16 17:04:29 -07001190std::string_view NodeName(const Configuration *config, size_t node_index) {
1191 if (!configuration::MultiNode(config)) {
1192 return "(singlenode)";
1193 }
1194 return config->nodes()->Get(node_index)->name()->string_view();
1195}
1196
Austin Schuh217a9782019-12-21 23:02:50 -08001197const Node *GetMyNode(const Configuration *config) {
1198 const std::string hostname = (FLAGS_override_hostname.size() > 0)
1199 ? FLAGS_override_hostname
1200 : network::GetHostname();
1201 const Node *node = GetNodeFromHostname(config, hostname);
1202 if (node != nullptr) return node;
1203
1204 LOG(FATAL) << "Unknown node for host: " << hostname
1205 << ". Consider using --override_hostname if hostname detection "
1206 "is wrong.";
1207 return nullptr;
1208}
1209
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001210const Node *GetNode(const Configuration *config, const Node *node) {
1211 if (!MultiNode(config)) {
1212 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1213 return nullptr;
1214 } else {
1215 CHECK(node != nullptr);
1216 CHECK(node->has_name());
1217 return GetNode(config, node->name()->string_view());
1218 }
1219}
1220
Austin Schuh217a9782019-12-21 23:02:50 -08001221const Node *GetNode(const Configuration *config, std::string_view name) {
Alexei Strots4b1e1442023-05-01 22:11:05 -07001222 if (!MultiNode(config)) {
1223 if (name.empty()) {
1224 return nullptr;
1225 }
1226 LOG(FATAL) << ": Asking for a named node from a single node configuration.";
1227 }
Austin Schuh217a9782019-12-21 23:02:50 -08001228 for (const Node *node : *config->nodes()) {
Austin Schuhfd960622020-01-01 13:22:55 -08001229 CHECK(node->has_name()) << ": Malformed node " << FlatbufferToJson(node);
Austin Schuh217a9782019-12-21 23:02:50 -08001230 if (node->name()->string_view() == name) {
1231 return node;
1232 }
1233 }
1234 return nullptr;
1235}
1236
Austin Schuh0ca1fd32020-12-18 22:53:05 -08001237const Node *GetNode(const Configuration *config, size_t node_index) {
1238 if (!MultiNode(config)) {
1239 CHECK_EQ(node_index, 0u) << ": Invalid node in a single node world.";
1240 return nullptr;
1241 } else {
1242 CHECK_LT(node_index, config->nodes()->size());
1243 return config->nodes()->Get(node_index);
1244 }
1245}
1246
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001247const Node *GetNodeOrDie(const Configuration *config, const Node *node) {
1248 if (!MultiNode(config)) {
1249 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1250 return nullptr;
1251 } else {
1252 const Node *config_node = GetNode(config, node);
1253 if (config_node == nullptr) {
1254 LOG(FATAL) << "Couldn't find node matching " << FlatbufferToJson(node);
1255 }
1256 return config_node;
1257 }
1258}
1259
Austin Schuh8bd96322020-02-13 21:18:22 -08001260namespace {
1261int GetNodeIndexFromConfig(const Configuration *config, const Node *node) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001262 int node_index = 0;
1263 for (const Node *iterated_node : *config->nodes()) {
1264 if (iterated_node == node) {
1265 return node_index;
1266 }
1267 ++node_index;
1268 }
Austin Schuh8bd96322020-02-13 21:18:22 -08001269 return -1;
1270}
1271} // namespace
1272
Austin Schuha9df9ad2021-06-16 14:49:39 -07001273aos::FlatbufferDetachedBuffer<aos::Configuration> AddSchema(
1274 std::string_view json,
1275 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
1276 FlatbufferDetachedBuffer<Configuration> addition =
1277 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1278 return MergeConfiguration(addition, schemas);
1279}
1280
Austin Schuh8bd96322020-02-13 21:18:22 -08001281int GetNodeIndex(const Configuration *config, const Node *node) {
1282 if (!MultiNode(config)) {
1283 return 0;
1284 }
1285
1286 {
1287 int node_index = GetNodeIndexFromConfig(config, node);
1288 if (node_index != -1) {
1289 return node_index;
1290 }
1291 }
1292
1293 const Node *result = GetNode(config, node);
1294 CHECK(result != nullptr);
1295
1296 {
Austin Schuh04408fc2020-02-16 21:48:54 -08001297 int node_index = GetNodeIndexFromConfig(config, result);
Austin Schuh8bd96322020-02-13 21:18:22 -08001298 if (node_index != -1) {
1299 return node_index;
1300 }
1301 }
1302
1303 LOG(FATAL) << "Node " << FlatbufferToJson(node)
1304 << " not found in the configuration.";
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001305}
1306
Austin Schuh04408fc2020-02-16 21:48:54 -08001307int GetNodeIndex(const Configuration *config, std::string_view name) {
1308 if (!MultiNode(config)) {
1309 return 0;
1310 }
1311
1312 {
1313 int node_index = 0;
1314 for (const Node *iterated_node : *config->nodes()) {
1315 if (iterated_node->name()->string_view() == name) {
1316 return node_index;
1317 }
1318 ++node_index;
1319 }
1320 }
1321 LOG(FATAL) << "Node " << name << " not found in the configuration.";
1322}
1323
Austin Schuh681a2472020-12-31 23:55:40 -08001324size_t NodesCount(const Configuration *config) {
1325 if (!MultiNode(config)) {
1326 return 1u;
1327 }
1328
1329 return config->nodes()->size();
1330}
1331
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001332std::vector<const Node *> GetNodes(const Configuration *config) {
1333 std::vector<const Node *> nodes;
Austin Schuh8bd96322020-02-13 21:18:22 -08001334 if (MultiNode(config)) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001335 for (const Node *node : *config->nodes()) {
1336 nodes.emplace_back(node);
1337 }
1338 } else {
1339 nodes.emplace_back(nullptr);
1340 }
1341 return nodes;
1342}
1343
Austin Schuh65465332020-11-05 17:36:53 -08001344std::vector<const Node *> GetNodesWithTag(const Configuration *config,
1345 std::string_view tag) {
1346 std::vector<const Node *> nodes;
1347 if (!MultiNode(config)) {
1348 nodes.emplace_back(nullptr);
1349 } else {
1350 for (const Node *node : *config->nodes()) {
1351 if (!node->has_tags()) {
1352 continue;
1353 }
1354 bool did_found_tag = false;
1355 for (const flatbuffers::String *found_tag : *node->tags()) {
1356 if (found_tag->string_view() == tag) {
1357 did_found_tag = true;
1358 break;
1359 }
1360 }
1361 if (did_found_tag) {
1362 nodes.emplace_back(node);
1363 }
1364 }
1365 }
1366 return nodes;
1367}
1368
Brian Silverman631b6262021-11-10 12:25:08 -08001369bool NodeHasTag(const Node *node, std::string_view tag) {
1370 if (node == nullptr) {
1371 return true;
1372 }
1373
Austin Schuh97a52432022-08-17 15:02:59 -07001374 if (!node->has_tags()) {
1375 return false;
1376 }
1377
Brian Silverman631b6262021-11-10 12:25:08 -08001378 const auto *const tags = node->tags();
1379 return std::find_if(tags->begin(), tags->end(),
1380 [tag](const flatbuffers::String *candidate) {
1381 return candidate->string_view() == tag;
1382 }) != tags->end();
1383}
1384
Austin Schuhac0771c2020-01-07 18:36:30 -08001385bool MultiNode(const Configuration *config) { return config->has_nodes(); }
1386
Austin Schuh217a9782019-12-21 23:02:50 -08001387bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001388 if (node == nullptr) {
1389 return true;
1390 }
Austin Schuh3c5dae52020-10-06 18:55:18 -07001391 CHECK(channel->has_source_node()) << FlatbufferToJson(channel);
1392 CHECK(node->has_name()) << FlatbufferToJson(node);
Austin Schuh196a4452020-03-15 23:12:03 -07001393 return (CHECK_NOTNULL(channel)->source_node()->string_view() ==
1394 node->name()->string_view());
Austin Schuh217a9782019-12-21 23:02:50 -08001395}
1396
1397bool ChannelIsReadableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001398 if (node == nullptr) {
1399 return true;
1400 }
1401
Austin Schuh217a9782019-12-21 23:02:50 -08001402 if (channel->source_node()->string_view() == node->name()->string_view()) {
1403 return true;
1404 }
1405
1406 if (!channel->has_destination_nodes()) {
1407 return false;
1408 }
1409
Austin Schuh719946b2019-12-28 14:51:01 -08001410 for (const Connection *connection : *channel->destination_nodes()) {
1411 CHECK(connection->has_name());
1412 if (connection->name()->string_view() == node->name()->string_view()) {
Austin Schuh217a9782019-12-21 23:02:50 -08001413 return true;
1414 }
1415 }
1416
1417 return false;
1418}
1419
James Kuszmaul24db2d32023-05-26 11:40:12 -07001420bool ChannelIsForwardedFromNode(const Channel *channel, const Node *node) {
1421 if (node == nullptr) {
1422 return false;
1423 }
1424 return ChannelIsSendableOnNode(channel, node) &&
1425 channel->has_destination_nodes() &&
1426 channel->destination_nodes()->size() > 0u;
1427}
1428
Austin Schuh719946b2019-12-28 14:51:01 -08001429bool ChannelMessageIsLoggedOnNode(const Channel *channel, const Node *node) {
Austin Schuh48e94502021-06-18 18:35:53 -07001430 if (node == nullptr) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001431 // Single node world. If there is a local logger, then we want to use
1432 // it.
Austin Schuh48e94502021-06-18 18:35:53 -07001433 if (channel->logger() == LoggerConfig::LOCAL_LOGGER) {
1434 return true;
1435 } else if (channel->logger() == LoggerConfig::NOT_LOGGED) {
1436 return false;
1437 }
1438 LOG(FATAL) << "Unsupported logging configuration in a single node world: "
1439 << CleanedChannelToString(channel);
Austin Schuh2bb80e02021-03-20 21:46:17 -07001440 }
1441 return ChannelMessageIsLoggedOnNode(
1442 channel, CHECK_NOTNULL(node)->name()->string_view());
1443}
1444
1445bool ChannelMessageIsLoggedOnNode(const Channel *channel,
1446 std::string_view node_name) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001447 switch (channel->logger()) {
Austin Schuh719946b2019-12-28 14:51:01 -08001448 case LoggerConfig::LOCAL_LOGGER:
Austin Schuh2bb80e02021-03-20 21:46:17 -07001449 return channel->source_node()->string_view() == node_name;
Austin Schuh719946b2019-12-28 14:51:01 -08001450 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001451 CHECK(channel->has_logger_nodes())
1452 << "Missing logger nodes on " << StrippedChannelToString(channel);
1453 CHECK_GT(channel->logger_nodes()->size(), 0u)
1454 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuh719946b2019-12-28 14:51:01 -08001455
Austin Schuh2bb80e02021-03-20 21:46:17 -07001456 if (channel->source_node()->string_view() == node_name) {
Austin Schuh719946b2019-12-28 14:51:01 -08001457 return true;
1458 }
Austin Schuhda40e472020-03-28 15:15:29 -07001459
1460 [[fallthrough]];
1461 case LoggerConfig::REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001462 CHECK(channel->has_logger_nodes())
1463 << "Missing logger nodes on " << StrippedChannelToString(channel);
1464 CHECK_GT(channel->logger_nodes()->size(), 0u)
1465 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuhda40e472020-03-28 15:15:29 -07001466 for (const flatbuffers::String *logger_node : *channel->logger_nodes()) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001467 if (logger_node->string_view() == node_name) {
Austin Schuhda40e472020-03-28 15:15:29 -07001468 return true;
1469 }
Austin Schuh719946b2019-12-28 14:51:01 -08001470 }
1471
1472 return false;
1473 case LoggerConfig::NOT_LOGGED:
1474 return false;
1475 }
1476
1477 LOG(FATAL) << "Unknown logger config " << static_cast<int>(channel->logger());
1478}
1479
Austin Schuh58646e22021-08-23 23:51:46 -07001480size_t ConnectionCount(const Channel *channel) {
1481 if (!channel->has_destination_nodes()) {
1482 return 0;
1483 }
1484 return channel->destination_nodes()->size();
1485}
1486
Austin Schuh719946b2019-12-28 14:51:01 -08001487const Connection *ConnectionToNode(const Channel *channel, const Node *node) {
1488 if (!channel->has_destination_nodes()) {
1489 return nullptr;
1490 }
1491 for (const Connection *connection : *channel->destination_nodes()) {
1492 if (connection->name()->string_view() == node->name()->string_view()) {
1493 return connection;
1494 }
1495 }
1496 return nullptr;
1497}
1498
1499bool ConnectionDeliveryTimeIsLoggedOnNode(const Channel *channel,
1500 const Node *node,
1501 const Node *logger_node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001502 return ConnectionDeliveryTimeIsLoggedOnNode(ConnectionToNode(channel, node),
1503 logger_node);
Austin Schuh719946b2019-12-28 14:51:01 -08001504}
1505
1506bool ConnectionDeliveryTimeIsLoggedOnNode(const Connection *connection,
1507 const Node *node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001508 if (connection == nullptr) {
1509 return false;
1510 }
Austin Schuh719946b2019-12-28 14:51:01 -08001511 switch (connection->timestamp_logger()) {
1512 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
Austin Schuhda40e472020-03-28 15:15:29 -07001513 CHECK(connection->has_timestamp_logger_nodes());
1514 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
Austin Schuh719946b2019-12-28 14:51:01 -08001515 if (connection->name()->string_view() == node->name()->string_view()) {
1516 return true;
1517 }
1518
Austin Schuhda40e472020-03-28 15:15:29 -07001519 [[fallthrough]];
1520 case LoggerConfig::REMOTE_LOGGER:
1521 CHECK(connection->has_timestamp_logger_nodes());
1522 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1523 for (const flatbuffers::String *timestamp_logger_node :
1524 *connection->timestamp_logger_nodes()) {
1525 if (timestamp_logger_node->string_view() ==
1526 node->name()->string_view()) {
1527 return true;
1528 }
Austin Schuh719946b2019-12-28 14:51:01 -08001529 }
1530
1531 return false;
1532 case LoggerConfig::LOCAL_LOGGER:
1533 return connection->name()->string_view() == node->name()->string_view();
Austin Schuh719946b2019-12-28 14:51:01 -08001534 case LoggerConfig::NOT_LOGGED:
1535 return false;
1536 }
1537
1538 LOG(FATAL) << "Unknown logger config "
1539 << static_cast<int>(connection->timestamp_logger());
1540}
1541
Austin Schuhe84c3ed2019-12-14 15:29:48 -08001542std::vector<std::string_view> SourceNodeNames(const Configuration *config,
1543 const Node *my_node) {
1544 std::set<std::string_view> result_set;
1545
1546 for (const Channel *channel : *config->channels()) {
1547 if (channel->has_destination_nodes()) {
1548 for (const Connection *connection : *channel->destination_nodes()) {
1549 if (connection->name()->string_view() ==
1550 my_node->name()->string_view()) {
1551 result_set.insert(channel->source_node()->string_view());
1552 }
1553 }
1554 }
1555 }
1556
1557 std::vector<std::string_view> result;
1558 for (const std::string_view source : result_set) {
1559 VLOG(1) << "Found a source node of " << source;
1560 result.emplace_back(source);
1561 }
1562 return result;
1563}
1564
1565std::vector<std::string_view> DestinationNodeNames(const Configuration *config,
1566 const Node *my_node) {
1567 std::vector<std::string_view> result;
1568
1569 for (const Channel *channel : *config->channels()) {
1570 if (channel->has_source_node() && channel->source_node()->string_view() ==
1571 my_node->name()->string_view()) {
1572 if (!channel->has_destination_nodes()) continue;
1573
1574 if (channel->source_node()->string_view() !=
1575 my_node->name()->string_view()) {
1576 continue;
1577 }
1578
1579 for (const Connection *connection : *channel->destination_nodes()) {
1580 if (std::find(result.begin(), result.end(),
1581 connection->name()->string_view()) == result.end()) {
1582 result.emplace_back(connection->name()->string_view());
1583 }
1584 }
1585 }
1586 }
1587
1588 for (const std::string_view destination : result) {
1589 VLOG(1) << "Found a destination node of " << destination;
1590 }
1591 return result;
1592}
1593
Austin Schuh8d7e0bb2020-10-02 17:57:00 -07001594std::vector<const Node *> TimestampNodes(const Configuration *config,
1595 const Node *my_node) {
1596 if (!configuration::MultiNode(config)) {
1597 CHECK(my_node == nullptr);
1598 return std::vector<const Node *>{};
1599 }
1600
1601 std::set<const Node *> timestamp_logger_nodes;
1602 for (const Channel *channel : *config->channels()) {
1603 if (!configuration::ChannelIsSendableOnNode(channel, my_node)) {
1604 continue;
1605 }
1606 if (!channel->has_destination_nodes()) {
1607 continue;
1608 }
1609 for (const Connection *connection : *channel->destination_nodes()) {
1610 const Node *other_node =
1611 configuration::GetNode(config, connection->name()->string_view());
1612
1613 if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(connection,
1614 my_node)) {
1615 VLOG(1) << "Timestamps are logged from "
1616 << FlatbufferToJson(other_node);
1617 timestamp_logger_nodes.insert(other_node);
1618 }
1619 }
1620 }
1621
1622 std::vector<const Node *> result;
1623 for (const Node *node : timestamp_logger_nodes) {
1624 result.emplace_back(node);
1625 }
1626 return result;
1627}
1628
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001629bool ApplicationShouldStart(const Configuration *config, const Node *my_node,
1630 const Application *application) {
1631 if (MultiNode(config)) {
1632 // Ok, we need
1633 CHECK(application->has_nodes());
1634 CHECK(my_node != nullptr);
1635 for (const flatbuffers::String *str : *application->nodes()) {
1636 if (str->string_view() == my_node->name()->string_view()) {
1637 return true;
1638 }
1639 }
1640 return false;
1641 } else {
1642 return true;
1643 }
1644}
1645
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001646const Application *GetApplication(const Configuration *config,
1647 const Node *my_node,
1648 std::string_view application_name) {
1649 if (config->has_applications()) {
1650 auto application_iterator = std::lower_bound(
1651 config->applications()->cbegin(), config->applications()->cend(),
1652 application_name, CompareApplications);
1653 if (application_iterator != config->applications()->cend() &&
1654 EqualsApplications(*application_iterator, application_name)) {
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001655 if (ApplicationShouldStart(config, my_node, *application_iterator)) {
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001656 return *application_iterator;
1657 }
1658 }
1659 }
1660 return nullptr;
1661}
1662
Austin Schuh1ccc3a12024-04-30 17:46:29 -07001663const Node *SourceNode(const Configuration *config, const Channel *channel) {
1664 if (!MultiNode(config)) {
1665 return nullptr;
1666 }
1667 return GetNode(config, channel->source_node()->string_view());
1668}
1669
Austin Schuhfc7b6a02021-07-12 21:19:07 -07001670std::vector<size_t> SourceNodeIndex(const Configuration *config) {
1671 CHECK(config->has_channels());
1672 std::vector<size_t> result;
1673 result.resize(config->channels()->size(), 0u);
1674 if (MultiNode(config)) {
1675 for (size_t i = 0; i < config->channels()->size(); ++i) {
1676 result[i] = GetNodeIndex(
1677 config, config->channels()->Get(i)->source_node()->string_view());
1678 }
1679 }
1680 return result;
1681}
1682
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001683chrono::nanoseconds ChannelStorageDuration(const Configuration *config,
1684 const Channel *channel) {
1685 CHECK(channel != nullptr);
Austin Schuhdda6db72023-06-21 17:02:34 -07001686 if (channel->has_channel_storage_duration()) {
1687 return chrono::nanoseconds(channel->channel_storage_duration());
1688 }
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001689 return chrono::nanoseconds(config->channel_storage_duration());
1690}
1691
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001692size_t QueueSize(const Configuration *config, const Channel *channel) {
Austin Schuhfb37c612022-08-11 15:24:51 -07001693 return QueueSize(channel->frequency(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001694 ChannelStorageDuration(config, channel));
Austin Schuhfb37c612022-08-11 15:24:51 -07001695}
1696
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001697size_t QueueSize(size_t frequency,
1698 chrono::nanoseconds channel_storage_duration) {
Austin Schuhfb37c612022-08-11 15:24:51 -07001699 // Use integer arithmetic and round up at all cost.
1700 return static_cast<int>(
Philipp Schrader0434f902023-09-06 08:41:32 -07001701 (999'999'999 +
1702 static_cast<int64_t>(frequency) *
1703 static_cast<int64_t>(channel_storage_duration.count())) /
1704 static_cast<int64_t>(1'000'000'000));
Austin Schuhfb37c612022-08-11 15:24:51 -07001705}
1706
1707int QueueScratchBufferSize(const Channel *channel) {
1708 return channel->num_readers() + channel->num_senders();
1709}
1710
Nathan Leong307c9692022-10-08 15:25:03 -07001711// Searches through configurations for schemas that include a certain type
1712const reflection::Schema *GetSchema(const Configuration *config,
1713 std::string_view schema_type) {
1714 if (config->has_channels()) {
1715 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
1716 for (const Channel *c : *config->channels()) {
1717 if (schema_type == c->type()->string_view()) {
1718 return c->schema();
1719 }
1720 }
1721 }
1722 return nullptr;
1723}
1724
1725// Copy schema reflection into detached flatbuffer
1726std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
1727GetSchemaDetachedBuffer(const Configuration *config,
1728 std::string_view schema_type) {
1729 const reflection::Schema *found_schema = GetSchema(config, schema_type);
1730 if (found_schema == nullptr) {
1731 return std::nullopt;
1732 }
1733 return RecursiveCopyFlatBuffer(found_schema);
1734}
1735
James Kuszmaul741a4d02023-01-05 14:59:21 -08001736aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
1737 const Configuration *config, std::string_view name,
1738 aos::FlatbufferVector<reflection::Schema> schema, const aos::Node *node,
1739 ChannelT overrides) {
1740 overrides.name = name;
James Kuszmaul80d6c422023-01-06 14:16:04 -08001741 CHECK(schema.message().has_root_table());
James Kuszmaul741a4d02023-01-05 14:59:21 -08001742 overrides.type = schema.message().root_table()->name()->string_view();
1743 if (node != nullptr) {
1744 CHECK(node->has_name());
1745 overrides.source_node = node->name()->string_view();
1746 }
1747 flatbuffers::FlatBufferBuilder fbb;
1748 // Don't populate fields from overrides that the user doesn't explicitly
1749 // override.
1750 fbb.ForceDefaults(false);
1751 const flatbuffers::Offset<Channel> channel_offset =
1752 Channel::Pack(fbb, &overrides);
1753 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
1754 channels_offset = fbb.CreateVector({channel_offset});
1755 Configuration::Builder config_builder(fbb);
1756 config_builder.add_channels(channels_offset);
1757 fbb.Finish(config_builder.Finish());
1758 FlatbufferDetachedBuffer<Configuration> new_channel_config = fbb.Release();
1759 new_channel_config = MergeConfiguration(new_channel_config, {schema});
1760 return MergeWithConfig(config, new_channel_config);
1761}
1762
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07001763FlatbufferDetachedBuffer<Configuration> GetPartialConfiguration(
1764 const Configuration &configuration,
1765 std::function<bool(const Channel &)> should_include_channel) {
1766 // create new_configuration1, containing everything except the `channels`
1767 // field.
1768 FlatbufferDetachedBuffer<Configuration> new_configuration1 =
1769 RecursiveCopyFlatBuffer(&configuration);
1770 new_configuration1.mutable_message()->clear_channels();
1771
1772 // create new_configuration2, containing only the `channels` field.
1773 flatbuffers::FlatBufferBuilder fbb;
1774 std::vector<flatbuffers::Offset<Channel>> new_channels_vec;
1775 for (const auto &channel : *configuration.channels()) {
1776 CHECK_NOTNULL(channel);
1777 if (should_include_channel(*channel)) {
1778 new_channels_vec.push_back(RecursiveCopyFlatBuffer(channel, &fbb));
1779 }
1780 }
1781 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
1782 new_channels_offset = fbb.CreateVector(new_channels_vec);
1783 Configuration::Builder new_configuration2_builder(fbb);
1784 new_configuration2_builder.add_channels(new_channels_offset);
1785 fbb.Finish(new_configuration2_builder.Finish());
1786 FlatbufferDetachedBuffer<Configuration> new_configuration2 = fbb.Release();
1787
1788 // Merge the configuration containing channels with the configuration
1789 // containing everything else, creating a complete configuration.
1790 const aos::FlatbufferDetachedBuffer<Configuration> raw_subset_configuration =
1791 MergeFlatBuffers(&new_configuration1.message(),
1792 &new_configuration2.message());
1793
1794 // Use MergeConfiguration to clean up redundant schemas.
1795 return configuration::MergeConfiguration(raw_subset_configuration);
1796}
Brian Silverman66f079a2013-08-26 16:24:30 -07001797} // namespace configuration
1798} // namespace aos