blob: 3b50ab40bc181c022a7a9905f3eaf1850e0c0299 [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
Austin Schuh63097262023-08-16 17:04:29 -07001166std::string_view NodeName(const Configuration *config, size_t node_index) {
1167 if (!configuration::MultiNode(config)) {
1168 return "(singlenode)";
1169 }
1170 return config->nodes()->Get(node_index)->name()->string_view();
1171}
1172
Austin Schuh217a9782019-12-21 23:02:50 -08001173const Node *GetMyNode(const Configuration *config) {
1174 const std::string hostname = (FLAGS_override_hostname.size() > 0)
1175 ? FLAGS_override_hostname
1176 : network::GetHostname();
1177 const Node *node = GetNodeFromHostname(config, hostname);
1178 if (node != nullptr) return node;
1179
1180 LOG(FATAL) << "Unknown node for host: " << hostname
1181 << ". Consider using --override_hostname if hostname detection "
1182 "is wrong.";
1183 return nullptr;
1184}
1185
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001186const Node *GetNode(const Configuration *config, const Node *node) {
1187 if (!MultiNode(config)) {
1188 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1189 return nullptr;
1190 } else {
1191 CHECK(node != nullptr);
1192 CHECK(node->has_name());
1193 return GetNode(config, node->name()->string_view());
1194 }
1195}
1196
Austin Schuh217a9782019-12-21 23:02:50 -08001197const Node *GetNode(const Configuration *config, std::string_view name) {
Alexei Strots4b1e1442023-05-01 22:11:05 -07001198 if (!MultiNode(config)) {
1199 if (name.empty()) {
1200 return nullptr;
1201 }
1202 LOG(FATAL) << ": Asking for a named node from a single node configuration.";
1203 }
Austin Schuh217a9782019-12-21 23:02:50 -08001204 for (const Node *node : *config->nodes()) {
Austin Schuhfd960622020-01-01 13:22:55 -08001205 CHECK(node->has_name()) << ": Malformed node " << FlatbufferToJson(node);
Austin Schuh217a9782019-12-21 23:02:50 -08001206 if (node->name()->string_view() == name) {
1207 return node;
1208 }
1209 }
1210 return nullptr;
1211}
1212
Austin Schuh0ca1fd32020-12-18 22:53:05 -08001213const Node *GetNode(const Configuration *config, size_t node_index) {
1214 if (!MultiNode(config)) {
1215 CHECK_EQ(node_index, 0u) << ": Invalid node in a single node world.";
1216 return nullptr;
1217 } else {
1218 CHECK_LT(node_index, config->nodes()->size());
1219 return config->nodes()->Get(node_index);
1220 }
1221}
1222
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001223const Node *GetNodeOrDie(const Configuration *config, const Node *node) {
1224 if (!MultiNode(config)) {
1225 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1226 return nullptr;
1227 } else {
1228 const Node *config_node = GetNode(config, node);
1229 if (config_node == nullptr) {
1230 LOG(FATAL) << "Couldn't find node matching " << FlatbufferToJson(node);
1231 }
1232 return config_node;
1233 }
1234}
1235
Austin Schuh8bd96322020-02-13 21:18:22 -08001236namespace {
1237int GetNodeIndexFromConfig(const Configuration *config, const Node *node) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001238 int node_index = 0;
1239 for (const Node *iterated_node : *config->nodes()) {
1240 if (iterated_node == node) {
1241 return node_index;
1242 }
1243 ++node_index;
1244 }
Austin Schuh8bd96322020-02-13 21:18:22 -08001245 return -1;
1246}
1247} // namespace
1248
Austin Schuha9df9ad2021-06-16 14:49:39 -07001249aos::FlatbufferDetachedBuffer<aos::Configuration> AddSchema(
1250 std::string_view json,
1251 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
1252 FlatbufferDetachedBuffer<Configuration> addition =
1253 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1254 return MergeConfiguration(addition, schemas);
1255}
1256
Austin Schuh8bd96322020-02-13 21:18:22 -08001257int GetNodeIndex(const Configuration *config, const Node *node) {
1258 if (!MultiNode(config)) {
1259 return 0;
1260 }
1261
1262 {
1263 int node_index = GetNodeIndexFromConfig(config, node);
1264 if (node_index != -1) {
1265 return node_index;
1266 }
1267 }
1268
1269 const Node *result = GetNode(config, node);
1270 CHECK(result != nullptr);
1271
1272 {
Austin Schuh04408fc2020-02-16 21:48:54 -08001273 int node_index = GetNodeIndexFromConfig(config, result);
Austin Schuh8bd96322020-02-13 21:18:22 -08001274 if (node_index != -1) {
1275 return node_index;
1276 }
1277 }
1278
1279 LOG(FATAL) << "Node " << FlatbufferToJson(node)
1280 << " not found in the configuration.";
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001281}
1282
Austin Schuh04408fc2020-02-16 21:48:54 -08001283int GetNodeIndex(const Configuration *config, std::string_view name) {
1284 if (!MultiNode(config)) {
1285 return 0;
1286 }
1287
1288 {
1289 int node_index = 0;
1290 for (const Node *iterated_node : *config->nodes()) {
1291 if (iterated_node->name()->string_view() == name) {
1292 return node_index;
1293 }
1294 ++node_index;
1295 }
1296 }
1297 LOG(FATAL) << "Node " << name << " not found in the configuration.";
1298}
1299
Austin Schuh681a2472020-12-31 23:55:40 -08001300size_t NodesCount(const Configuration *config) {
1301 if (!MultiNode(config)) {
1302 return 1u;
1303 }
1304
1305 return config->nodes()->size();
1306}
1307
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001308std::vector<const Node *> GetNodes(const Configuration *config) {
1309 std::vector<const Node *> nodes;
Austin Schuh8bd96322020-02-13 21:18:22 -08001310 if (MultiNode(config)) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001311 for (const Node *node : *config->nodes()) {
1312 nodes.emplace_back(node);
1313 }
1314 } else {
1315 nodes.emplace_back(nullptr);
1316 }
1317 return nodes;
1318}
1319
Austin Schuh65465332020-11-05 17:36:53 -08001320std::vector<const Node *> GetNodesWithTag(const Configuration *config,
1321 std::string_view tag) {
1322 std::vector<const Node *> nodes;
1323 if (!MultiNode(config)) {
1324 nodes.emplace_back(nullptr);
1325 } else {
1326 for (const Node *node : *config->nodes()) {
1327 if (!node->has_tags()) {
1328 continue;
1329 }
1330 bool did_found_tag = false;
1331 for (const flatbuffers::String *found_tag : *node->tags()) {
1332 if (found_tag->string_view() == tag) {
1333 did_found_tag = true;
1334 break;
1335 }
1336 }
1337 if (did_found_tag) {
1338 nodes.emplace_back(node);
1339 }
1340 }
1341 }
1342 return nodes;
1343}
1344
Brian Silverman631b6262021-11-10 12:25:08 -08001345bool NodeHasTag(const Node *node, std::string_view tag) {
1346 if (node == nullptr) {
1347 return true;
1348 }
1349
Austin Schuh97a52432022-08-17 15:02:59 -07001350 if (!node->has_tags()) {
1351 return false;
1352 }
1353
Brian Silverman631b6262021-11-10 12:25:08 -08001354 const auto *const tags = node->tags();
1355 return std::find_if(tags->begin(), tags->end(),
1356 [tag](const flatbuffers::String *candidate) {
1357 return candidate->string_view() == tag;
1358 }) != tags->end();
1359}
1360
Austin Schuhac0771c2020-01-07 18:36:30 -08001361bool MultiNode(const Configuration *config) { return config->has_nodes(); }
1362
Austin Schuh217a9782019-12-21 23:02:50 -08001363bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001364 if (node == nullptr) {
1365 return true;
1366 }
Austin Schuh3c5dae52020-10-06 18:55:18 -07001367 CHECK(channel->has_source_node()) << FlatbufferToJson(channel);
1368 CHECK(node->has_name()) << FlatbufferToJson(node);
Austin Schuh196a4452020-03-15 23:12:03 -07001369 return (CHECK_NOTNULL(channel)->source_node()->string_view() ==
1370 node->name()->string_view());
Austin Schuh217a9782019-12-21 23:02:50 -08001371}
1372
1373bool ChannelIsReadableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001374 if (node == nullptr) {
1375 return true;
1376 }
1377
Austin Schuh217a9782019-12-21 23:02:50 -08001378 if (channel->source_node()->string_view() == node->name()->string_view()) {
1379 return true;
1380 }
1381
1382 if (!channel->has_destination_nodes()) {
1383 return false;
1384 }
1385
Austin Schuh719946b2019-12-28 14:51:01 -08001386 for (const Connection *connection : *channel->destination_nodes()) {
1387 CHECK(connection->has_name());
1388 if (connection->name()->string_view() == node->name()->string_view()) {
Austin Schuh217a9782019-12-21 23:02:50 -08001389 return true;
1390 }
1391 }
1392
1393 return false;
1394}
1395
James Kuszmaul24db2d32023-05-26 11:40:12 -07001396bool ChannelIsForwardedFromNode(const Channel *channel, const Node *node) {
1397 if (node == nullptr) {
1398 return false;
1399 }
1400 return ChannelIsSendableOnNode(channel, node) &&
1401 channel->has_destination_nodes() &&
1402 channel->destination_nodes()->size() > 0u;
1403}
1404
Austin Schuh719946b2019-12-28 14:51:01 -08001405bool ChannelMessageIsLoggedOnNode(const Channel *channel, const Node *node) {
Austin Schuh48e94502021-06-18 18:35:53 -07001406 if (node == nullptr) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001407 // Single node world. If there is a local logger, then we want to use
1408 // it.
Austin Schuh48e94502021-06-18 18:35:53 -07001409 if (channel->logger() == LoggerConfig::LOCAL_LOGGER) {
1410 return true;
1411 } else if (channel->logger() == LoggerConfig::NOT_LOGGED) {
1412 return false;
1413 }
1414 LOG(FATAL) << "Unsupported logging configuration in a single node world: "
1415 << CleanedChannelToString(channel);
Austin Schuh2bb80e02021-03-20 21:46:17 -07001416 }
1417 return ChannelMessageIsLoggedOnNode(
1418 channel, CHECK_NOTNULL(node)->name()->string_view());
1419}
1420
1421bool ChannelMessageIsLoggedOnNode(const Channel *channel,
1422 std::string_view node_name) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001423 switch (channel->logger()) {
Austin Schuh719946b2019-12-28 14:51:01 -08001424 case LoggerConfig::LOCAL_LOGGER:
Austin Schuh2bb80e02021-03-20 21:46:17 -07001425 return channel->source_node()->string_view() == node_name;
Austin Schuh719946b2019-12-28 14:51:01 -08001426 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001427 CHECK(channel->has_logger_nodes())
1428 << "Missing logger nodes on " << StrippedChannelToString(channel);
1429 CHECK_GT(channel->logger_nodes()->size(), 0u)
1430 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuh719946b2019-12-28 14:51:01 -08001431
Austin Schuh2bb80e02021-03-20 21:46:17 -07001432 if (channel->source_node()->string_view() == node_name) {
Austin Schuh719946b2019-12-28 14:51:01 -08001433 return true;
1434 }
Austin Schuhda40e472020-03-28 15:15:29 -07001435
1436 [[fallthrough]];
1437 case LoggerConfig::REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001438 CHECK(channel->has_logger_nodes())
1439 << "Missing logger nodes on " << StrippedChannelToString(channel);
1440 CHECK_GT(channel->logger_nodes()->size(), 0u)
1441 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuhda40e472020-03-28 15:15:29 -07001442 for (const flatbuffers::String *logger_node : *channel->logger_nodes()) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001443 if (logger_node->string_view() == node_name) {
Austin Schuhda40e472020-03-28 15:15:29 -07001444 return true;
1445 }
Austin Schuh719946b2019-12-28 14:51:01 -08001446 }
1447
1448 return false;
1449 case LoggerConfig::NOT_LOGGED:
1450 return false;
1451 }
1452
1453 LOG(FATAL) << "Unknown logger config " << static_cast<int>(channel->logger());
1454}
1455
Austin Schuh58646e22021-08-23 23:51:46 -07001456size_t ConnectionCount(const Channel *channel) {
1457 if (!channel->has_destination_nodes()) {
1458 return 0;
1459 }
1460 return channel->destination_nodes()->size();
1461}
1462
Austin Schuh719946b2019-12-28 14:51:01 -08001463const Connection *ConnectionToNode(const Channel *channel, const Node *node) {
1464 if (!channel->has_destination_nodes()) {
1465 return nullptr;
1466 }
1467 for (const Connection *connection : *channel->destination_nodes()) {
1468 if (connection->name()->string_view() == node->name()->string_view()) {
1469 return connection;
1470 }
1471 }
1472 return nullptr;
1473}
1474
1475bool ConnectionDeliveryTimeIsLoggedOnNode(const Channel *channel,
1476 const Node *node,
1477 const Node *logger_node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001478 return ConnectionDeliveryTimeIsLoggedOnNode(ConnectionToNode(channel, node),
1479 logger_node);
Austin Schuh719946b2019-12-28 14:51:01 -08001480}
1481
1482bool ConnectionDeliveryTimeIsLoggedOnNode(const Connection *connection,
1483 const Node *node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001484 if (connection == nullptr) {
1485 return false;
1486 }
Austin Schuh719946b2019-12-28 14:51:01 -08001487 switch (connection->timestamp_logger()) {
1488 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
Austin Schuhda40e472020-03-28 15:15:29 -07001489 CHECK(connection->has_timestamp_logger_nodes());
1490 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
Austin Schuh719946b2019-12-28 14:51:01 -08001491 if (connection->name()->string_view() == node->name()->string_view()) {
1492 return true;
1493 }
1494
Austin Schuhda40e472020-03-28 15:15:29 -07001495 [[fallthrough]];
1496 case LoggerConfig::REMOTE_LOGGER:
1497 CHECK(connection->has_timestamp_logger_nodes());
1498 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1499 for (const flatbuffers::String *timestamp_logger_node :
1500 *connection->timestamp_logger_nodes()) {
1501 if (timestamp_logger_node->string_view() ==
1502 node->name()->string_view()) {
1503 return true;
1504 }
Austin Schuh719946b2019-12-28 14:51:01 -08001505 }
1506
1507 return false;
1508 case LoggerConfig::LOCAL_LOGGER:
1509 return connection->name()->string_view() == node->name()->string_view();
Austin Schuh719946b2019-12-28 14:51:01 -08001510 case LoggerConfig::NOT_LOGGED:
1511 return false;
1512 }
1513
1514 LOG(FATAL) << "Unknown logger config "
1515 << static_cast<int>(connection->timestamp_logger());
1516}
1517
Austin Schuhe84c3ed2019-12-14 15:29:48 -08001518std::vector<std::string_view> SourceNodeNames(const Configuration *config,
1519 const Node *my_node) {
1520 std::set<std::string_view> result_set;
1521
1522 for (const Channel *channel : *config->channels()) {
1523 if (channel->has_destination_nodes()) {
1524 for (const Connection *connection : *channel->destination_nodes()) {
1525 if (connection->name()->string_view() ==
1526 my_node->name()->string_view()) {
1527 result_set.insert(channel->source_node()->string_view());
1528 }
1529 }
1530 }
1531 }
1532
1533 std::vector<std::string_view> result;
1534 for (const std::string_view source : result_set) {
1535 VLOG(1) << "Found a source node of " << source;
1536 result.emplace_back(source);
1537 }
1538 return result;
1539}
1540
1541std::vector<std::string_view> DestinationNodeNames(const Configuration *config,
1542 const Node *my_node) {
1543 std::vector<std::string_view> result;
1544
1545 for (const Channel *channel : *config->channels()) {
1546 if (channel->has_source_node() && channel->source_node()->string_view() ==
1547 my_node->name()->string_view()) {
1548 if (!channel->has_destination_nodes()) continue;
1549
1550 if (channel->source_node()->string_view() !=
1551 my_node->name()->string_view()) {
1552 continue;
1553 }
1554
1555 for (const Connection *connection : *channel->destination_nodes()) {
1556 if (std::find(result.begin(), result.end(),
1557 connection->name()->string_view()) == result.end()) {
1558 result.emplace_back(connection->name()->string_view());
1559 }
1560 }
1561 }
1562 }
1563
1564 for (const std::string_view destination : result) {
1565 VLOG(1) << "Found a destination node of " << destination;
1566 }
1567 return result;
1568}
1569
Austin Schuh8d7e0bb2020-10-02 17:57:00 -07001570std::vector<const Node *> TimestampNodes(const Configuration *config,
1571 const Node *my_node) {
1572 if (!configuration::MultiNode(config)) {
1573 CHECK(my_node == nullptr);
1574 return std::vector<const Node *>{};
1575 }
1576
1577 std::set<const Node *> timestamp_logger_nodes;
1578 for (const Channel *channel : *config->channels()) {
1579 if (!configuration::ChannelIsSendableOnNode(channel, my_node)) {
1580 continue;
1581 }
1582 if (!channel->has_destination_nodes()) {
1583 continue;
1584 }
1585 for (const Connection *connection : *channel->destination_nodes()) {
1586 const Node *other_node =
1587 configuration::GetNode(config, connection->name()->string_view());
1588
1589 if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(connection,
1590 my_node)) {
1591 VLOG(1) << "Timestamps are logged from "
1592 << FlatbufferToJson(other_node);
1593 timestamp_logger_nodes.insert(other_node);
1594 }
1595 }
1596 }
1597
1598 std::vector<const Node *> result;
1599 for (const Node *node : timestamp_logger_nodes) {
1600 result.emplace_back(node);
1601 }
1602 return result;
1603}
1604
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001605bool ApplicationShouldStart(const Configuration *config, const Node *my_node,
1606 const Application *application) {
1607 if (MultiNode(config)) {
1608 // Ok, we need
1609 CHECK(application->has_nodes());
1610 CHECK(my_node != nullptr);
1611 for (const flatbuffers::String *str : *application->nodes()) {
1612 if (str->string_view() == my_node->name()->string_view()) {
1613 return true;
1614 }
1615 }
1616 return false;
1617 } else {
1618 return true;
1619 }
1620}
1621
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001622const Application *GetApplication(const Configuration *config,
1623 const Node *my_node,
1624 std::string_view application_name) {
1625 if (config->has_applications()) {
1626 auto application_iterator = std::lower_bound(
1627 config->applications()->cbegin(), config->applications()->cend(),
1628 application_name, CompareApplications);
1629 if (application_iterator != config->applications()->cend() &&
1630 EqualsApplications(*application_iterator, application_name)) {
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001631 if (ApplicationShouldStart(config, my_node, *application_iterator)) {
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001632 return *application_iterator;
1633 }
1634 }
1635 }
1636 return nullptr;
1637}
1638
Austin Schuh1ccc3a12024-04-30 17:46:29 -07001639const Node *SourceNode(const Configuration *config, const Channel *channel) {
1640 if (!MultiNode(config)) {
1641 return nullptr;
1642 }
1643 return GetNode(config, channel->source_node()->string_view());
1644}
1645
Austin Schuhfc7b6a02021-07-12 21:19:07 -07001646std::vector<size_t> SourceNodeIndex(const Configuration *config) {
1647 CHECK(config->has_channels());
1648 std::vector<size_t> result;
1649 result.resize(config->channels()->size(), 0u);
1650 if (MultiNode(config)) {
1651 for (size_t i = 0; i < config->channels()->size(); ++i) {
1652 result[i] = GetNodeIndex(
1653 config, config->channels()->Get(i)->source_node()->string_view());
1654 }
1655 }
1656 return result;
1657}
1658
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001659chrono::nanoseconds ChannelStorageDuration(const Configuration *config,
1660 const Channel *channel) {
1661 CHECK(channel != nullptr);
Austin Schuhdda6db72023-06-21 17:02:34 -07001662 if (channel->has_channel_storage_duration()) {
1663 return chrono::nanoseconds(channel->channel_storage_duration());
1664 }
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001665 return chrono::nanoseconds(config->channel_storage_duration());
1666}
1667
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001668size_t QueueSize(const Configuration *config, const Channel *channel) {
Austin Schuhfb37c612022-08-11 15:24:51 -07001669 return QueueSize(channel->frequency(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001670 ChannelStorageDuration(config, channel));
Austin Schuhfb37c612022-08-11 15:24:51 -07001671}
1672
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001673size_t QueueSize(size_t frequency,
1674 chrono::nanoseconds channel_storage_duration) {
Austin Schuhfb37c612022-08-11 15:24:51 -07001675 // Use integer arithmetic and round up at all cost.
1676 return static_cast<int>(
Philipp Schrader0434f902023-09-06 08:41:32 -07001677 (999'999'999 +
1678 static_cast<int64_t>(frequency) *
1679 static_cast<int64_t>(channel_storage_duration.count())) /
1680 static_cast<int64_t>(1'000'000'000));
Austin Schuhfb37c612022-08-11 15:24:51 -07001681}
1682
1683int QueueScratchBufferSize(const Channel *channel) {
1684 return channel->num_readers() + channel->num_senders();
1685}
1686
Nathan Leong307c9692022-10-08 15:25:03 -07001687// Searches through configurations for schemas that include a certain type
1688const reflection::Schema *GetSchema(const Configuration *config,
1689 std::string_view schema_type) {
1690 if (config->has_channels()) {
1691 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
1692 for (const Channel *c : *config->channels()) {
1693 if (schema_type == c->type()->string_view()) {
1694 return c->schema();
1695 }
1696 }
1697 }
1698 return nullptr;
1699}
1700
1701// Copy schema reflection into detached flatbuffer
1702std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
1703GetSchemaDetachedBuffer(const Configuration *config,
1704 std::string_view schema_type) {
1705 const reflection::Schema *found_schema = GetSchema(config, schema_type);
1706 if (found_schema == nullptr) {
1707 return std::nullopt;
1708 }
1709 return RecursiveCopyFlatBuffer(found_schema);
1710}
1711
James Kuszmaul741a4d02023-01-05 14:59:21 -08001712aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
1713 const Configuration *config, std::string_view name,
1714 aos::FlatbufferVector<reflection::Schema> schema, const aos::Node *node,
1715 ChannelT overrides) {
1716 overrides.name = name;
James Kuszmaul80d6c422023-01-06 14:16:04 -08001717 CHECK(schema.message().has_root_table());
James Kuszmaul741a4d02023-01-05 14:59:21 -08001718 overrides.type = schema.message().root_table()->name()->string_view();
1719 if (node != nullptr) {
1720 CHECK(node->has_name());
1721 overrides.source_node = node->name()->string_view();
1722 }
1723 flatbuffers::FlatBufferBuilder fbb;
1724 // Don't populate fields from overrides that the user doesn't explicitly
1725 // override.
1726 fbb.ForceDefaults(false);
1727 const flatbuffers::Offset<Channel> channel_offset =
1728 Channel::Pack(fbb, &overrides);
1729 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
1730 channels_offset = fbb.CreateVector({channel_offset});
1731 Configuration::Builder config_builder(fbb);
1732 config_builder.add_channels(channels_offset);
1733 fbb.Finish(config_builder.Finish());
1734 FlatbufferDetachedBuffer<Configuration> new_channel_config = fbb.Release();
1735 new_channel_config = MergeConfiguration(new_channel_config, {schema});
1736 return MergeWithConfig(config, new_channel_config);
1737}
1738
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07001739FlatbufferDetachedBuffer<Configuration> GetPartialConfiguration(
1740 const Configuration &configuration,
1741 std::function<bool(const Channel &)> should_include_channel) {
1742 // create new_configuration1, containing everything except the `channels`
1743 // field.
1744 FlatbufferDetachedBuffer<Configuration> new_configuration1 =
1745 RecursiveCopyFlatBuffer(&configuration);
1746 new_configuration1.mutable_message()->clear_channels();
1747
1748 // create new_configuration2, containing only the `channels` field.
1749 flatbuffers::FlatBufferBuilder fbb;
1750 std::vector<flatbuffers::Offset<Channel>> new_channels_vec;
1751 for (const auto &channel : *configuration.channels()) {
1752 CHECK_NOTNULL(channel);
1753 if (should_include_channel(*channel)) {
1754 new_channels_vec.push_back(RecursiveCopyFlatBuffer(channel, &fbb));
1755 }
1756 }
1757 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
1758 new_channels_offset = fbb.CreateVector(new_channels_vec);
1759 Configuration::Builder new_configuration2_builder(fbb);
1760 new_configuration2_builder.add_channels(new_channels_offset);
1761 fbb.Finish(new_configuration2_builder.Finish());
1762 FlatbufferDetachedBuffer<Configuration> new_configuration2 = fbb.Release();
1763
1764 // Merge the configuration containing channels with the configuration
1765 // containing everything else, creating a complete configuration.
1766 const aos::FlatbufferDetachedBuffer<Configuration> raw_subset_configuration =
1767 MergeFlatBuffers(&new_configuration1.message(),
1768 &new_configuration2.message());
1769
1770 // Use MergeConfiguration to clean up redundant schemas.
1771 return configuration::MergeConfiguration(raw_subset_configuration);
1772}
Brian Silverman66f079a2013-08-26 16:24:30 -07001773} // namespace configuration
1774} // namespace aos