blob: fd446c2bf39c0df5568a2431ee5a79f238f539ef [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 Schuh605d5ff2024-05-10 15:59:54 -070017#include "absl/container/btree_map.h"
Austin Schuhcb108412019-10-13 16:09:54 -070018#include "absl/container/btree_set.h"
Austin Schuh99f7c6a2024-06-25 22:07:44 -070019#include "absl/flags/flag.h"
20#include "absl/log/check.h"
21#include "absl/log/log.h"
Austin Schuha81454b2020-05-12 19:58:36 -070022#include "absl/strings/str_cat.h"
Austin Schuhef38cd22021-07-21 15:24:23 -070023#include "absl/strings/str_join.h"
24#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070025
Austin Schuhcb108412019-10-13 16:09:54 -070026#include "aos/configuration_generated.h"
27#include "aos/flatbuffer_merge.h"
Austin Schuh83cbb1e2023-06-23 12:59:02 -070028#include "aos/ipc_lib/index.h"
Austin Schuhcb108412019-10-13 16:09:54 -070029#include "aos/json_to_flatbuffer.h"
Austin Schuh217a9782019-12-21 23:02:50 -080030#include "aos/network/team_number.h"
Austin Schuhcb108412019-10-13 16:09:54 -070031#include "aos/unique_malloc_ptr.h"
32#include "aos/util/file.h"
Austin Schuh217a9782019-12-21 23:02:50 -080033
Austin Schuh99f7c6a2024-06-25 22:07:44 -070034ABSL_FLAG(uint32_t, max_queue_size_override, 0,
35 "If nonzero, this is the max number of elements in a queue to "
36 "enforce. If zero, use the number that the processor that this "
37 "application is compiled for can support. This is mostly useful "
38 "for config validation, and shouldn't be touched.");
Austin Schuh83cbb1e2023-06-23 12:59:02 -070039
Brian Silverman66f079a2013-08-26 16:24:30 -070040namespace aos {
Austin Schuh605d5ff2024-05-10 15:59:54 -070041namespace configuration {
Austin Schuh15182322020-10-10 15:25:21 -070042namespace {
Austin Schuhfb37c612022-08-11 15:24:51 -070043namespace chrono = std::chrono;
44
Austin Schuh15182322020-10-10 15:25:21 -070045bool EndsWith(std::string_view str, std::string_view end) {
46 if (str.size() < end.size()) {
47 return false;
48 }
49 if (str.substr(str.size() - end.size(), end.size()) != end) {
50 return false;
51 }
52 return true;
53}
54
55std::string MaybeReplaceExtension(std::string_view filename,
56 std::string_view extension,
57 std::string_view replacement) {
58 if (!EndsWith(filename, extension)) {
59 return std::string(filename);
60 }
61 filename.remove_suffix(extension.size());
62 return absl::StrCat(filename, replacement);
63}
64
Austin Schuh605d5ff2024-05-10 15:59:54 -070065void ValidateUnmergedConfiguration(const Flatbuffer<Configuration> &config);
66
Austin Schuh15182322020-10-10 15:25:21 -070067FlatbufferDetachedBuffer<Configuration> ReadConfigFile(std::string_view path,
68 bool binary) {
69 if (binary) {
70 FlatbufferVector<Configuration> config =
71 FileToFlatbuffer<Configuration>(path);
72 return CopySpanAsDetachedBuffer(config.span());
73 }
74
75 flatbuffers::DetachedBuffer buffer = JsonToFlatbuffer(
76 util::ReadFileToStringOrDie(path), ConfigurationTypeTable());
77
Austin Schuh84a039a2021-11-03 16:50:34 -070078 CHECK_GT(buffer.size(), 0u) << ": Failed to parse JSON file: " << path;
Austin Schuh15182322020-10-10 15:25:21 -070079
Austin Schuh605d5ff2024-05-10 15:59:54 -070080 FlatbufferDetachedBuffer<Configuration> result(std::move(buffer));
81 configuration::ValidateUnmergedConfiguration(result);
82 return result;
Austin Schuh15182322020-10-10 15:25:21 -070083}
84
Austin Schuh605d5ff2024-05-10 15:59:54 -070085// Struct representing a Connection in a channel in a way that is easy to work
86// with.
87struct MutableConnection {
88 // See configuration.fbs for a description of what each of these fields
89 // represents.
90 std::string_view name;
91 std::optional<LoggerConfig> timestamp_logger;
92 absl::btree_set<std::string_view> timestamp_logger_nodes;
93 std::optional<uint16_t> priority;
94 std::optional<uint32_t> time_to_live;
95};
96
97// The name of a channel.
98struct MutableChannelName {
99 std::string_view name;
100 std::string_view type;
101
102 bool operator==(const MutableChannelName &other) const {
103 return std::make_tuple(name, type) ==
104 std::make_tuple(other.name, other.type);
Austin Schuhcb108412019-10-13 16:09:54 -0700105 }
Austin Schuh605d5ff2024-05-10 15:59:54 -0700106 std::strong_ordering operator<=>(const MutableChannelName &other) const {
107 return std::make_tuple(name, type) <=>
108 std::make_tuple(other.name, other.type);
Adam Snaider13d48d92023-08-03 12:20:15 -0700109 }
110};
111
Austin Schuh605d5ff2024-05-10 15:59:54 -0700112// Struct representing a Channel in a way that is easy to work with.
113struct MutableChannel {
114 // See configuration.fbs for a description of what each of these fields
115 // represents.
116 std::string_view name;
117 std::string_view type;
118 std::optional<int32_t> frequency;
119 std::optional<int32_t> max_size;
120 std::optional<int32_t> num_senders;
121 std::optional<int32_t> num_watchers;
122
123 std::string_view source_node;
124 absl::btree_map<std::string_view, MutableConnection> destination_nodes;
125 std::optional<LoggerConfig> logger;
126 absl::btree_set<std::string_view> logger_nodes;
127 std::optional<ReadMethod> read_method;
128 std::optional<int32_t> num_readers;
129
130 std::optional<int64_t> channel_storage_duration;
131};
132
133// Struct representing a Node in a way that is easy to work with.
134struct MutableNode {
135 // See configuration.fbs for a description of what each of these fields
136 // represents.
137 std::string_view name;
138 std::string_view hostname;
139 std::optional<uint16_t> port;
140 absl::btree_set<std::string_view> hostnames;
141 absl::btree_set<std::string_view> tags;
142};
143
144// Struct representing a Map in a way that is easy to work with.
145struct MutableMap {
146 // See configuration.fbs for a description of what each of these fields
147 // represents.
148 MutableChannel match;
149 MutableChannel rename;
150};
151
152// Struct representing an Application in a way that is easy to work with.
153struct MutableApplication {
154 // See configuration.fbs for a description of what each of these fields
155 // represents.
156 std::string_view name;
157 std::string_view executable_name;
158 std::vector<MutableMap> maps;
159 absl::btree_set<std::string_view> nodes;
160 std::string_view user;
161 std::vector<std::string_view> args;
162 std::optional<bool> autostart;
163 std::optional<bool> autorestart;
164 std::optional<uint64_t> memory_limit;
165 std::optional<int64_t> stop_time;
166
167 bool operator==(const MutableApplication &other) const {
168 return name == other.name;
169 }
170 std::strong_ordering operator<=>(const MutableApplication &other) const {
171 return name <=> other.name;
172 }
173};
174
175// Struct representing a Configuration in a way that is easy to work with. To
176// use this class, start with a flatbuffer configuration, call
177// UnpackConfiguration() on it, and then manipulate from there. (Note: this API
178// generally assumes that the lifetime of strings is managed by something else,
179// and a string_view is good enough).
180// PackConfiguration() creates the corresponding flatbuffer from a mutable
181// configuration for downstream use.
182struct MutableConfiguration {
183 // See configuration.fbs for a description of what each of these fields
184 // represents.
185 // TODO(austin): Is this a better object for LogReader to manipulate than raw
186 // configs?
187 absl::btree_map<MutableChannelName, MutableChannel> channels;
188 std::vector<MutableMap> maps;
189 absl::btree_map<std::string_view, MutableNode> nodes;
190 absl::btree_map<std::string_view, MutableApplication> applications;
191 std::optional<uint64_t> channel_storage_duration;
192
193 absl::btree_map<std::string_view, const reflection::Schema *> schemas;
194};
195
196// Unpacks a vector of strings into a set.
197void UnpackStringSet(
198 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>
199 *strings,
200 absl::btree_set<std::string_view> *result) {
201 for (const flatbuffers::String *str : *strings) {
202 result->insert(str->string_view());
203 }
204}
205
206void UnpackConnection(const Connection *destination_node,
207 MutableConnection *result) {
208 CHECK_EQ(Connection::MiniReflectTypeTable()->num_elems, 5u)
209 << ": Merging logic needs to be updated when the number of connection "
210 "fields changes.";
211 if (destination_node->has_timestamp_logger()) {
212 result->timestamp_logger = destination_node->timestamp_logger();
213 }
214
215 if (destination_node->has_timestamp_logger_nodes()) {
216 UnpackStringSet(destination_node->timestamp_logger_nodes(),
217 &result->timestamp_logger_nodes);
218 }
219 if (destination_node->has_priority()) {
220 result->priority = destination_node->priority();
221 }
222 if (destination_node->has_time_to_live()) {
223 result->time_to_live = destination_node->time_to_live();
224 }
225}
226
227void UnpackChannel(const Channel *channel, MutableChannel *result) {
228 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
229 << ": Merging logic needs to be updated when the number of channel "
230 "fields changes.";
231
232 if (channel->has_name()) {
233 result->name = channel->name()->string_view();
234 }
235 if (channel->has_type()) {
236 result->type = channel->type()->string_view();
237 }
238 if (channel->has_frequency()) {
239 result->frequency = channel->frequency();
240 }
241 if (channel->has_max_size()) {
242 result->max_size = channel->max_size();
243 }
244 if (channel->has_num_senders()) {
245 result->num_senders = channel->num_senders();
246 }
247 if (channel->has_num_watchers()) {
248 result->num_watchers = channel->num_watchers();
249 }
250 if (channel->has_source_node()) {
251 result->source_node = channel->source_node()->string_view();
252 }
253 if (channel->has_destination_nodes()) {
254 for (const Connection *destination_node : *channel->destination_nodes()) {
255 MutableConnection &destination =
256 result->destination_nodes
257 .try_emplace(destination_node->name()->string_view(),
258 MutableConnection{
259 .name = destination_node->name()->string_view(),
260 })
261 .first->second;
262 UnpackConnection(destination_node, &destination);
263 }
264 }
265 if (channel->has_logger()) {
266 result->logger = channel->logger();
267 }
268 if (channel->has_logger_nodes()) {
269 UnpackStringSet(channel->logger_nodes(), &(result->logger_nodes));
270 }
271
272 if (channel->has_read_method()) {
273 result->read_method = channel->read_method();
274 }
275 if (channel->has_num_readers()) {
276 result->num_readers = channel->num_readers();
277 }
278 if (channel->has_channel_storage_duration()) {
279 result->channel_storage_duration = channel->channel_storage_duration();
280 }
281}
282
283void UnpackNode(const Node *node, MutableNode *result) {
284 CHECK_EQ(node->name()->string_view(), result->name);
285 CHECK_EQ(Node::MiniReflectTypeTable()->num_elems, 5u)
286 << ": Merging logic needs to be updated when the number of node "
287 "fields changes.";
288 if (node->has_hostname()) {
289 result->hostname = node->hostname()->string_view();
290 }
291 if (node->has_port()) {
292 result->port = node->port();
293 }
294
295 if (node->has_hostnames()) {
296 UnpackStringSet(node->hostnames(), &(result->hostnames));
297 }
298 if (node->has_tags()) {
299 UnpackStringSet(node->tags(), &(result->tags));
300 }
301}
302
303void UnpackMap(const Map *map, MutableMap *result) {
304 CHECK(map->has_match());
305 CHECK(map->has_rename());
306 UnpackChannel(map->match(), &(result->match));
307 UnpackChannel(map->rename(), &(result->rename));
308}
309
310void UnpackApplication(const Application *application,
311 MutableApplication *result) {
312 CHECK_EQ(application->name()->string_view(), result->name);
313
314 if (application->has_executable_name()) {
315 result->executable_name = application->executable_name()->string_view();
316 }
317 if (application->has_maps()) {
318 result->maps.reserve(application->maps()->size());
319 for (const Map *map : *application->maps()) {
320 result->maps.emplace_back();
321 UnpackMap(map, &(result->maps.back()));
322 }
323 }
324
325 if (application->has_nodes()) {
326 UnpackStringSet(application->nodes(), &(result->nodes));
327 }
328
329 if (application->has_user()) {
330 result->user = application->user()->string_view();
331 }
332
333 if (application->has_args()) {
334 // Very important, arguments replace old arguments.
335 result->args.clear();
336
337 result->args.reserve(application->args()->size());
338 for (const flatbuffers::String *arg : *application->args()) {
339 result->args.emplace_back(arg->string_view());
340 }
341 }
342
343 if (application->has_autostart()) {
344 result->autostart = application->autostart();
345 }
346 if (application->has_autorestart()) {
347 result->autorestart = application->autorestart();
348 }
349 if (application->has_memory_limit()) {
350 result->memory_limit = application->memory_limit();
351 }
352 if (application->has_stop_time()) {
353 result->stop_time = application->stop_time();
354 }
355}
356
357void UnpackConfiguration(const Configuration *configuration,
358 MutableConfiguration *result) {
359 if (configuration->has_channels()) {
360 for (const Channel *channel : *configuration->channels()) {
361 // Explode on malformed entries.
362 CHECK(channel->has_name() && channel->has_type());
363
364 // Attempt to insert the channel.
365 MutableChannel &unpacked_channel =
366 result->channels
367 .try_emplace(
368 MutableChannelName{
369 .name = channel->name()->string_view(),
370 .type = channel->type()->string_view(),
371 },
372 MutableChannel{
373 .name = channel->name()->string_view(),
374 .type = channel->type()->string_view(),
375 })
376 .first->second;
377
378 UnpackChannel(channel, &unpacked_channel);
379 if (channel->has_schema()) {
380 result->schemas.emplace(channel->type()->string_view(),
381 channel->schema());
382 }
383 }
384 }
385
386 if (configuration->has_maps()) {
387 result->maps.reserve(configuration->maps()->size());
388
389 for (const Map *map : *configuration->maps()) {
390 CHECK(map->has_match());
391 CHECK(map->has_rename());
392
393 result->maps.emplace_back();
394 UnpackMap(map, &(result->maps.back()));
395 }
396 }
397
398 if (configuration->has_nodes()) {
399 for (const Node *node : *configuration->nodes()) {
400 CHECK(node->has_name());
401
402 MutableNode &unpacked_node =
403 result->nodes
404 .try_emplace(node->name()->string_view(),
405 MutableNode{
406 .name = node->name()->string_view(),
407 })
408 .first->second;
409 UnpackNode(node, &unpacked_node);
410 }
411 }
412
413 if (configuration->has_applications()) {
414 for (const Application *application : *configuration->applications()) {
415 CHECK(application->has_name());
416
417 MutableApplication &unpacked_application =
418 result->applications
419 .try_emplace(application->name()->string_view(),
420 MutableApplication{
421 .name = application->name()->string_view(),
422 })
423 .first->second;
424 UnpackApplication(application, &unpacked_application);
425 }
426 }
427
428 if (configuration->has_channel_storage_duration()) {
429 result->channel_storage_duration =
430 configuration->channel_storage_duration();
431 }
432}
433
434flatbuffers::Offset<
435 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
436PackStringSet(const absl::btree_set<std::string_view> &set,
437 flatbuffers::FlatBufferBuilder *fbb) {
438 std::vector<flatbuffers::Offset<flatbuffers::String>> strings_offsets;
439 for (const std::string_view &str : set) {
440 strings_offsets.push_back(fbb->CreateSharedString(str));
441 }
442
443 flatbuffers::Offset<
444 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
445 result;
446
447 if (!strings_offsets.empty()) {
448 result = fbb->CreateVector(strings_offsets);
449 }
450 return result;
451}
452
453flatbuffers::Offset<Connection> PackConnection(
454 const MutableConnection &destination_node,
455 flatbuffers::FlatBufferBuilder *fbb) {
456 flatbuffers::Offset<flatbuffers::String> name_offset =
457 fbb->CreateSharedString(destination_node.name);
458
459 flatbuffers::Offset<
460 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
461 timestamp_logger_nodes_offset =
462 PackStringSet(destination_node.timestamp_logger_nodes, fbb);
463
464 Connection::Builder connection_builder(*fbb);
465 connection_builder.add_name(name_offset);
466 if (destination_node.timestamp_logger.has_value()) {
467 connection_builder.add_timestamp_logger(
468 destination_node.timestamp_logger.value());
469 }
470
471 if (!timestamp_logger_nodes_offset.IsNull()) {
472 connection_builder.add_timestamp_logger_nodes(
473 timestamp_logger_nodes_offset);
474 }
475 if (destination_node.priority.has_value()) {
476 connection_builder.add_priority(destination_node.priority.value());
477 }
478 if (destination_node.time_to_live.has_value()) {
479 connection_builder.add_time_to_live(destination_node.time_to_live.value());
480 }
481 return connection_builder.Finish();
482}
483
484flatbuffers::Offset<Channel> PackChannel(
485 const MutableChannel &channel,
486 flatbuffers::Offset<reflection::Schema> schema_offset,
487 flatbuffers::FlatBufferBuilder *fbb) {
488 std::vector<flatbuffers::Offset<Connection>> connection_offsets;
489
490 for (const std::pair<const std::string_view, MutableConnection>
491 &destination_node : channel.destination_nodes) {
492 CHECK_EQ(destination_node.first, destination_node.second.name);
493 connection_offsets.push_back(PackConnection(destination_node.second, fbb));
494 }
495
496 flatbuffers::Offset<flatbuffers::String> name_offset;
497 if (!channel.name.empty()) {
498 name_offset = fbb->CreateSharedString(channel.name);
499 }
500 flatbuffers::Offset<flatbuffers::String> type_offset;
501 if (!channel.type.empty()) {
502 type_offset = fbb->CreateSharedString(channel.type);
503 }
504 flatbuffers::Offset<flatbuffers::String> source_node_offset;
505 if (!channel.source_node.empty()) {
506 source_node_offset = fbb->CreateSharedString(channel.source_node);
507 }
508 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Connection>>>
509 destination_nodes_offset;
510 if (!connection_offsets.empty()) {
511 destination_nodes_offset = fbb->CreateVector(connection_offsets);
512 }
513
514 flatbuffers::Offset<
515 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
516 logger_nodes_offset = PackStringSet(channel.logger_nodes, fbb);
517
518 Channel::Builder channel_builder(*fbb);
519
520 if (!name_offset.IsNull()) {
521 channel_builder.add_name(name_offset);
522 }
523 if (!type_offset.IsNull()) {
524 channel_builder.add_type(type_offset);
525 }
526 if (channel.frequency.has_value()) {
527 channel_builder.add_frequency(channel.frequency.value());
528 }
529 if (channel.max_size.has_value()) {
530 channel_builder.add_max_size(channel.max_size.value());
531 }
532 if (channel.num_senders.has_value()) {
533 channel_builder.add_num_senders(channel.num_senders.value());
534 }
535 if (channel.num_watchers.has_value()) {
536 channel_builder.add_num_watchers(channel.num_watchers.value());
537 }
538
539 if (!schema_offset.IsNull()) {
540 channel_builder.add_schema(schema_offset);
541 }
542
543 if (!source_node_offset.IsNull()) {
544 channel_builder.add_source_node(source_node_offset);
545 }
546 if (!destination_nodes_offset.IsNull()) {
547 channel_builder.add_destination_nodes(destination_nodes_offset);
548 }
549
550 if (channel.logger.has_value()) {
551 channel_builder.add_logger(channel.logger.value());
552 }
553 if (!logger_nodes_offset.IsNull()) {
554 channel_builder.add_logger_nodes(logger_nodes_offset);
555 }
556 if (channel.read_method.has_value()) {
557 channel_builder.add_read_method(channel.read_method.value());
558 }
559 if (channel.num_readers.has_value()) {
560 channel_builder.add_num_readers(channel.num_readers.value());
561 }
562 if (channel.channel_storage_duration.has_value()) {
563 channel_builder.add_channel_storage_duration(
564 channel.channel_storage_duration.value());
565 }
566
567 return channel_builder.Finish();
568}
569
570flatbuffers::Offset<Node> PackNode(const MutableNode &node,
571 flatbuffers::FlatBufferBuilder *fbb) {
572 flatbuffers::Offset<flatbuffers::String> name_offset =
573 fbb->CreateSharedString(node.name);
574 flatbuffers::Offset<flatbuffers::String> hostname_offset;
575 if (!node.hostname.empty()) {
576 hostname_offset = fbb->CreateSharedString(node.hostname);
577 }
578 flatbuffers::Offset<
579 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
580 hostnames_offset = PackStringSet(node.hostnames, fbb);
581 flatbuffers::Offset<
582 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
583 tags_offset = PackStringSet(node.tags, fbb);
584 Node::Builder node_builder(*fbb);
585 node_builder.add_name(name_offset);
586 if (!hostname_offset.IsNull()) {
587 node_builder.add_hostname(hostname_offset);
588 }
589 if (node.port) {
590 node_builder.add_port(node.port.value());
591 }
592 if (!hostnames_offset.IsNull()) {
593 node_builder.add_hostnames(hostnames_offset);
594 }
595 if (!tags_offset.IsNull()) {
596 node_builder.add_tags(tags_offset);
597 }
598 return node_builder.Finish();
599}
600
601flatbuffers::Offset<Map> PackMap(const MutableMap &map,
602 flatbuffers::FlatBufferBuilder *fbb) {
603 flatbuffers::Offset<Channel> match_offset =
604 PackChannel(map.match, flatbuffers::Offset<reflection::Schema>(), fbb);
605 flatbuffers::Offset<Channel> rename_offset =
606 PackChannel(map.rename, flatbuffers::Offset<reflection::Schema>(), fbb);
607 Map::Builder map_builder(*fbb);
608 map_builder.add_match(match_offset);
609 map_builder.add_rename(rename_offset);
610 return map_builder.Finish();
611}
612
613flatbuffers::Offset<Application> PackApplication(
614 const MutableApplication &application,
615 flatbuffers::FlatBufferBuilder *fbb) {
616 flatbuffers::Offset<flatbuffers::String> name_offset =
617 fbb->CreateSharedString(application.name);
618
619 flatbuffers::Offset<flatbuffers::String> executable_name_offset;
620 if (!application.executable_name.empty()) {
621 executable_name_offset =
622 fbb->CreateSharedString(application.executable_name);
623 }
624
625 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
626 maps_offset;
627 if (!application.maps.empty()) {
628 std::vector<flatbuffers::Offset<Map>> maps_offsets;
629 maps_offsets.reserve(application.maps.size());
630 for (const MutableMap &map : application.maps) {
631 maps_offsets.emplace_back(PackMap(map, fbb));
632 }
633 maps_offset = fbb->CreateVector(maps_offsets);
634 }
635
636 flatbuffers::Offset<
637 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
638 nodes_offset = PackStringSet(application.nodes, fbb);
639
640 flatbuffers::Offset<flatbuffers::String> user_offset;
641 if (!application.user.empty()) {
642 user_offset = fbb->CreateSharedString(application.user);
643 }
644
645 flatbuffers::Offset<
646 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
647 args_offset;
648 if (!application.args.empty()) {
649 std::vector<flatbuffers::Offset<flatbuffers::String>> args_offsets;
650 for (const std::string_view arg : application.args) {
651 args_offsets.emplace_back(fbb->CreateSharedString(arg));
652 }
653 args_offset = fbb->CreateVector(args_offsets);
654 }
655
656 Application::Builder application_builder(*fbb);
657 application_builder.add_name(name_offset);
658 if (!executable_name_offset.IsNull()) {
659 application_builder.add_executable_name(executable_name_offset);
660 }
661 if (!maps_offset.IsNull()) {
662 application_builder.add_maps(maps_offset);
663 }
664 if (!nodes_offset.IsNull()) {
665 application_builder.add_nodes(nodes_offset);
666 }
667 if (!user_offset.IsNull()) {
668 application_builder.add_user(user_offset);
669 }
670 if (!args_offset.IsNull()) {
671 application_builder.add_args(args_offset);
672 }
673 if (application.autostart) {
674 application_builder.add_autostart(application.autostart.value());
675 }
676 if (application.autorestart) {
677 application_builder.add_autorestart(application.autorestart.value());
678 }
679 if (application.memory_limit) {
680 application_builder.add_memory_limit(application.memory_limit.value());
681 }
682 if (application.stop_time) {
683 application_builder.add_stop_time(application.stop_time.value());
684 }
685 return application_builder.Finish();
686}
687
688flatbuffers::Offset<Configuration> PackConfiguration(
689 const MutableConfiguration &configuration,
690 flatbuffers::FlatBufferBuilder *fbb) {
691 // Start by building the vectors. They need to come before the final table.
692 // Channels
693 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
694 channels_offset;
695 {
696 // We want to add channels unconditionally since everyone expects them to be
697 // there.
698 std::map<std::string_view, flatbuffers::Offset<reflection::Schema>>
699 schema_cache;
700
701 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
702 for (const std::pair<const MutableChannelName, MutableChannel>
703 &channel_key : configuration.channels) {
704 CHECK_EQ(channel_key.first.name, channel_key.second.name);
705 CHECK_EQ(channel_key.first.type, channel_key.second.type);
706 const MutableChannel &channel = channel_key.second;
707 auto cached_schema = schema_cache.find(channel.type);
708 flatbuffers::Offset<reflection::Schema> schema_offset;
709 if (cached_schema != schema_cache.end()) {
710 schema_offset = cached_schema->second;
711 } else {
712 auto schema_to_copy_it = configuration.schemas.find(channel.type);
713 if (schema_to_copy_it != configuration.schemas.end()) {
714 schema_offset = RecursiveCopyFlatBuffer<reflection::Schema>(
715 schema_to_copy_it->second, fbb);
716 schema_cache.emplace(channel.type, schema_offset);
717 }
718 }
719
720 channel_offsets.emplace_back(PackChannel(channel, schema_offset, fbb));
721 }
722 channels_offset = fbb->CreateVector(channel_offsets);
723 }
724
725 // Maps
726 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
727 maps_offset;
728 if (!configuration.maps.empty()) {
729 std::vector<flatbuffers::Offset<Map>> map_offsets;
730 for (const MutableMap &map : configuration.maps) {
731 map_offsets.emplace_back(PackMap(map, fbb));
732 }
733 maps_offset = fbb->CreateVector(map_offsets);
734 }
735
736 // Nodes
737 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
738 nodes_offset;
739 if (!configuration.nodes.empty()) {
740 std::vector<flatbuffers::Offset<Node>> node_offsets;
741 for (const std::pair<const std::string_view, MutableNode> &node :
742 configuration.nodes) {
743 CHECK_EQ(node.first, node.second.name);
744 node_offsets.emplace_back(PackNode(node.second, fbb));
745 }
746 nodes_offset = fbb->CreateVector(node_offsets);
747 }
748
749 // Applications
750 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
751 applications_offset;
752 if (!configuration.applications.empty()) {
753 std::vector<flatbuffers::Offset<Application>> applications_offsets;
754 for (const std::pair<const std::string_view, MutableApplication>
755 &application : configuration.applications) {
756 CHECK_EQ(application.first, application.second.name);
757 applications_offsets.emplace_back(
758 PackApplication(application.second, fbb));
759 }
760 applications_offset = fbb->CreateVector(applications_offsets);
761 }
762
763 // And then build a Configuration with them all.
764 ConfigurationBuilder configuration_builder(*fbb);
765 configuration_builder.add_channels(channels_offset);
766 if (!maps_offset.IsNull()) {
767 configuration_builder.add_maps(maps_offset);
768 }
769 if (!nodes_offset.IsNull()) {
770 configuration_builder.add_nodes(nodes_offset);
771 }
772 if (!applications_offset.IsNull()) {
773 configuration_builder.add_applications(applications_offset);
774 }
775 if (configuration.channel_storage_duration) {
776 configuration_builder.add_channel_storage_duration(
777 configuration.channel_storage_duration.value());
778 }
779
780 return configuration_builder.Finish();
781}
Adam Snaider13d48d92023-08-03 12:20:15 -0700782
Austin Schuhcb108412019-10-13 16:09:54 -0700783// Extracts the folder part of a path. Returns ./ if there is no path.
Austin Schuhf1fff282020-03-28 16:57:32 -0700784std::string_view ExtractFolder(const std::string_view filename) {
Austin Schuhcb108412019-10-13 16:09:54 -0700785 auto last_slash_pos = filename.find_last_of("/\\");
786
James Kuszmaul3ae42262019-11-08 12:33:41 -0800787 return last_slash_pos == std::string_view::npos
788 ? std::string_view("./")
Austin Schuhcb108412019-10-13 16:09:54 -0700789 : filename.substr(0, last_slash_pos + 1);
790}
791
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700792std::string AbsolutePath(const std::string_view filename) {
793 // Uses an std::string so that we know the input will be null-terminated.
794 const std::string terminated_file(filename);
795 char buffer[PATH_MAX];
796 PCHECK(NULL != realpath(terminated_file.c_str(), buffer));
797 return buffer;
798}
799
Austin Schuhef38cd22021-07-21 15:24:23 -0700800std::string RemoveDotDots(const std::string_view filename) {
801 std::vector<std::string> split = absl::StrSplit(filename, '/');
802 auto iterator = split.begin();
803 while (iterator != split.end()) {
804 if (iterator->empty()) {
805 iterator = split.erase(iterator);
806 } else if (*iterator == ".") {
807 iterator = split.erase(iterator);
808 } else if (*iterator == "..") {
809 CHECK(iterator != split.begin())
810 << ": Import path may not start with ..: " << filename;
811 auto previous = iterator;
812 --previous;
813 split.erase(iterator);
814 iterator = split.erase(previous);
815 } else {
816 ++iterator;
817 }
818 }
819 return absl::StrJoin(split, "/");
820}
821
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700822std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700823 const std::string_view path, absl::btree_set<std::string> *visited_paths,
824 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh15182322020-10-10 15:25:21 -0700825 std::string binary_path = MaybeReplaceExtension(path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700826 VLOG(1) << "Looking up: " << path << ", starting with: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700827 bool binary_path_exists = util::PathExists(binary_path);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700828 std::string raw_path(path);
Austin Schuh15182322020-10-10 15:25:21 -0700829 // For each .json file, look and see if we can find a .bfbs file next to it
830 // with the same base name. If we can, assume it is the same and use it
831 // instead. It is much faster to load .bfbs files than .json files.
832 if (!binary_path_exists && !util::PathExists(raw_path)) {
833 const bool path_is_absolute = raw_path.size() > 0 && raw_path[0] == '/';
Brian Silvermand0588192022-07-26 00:35:22 -0700834 if (path_is_absolute) {
835 // Nowhere else to look up an absolute path, so fail now. Note that we
836 // always have at least one extra import path based on /proc/self/exe, so
837 // warning about those paths existing isn't helpful.
838 LOG(ERROR) << ": Failed to find file " << path << ".";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700839 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700840 }
841
842 bool found_path = false;
843 for (const auto &import_path : extra_import_paths) {
Austin Schuhef38cd22021-07-21 15:24:23 -0700844 raw_path = std::string(import_path) + "/" + RemoveDotDots(path);
Austin Schuh15182322020-10-10 15:25:21 -0700845 binary_path = MaybeReplaceExtension(raw_path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700846 VLOG(1) << "Checking: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700847 binary_path_exists = util::PathExists(binary_path);
848 if (binary_path_exists) {
849 found_path = true;
850 break;
851 }
Austin Schuhef38cd22021-07-21 15:24:23 -0700852 VLOG(1) << "Checking: " << raw_path;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700853 if (util::PathExists(raw_path)) {
854 found_path = true;
855 break;
856 }
857 }
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700858 if (!found_path) {
859 LOG(ERROR) << ": Failed to find file " << path << ".";
860 return std::nullopt;
861 }
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700862 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700863
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700864 std::optional<FlatbufferDetachedBuffer<Configuration>> config =
865 ReadConfigFile(binary_path_exists ? binary_path : raw_path,
866 binary_path_exists);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700867
Austin Schuhcb108412019-10-13 16:09:54 -0700868 // Depth first. Take the following example:
869 //
870 // config1.json:
871 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700872 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700873 // {
874 // "name": "/foo",
875 // "type": ".aos.bar",
876 // "max_size": 5
877 // }
878 // ],
879 // "imports": [
880 // "config2.json",
881 // ]
882 // }
883 //
884 // config2.json:
885 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700886 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700887 // {
888 // "name": "/foo",
889 // "type": ".aos.bar",
890 // "max_size": 7
891 // }
892 // ],
893 // }
894 //
895 // We want the main config (config1.json) to be able to override the imported
896 // config. That means that it needs to be merged into the imported configs,
897 // not the other way around.
898
Austin Schuh15182322020-10-10 15:25:21 -0700899 const std::string absolute_path =
900 AbsolutePath(binary_path_exists ? binary_path : raw_path);
901 // Track that we have seen this file before recursing. Track the path we
902 // actually loaded (which should be consistent if imported twice).
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700903 if (!visited_paths->insert(absolute_path).second) {
904 for (const auto &visited_path : *visited_paths) {
905 LOG(INFO) << "Already visited: " << visited_path;
906 }
907 LOG(FATAL)
908 << "Already imported " << path << " (i.e. " << absolute_path
909 << "). See above for the files that have already been processed.";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700910 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700911 }
Austin Schuhcb108412019-10-13 16:09:54 -0700912
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700913 if (config->message().has_imports()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700914 // Capture the imports.
915 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *v =
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700916 config->message().imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700917
918 // And then wipe them. This gets GCed when we merge later.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700919 config->mutable_message()->clear_imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700920
921 // Start with an empty configuration to merge into.
Austin Schuh40485ed2019-10-26 21:51:44 -0700922 FlatbufferDetachedBuffer<Configuration> merged_config =
923 FlatbufferDetachedBuffer<Configuration>::Empty();
Austin Schuhcb108412019-10-13 16:09:54 -0700924
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700925 const std::string path_folder(ExtractFolder(path));
Austin Schuhcb108412019-10-13 16:09:54 -0700926 for (const flatbuffers::String *str : *v) {
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700927 const std::string included_config =
928 path_folder + "/" + std::string(str->string_view());
Austin Schuhcb108412019-10-13 16:09:54 -0700929
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700930 const auto optional_config =
931 MaybeReadConfig(included_config, visited_paths, extra_import_paths);
932 if (!optional_config.has_value()) {
933 return std::nullopt;
934 }
Austin Schuhcb108412019-10-13 16:09:54 -0700935 // And them merge everything in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700936 merged_config = MergeFlatBuffers(merged_config, *optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -0700937 }
938
939 // Finally, merge this file in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700940 config = MergeFlatBuffers(merged_config, *config);
Austin Schuhcb108412019-10-13 16:09:54 -0700941 }
942 return config;
943}
944
Alex Perrycb7da4b2019-08-28 19:35:56 -0700945// Compares (c < p) a channel, and a name, type tuple.
946bool CompareChannels(const Channel *c,
James Kuszmaul3ae42262019-11-08 12:33:41 -0800947 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700948 int name_compare = c->name()->string_view().compare(p.first);
949 if (name_compare == 0) {
950 return c->type()->string_view() < p.second;
951 } else if (name_compare < 0) {
952 return true;
953 } else {
954 return false;
955 }
956};
957
958// Compares for equality (c == p) a channel, and a name, type tuple.
959bool EqualsChannels(const Channel *c,
Austin Schuhf1fff282020-03-28 16:57:32 -0700960 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700961 return c->name()->string_view() == p.first &&
962 c->type()->string_view() == p.second;
963}
964
965// Compares (c < p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800966bool CompareApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700967 return a->name()->string_view() < name;
968};
969
970// Compares for equality (c == p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800971bool EqualsApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700972 return a->name()->string_view() == name;
973}
974
Austin Schuh605d5ff2024-05-10 15:59:54 -0700975void ValidateUnmergedConfiguration(const Flatbuffer<Configuration> &config) {
Austin Schuh15182322020-10-10 15:25:21 -0700976 // Check that if there is a node list, all the source nodes are filled out and
977 // valid, and all the destination nodes are valid (and not the source). This
978 // is a basic consistency check.
979 if (config.message().has_channels()) {
Austin Schuh15182322020-10-10 15:25:21 -0700980 for (const Channel *c : *config.message().channels()) {
981 CHECK(c->has_name());
982 CHECK(c->has_type());
983 if (c->name()->string_view().back() == '/') {
984 LOG(FATAL) << "Channel names can't end with '/'";
985 }
Austin Schuh47e382e2023-05-28 11:20:56 -0700986 if (c->name()->string_view().front() != '/') {
987 LOG(FATAL) << "Channel names must start with '/'";
988 }
Austin Schuh15182322020-10-10 15:25:21 -0700989 if (c->name()->string_view().find("//") != std::string_view::npos) {
990 LOG(FATAL) << ": Invalid channel name " << c->name()->string_view()
991 << ", can't use //.";
992 }
993 for (const char data : c->name()->string_view()) {
994 if (data >= '0' && data <= '9') {
995 continue;
996 }
997 if (data >= 'a' && data <= 'z') {
998 continue;
999 }
1000 if (data >= 'A' && data <= 'Z') {
1001 continue;
1002 }
1003 if (data == '-' || data == '_' || data == '/') {
1004 continue;
1005 }
1006 LOG(FATAL) << "Invalid channel name " << c->name()->string_view()
1007 << ", can only use [-a-zA-Z0-9_/]";
1008 }
1009
Austin Schuhfb37c612022-08-11 15:24:51 -07001010 CHECK_LT(QueueSize(&config.message(), c) + QueueScratchBufferSize(c),
Austin Schuh99f7c6a2024-06-25 22:07:44 -07001011 absl::GetFlag(FLAGS_max_queue_size_override) != 0
1012 ? absl::GetFlag(FLAGS_max_queue_size_override)
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001013 : std::numeric_limits<
1014 ipc_lib::QueueIndex::PackedIndexType>::max())
Austin Schuhfb37c612022-08-11 15:24:51 -07001015 << ": More messages/second configured than the queue can hold on "
1016 << CleanedChannelToString(c) << ", " << c->frequency() << "hz for "
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001017 << ChannelStorageDuration(&config.message(), c).count() << "ns";
Austin Schuhfb37c612022-08-11 15:24:51 -07001018
Austin Schuha156fb22021-10-11 19:23:21 -07001019 if (c->has_logger_nodes()) {
1020 // Confirm that we don't have duplicate logger nodes.
1021 absl::btree_set<std::string_view> logger_nodes;
1022 for (const flatbuffers::String *s : *c->logger_nodes()) {
1023 logger_nodes.insert(s->string_view());
1024 }
1025 CHECK_EQ(static_cast<size_t>(logger_nodes.size()),
1026 c->logger_nodes()->size())
1027 << ": Found duplicate logger_nodes in "
1028 << CleanedChannelToString(c);
1029 }
1030
1031 if (c->has_destination_nodes()) {
1032 // Confirm that we don't have duplicate timestamp logger nodes.
Austin Schuh5e95bd62021-10-11 18:40:22 -07001033 for (const Connection *d : *c->destination_nodes()) {
Austin Schuha156fb22021-10-11 19:23:21 -07001034 if (d->has_timestamp_logger_nodes()) {
1035 absl::btree_set<std::string_view> timestamp_logger_nodes;
1036 for (const flatbuffers::String *s : *d->timestamp_logger_nodes()) {
1037 timestamp_logger_nodes.insert(s->string_view());
1038 }
1039 CHECK_EQ(static_cast<size_t>(timestamp_logger_nodes.size()),
1040 d->timestamp_logger_nodes()->size())
1041 << ": Found duplicate timestamp_logger_nodes in "
1042 << CleanedChannelToString(c);
1043 }
1044 }
1045
1046 // There is no good use case today for logging timestamps but not the
1047 // corresponding data. Instead of plumbing through all of this on the
1048 // reader side, let'd just disallow it for now.
1049 if (c->logger() == LoggerConfig::NOT_LOGGED) {
1050 for (const Connection *d : *c->destination_nodes()) {
1051 CHECK(d->timestamp_logger() == LoggerConfig::NOT_LOGGED)
1052 << ": Logging timestamps without data is not supported. If "
1053 "you have a good use case, let's talk. "
1054 << CleanedChannelToString(c);
1055 }
Austin Schuh5e95bd62021-10-11 18:40:22 -07001056 }
1057 }
Austin Schuh605d5ff2024-05-10 15:59:54 -07001058 CHECK_EQ(c->read_method() == ReadMethod::PIN, c->num_readers() != 0)
1059 << ": num_readers may be set if and only if read_method is PIN,"
1060 " if you want 0 readers do not set PIN: "
1061 << CleanedChannelToString(c);
1062 }
1063 }
1064}
Austin Schuh5e95bd62021-10-11 18:40:22 -07001065
Austin Schuh605d5ff2024-05-10 15:59:54 -07001066void ValidateConfiguration(const Flatbuffer<Configuration> &config) {
1067 // No imports should be left.
1068 CHECK(!config.message().has_imports());
1069
1070 ValidateUnmergedConfiguration(config);
1071
1072 // A final config is also sorted. Lookups in the config assume it is sorted.
1073 if (config.message().has_channels()) {
1074 const Channel *last_channel = nullptr;
1075 for (const Channel *c : *config.message().channels()) {
Austin Schuh15182322020-10-10 15:25:21 -07001076 if (last_channel != nullptr) {
1077 CHECK(CompareChannels(
1078 last_channel,
1079 std::make_pair(c->name()->string_view(), c->type()->string_view())))
1080 << ": Channels not sorted!";
1081 }
1082 last_channel = c;
1083 }
1084 }
1085
1086 if (config.message().has_nodes() && config.message().has_channels()) {
1087 for (const Channel *c : *config.message().channels()) {
1088 CHECK(c->has_source_node()) << ": Channel " << FlatbufferToJson(c)
1089 << " is missing \"source_node\"";
1090 CHECK(GetNode(&config.message(), c->source_node()->string_view()) !=
1091 nullptr)
1092 << ": Channel " << FlatbufferToJson(c)
1093 << " has an unknown \"source_node\"";
1094
1095 if (c->has_destination_nodes()) {
1096 for (const Connection *connection : *c->destination_nodes()) {
1097 CHECK(connection->has_name());
1098 CHECK(GetNode(&config.message(), connection->name()->string_view()) !=
1099 nullptr)
1100 << ": Channel " << FlatbufferToJson(c)
1101 << " has an unknown \"destination_nodes\" "
1102 << connection->name()->string_view();
1103
1104 switch (connection->timestamp_logger()) {
1105 case LoggerConfig::LOCAL_LOGGER:
1106 case LoggerConfig::NOT_LOGGED:
Austin Schuhb98d02d2022-08-16 13:27:25 -07001107 CHECK(!connection->has_timestamp_logger_nodes())
1108 << ": " << CleanedChannelToString(c);
Austin Schuh15182322020-10-10 15:25:21 -07001109 break;
1110 case LoggerConfig::REMOTE_LOGGER:
1111 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
1112 CHECK(connection->has_timestamp_logger_nodes());
1113 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1114 for (const flatbuffers::String *timestamp_logger_node :
1115 *connection->timestamp_logger_nodes()) {
1116 CHECK(GetNode(&config.message(),
1117 timestamp_logger_node->string_view()) != nullptr)
1118 << ": Channel " << FlatbufferToJson(c)
1119 << " has an unknown \"timestamp_logger_node\""
1120 << connection->name()->string_view();
1121 }
1122 break;
1123 }
1124
1125 CHECK_NE(connection->name()->string_view(),
1126 c->source_node()->string_view())
1127 << ": Channel " << FlatbufferToJson(c)
1128 << " is forwarding data to itself";
1129 }
1130 }
1131 }
1132 }
1133}
1134
James Kuszmaulc8503f32022-06-25 16:17:12 -07001135void HandleReverseMaps(
1136 const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
1137 std::string_view type, const Node *node, std::set<std::string> *names) {
1138 for (const Map *map : *maps) {
Austin Schuh6bdcc372024-06-27 14:49:11 -07001139 CHECK(map != nullptr);
1140 const Channel *const match = map->match();
1141 CHECK(match != nullptr);
1142 const Channel *const rename = map->rename();
1143 CHECK(rename != nullptr);
James Kuszmaulc8503f32022-06-25 16:17:12 -07001144
1145 // Handle type specific maps.
1146 const flatbuffers::String *const match_type_string = match->type();
1147 if (match_type_string != nullptr &&
1148 match_type_string->string_view() != type) {
1149 continue;
1150 }
1151
1152 // Now handle node specific maps.
1153 const flatbuffers::String *const match_source_node_string =
1154 match->source_node();
Austin Schuha22ee352024-05-08 16:24:48 -07001155 if (match_source_node_string != nullptr &&
1156 // We have a node and it matches.
1157 ((node != nullptr && match_source_node_string->string_view() !=
1158 node->name()->string_view()) ||
1159 // Or we don't have a node so we can't match.
1160 node == nullptr)) {
James Kuszmaulc8503f32022-06-25 16:17:12 -07001161 continue;
1162 }
1163
1164 const flatbuffers::String *const match_name_string = match->name();
1165 const flatbuffers::String *const rename_name_string = rename->name();
1166 if (match_name_string == nullptr || rename_name_string == nullptr) {
1167 continue;
1168 }
1169
1170 const std::string rename_name = rename_name_string->str();
1171 const std::string_view match_name = match_name_string->string_view();
1172
1173 std::set<std::string> possible_renames;
1174
1175 // Check if the current name(s) could have been reached using the provided
1176 // rename.
1177 if (match_name.back() == '*') {
1178 for (const std::string &option : *names) {
1179 if (option.substr(0, rename_name.size()) == rename_name) {
1180 possible_renames.insert(
1181 absl::StrCat(match_name.substr(0, match_name.size() - 1),
1182 option.substr(rename_name.size())));
1183 }
1184 }
1185 names->insert(possible_renames.begin(), possible_renames.end());
1186 } else if (names->count(rename_name) != 0) {
1187 names->insert(std::string(match_name));
1188 }
1189 }
1190}
1191
Alex Perrycb7da4b2019-08-28 19:35:56 -07001192} // namespace
1193
Austin Schuh006a9f52021-04-07 16:24:18 -07001194// Maps name for the provided maps. Modifies name.
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001195//
1196// This is called many times during startup, and it dereferences a lot of
1197// pointers. These combine to make it a performance hotspot during many tests
1198// under msan, so there is some optimizing around caching intermediates instead
1199// of dereferencing the pointer multiple times.
James Kuszmaulc8503f32022-06-25 16:17:12 -07001200//
1201// Deliberately not in an anonymous namespace so that the log-reading code can
1202// reference it.
Austin Schuh006a9f52021-04-07 16:24:18 -07001203void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
1204 std::string *name, std::string_view type, const Node *node) {
1205 // For the same reason we merge configs in reverse order, we want to process
1206 // maps in reverse order. That lets the outer config overwrite channels from
1207 // the inner configs.
1208 for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001209 const Channel *const match = i->match();
1210 if (!match) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001211 continue;
1212 }
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001213 const flatbuffers::String *const match_name_string = match->name();
1214 if (!match_name_string) {
1215 continue;
1216 }
1217 const Channel *const rename = i->rename();
1218 if (!rename) {
1219 continue;
1220 }
1221 const flatbuffers::String *const rename_name_string = rename->name();
1222 if (!rename_name_string) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001223 continue;
1224 }
1225
1226 // Handle normal maps (now that we know that match and rename are filled
1227 // out).
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001228 const std::string_view match_name = match_name_string->string_view();
Austin Schuh006a9f52021-04-07 16:24:18 -07001229 if (match_name != *name) {
1230 if (match_name.back() == '*' &&
1231 std::string_view(*name).substr(
1232 0, std::min(name->size(), match_name.size() - 1)) ==
1233 match_name.substr(0, match_name.size() - 1)) {
1234 CHECK_EQ(match_name.find('*'), match_name.size() - 1);
1235 } else {
1236 continue;
1237 }
1238 }
1239
1240 // Handle type specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001241 const flatbuffers::String *const match_type_string = match->type();
1242 if (match_type_string && match_type_string->string_view() != type) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001243 continue;
1244 }
1245
1246 // Now handle node specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001247 const flatbuffers::String *const match_source_node_string =
1248 match->source_node();
Austin Schuha22ee352024-05-08 16:24:48 -07001249 if (match_source_node_string &&
1250 // We've got a node and it matches.
1251 ((node && match_source_node_string->string_view() !=
1252 node->name()->string_view()) ||
1253 // Or we don't have a node, so we can't match.
1254 node == nullptr)) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001255 continue;
1256 }
1257
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001258 std::string new_name(rename_name_string->string_view());
Austin Schuh006a9f52021-04-07 16:24:18 -07001259 if (match_name.back() == '*') {
1260 new_name += std::string(name->substr(match_name.size() - 1));
1261 }
1262 VLOG(1) << "Renamed \"" << *name << "\" to \"" << new_name << "\"";
1263 *name = std::move(new_name);
1264 }
1265}
1266
James Kuszmaulc8503f32022-06-25 16:17:12 -07001267std::set<std::string> GetChannelAliases(const Configuration *config,
1268 std::string_view name,
1269 std::string_view type,
1270 const std::string_view application_name,
1271 const Node *node) {
1272 std::set<std::string> names{std::string(name)};
1273 if (config->has_maps()) {
1274 HandleReverseMaps(config->maps(), type, node, &names);
1275 }
1276 {
1277 const Application *application =
1278 GetApplication(config, node, application_name);
1279 if (application != nullptr && application->has_maps()) {
1280 HandleReverseMaps(application->maps(), type, node, &names);
1281 }
1282 }
1283 return names;
1284}
1285
Austin Schuh40485ed2019-10-26 21:51:44 -07001286FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
Austin Schuhcb108412019-10-13 16:09:54 -07001287 const Flatbuffer<Configuration> &config) {
Austin Schuh605d5ff2024-05-10 15:59:54 -07001288 MutableConfiguration unpacked_config;
James Kuszmaul3c998592020-07-27 21:04:47 -07001289
Austin Schuh605d5ff2024-05-10 15:59:54 -07001290 // The act of unpacking a config merges everything.
1291 UnpackConfiguration(&config.message(), &unpacked_config);
Austin Schuh217a9782019-12-21 23:02:50 -08001292
Austin Schuhcb108412019-10-13 16:09:54 -07001293 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -08001294 fbb.ForceDefaults(true);
Austin Schuhcb108412019-10-13 16:09:54 -07001295
Austin Schuh605d5ff2024-05-10 15:59:54 -07001296 fbb.Finish(PackConfiguration(unpacked_config, &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -07001297
Austin Schuh605d5ff2024-05-10 15:59:54 -07001298 FlatbufferDetachedBuffer<aos::Configuration> result(fbb.Release());
Austin Schuh217a9782019-12-21 23:02:50 -08001299
Austin Schuh15182322020-10-10 15:25:21 -07001300 ValidateConfiguration(result);
Austin Schuh217a9782019-12-21 23:02:50 -08001301
1302 return result;
Austin Schuhcb108412019-10-13 16:09:54 -07001303}
1304
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001305std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -07001306 const std::string_view path,
Austin Schuhef38cd22021-07-21 15:24:23 -07001307 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh89026f52022-02-25 14:24:04 -08001308 // Add the executable directory to the search path. That makes it so that
1309 // tools can be run from any directory without hard-coding an absolute path to
1310 // the config into all binaries.
1311 std::vector<std::string_view> extra_import_paths_with_exe =
1312 extra_import_paths;
1313 char proc_self_exec_buffer[PATH_MAX + 1];
1314 std::memset(proc_self_exec_buffer, 0, sizeof(proc_self_exec_buffer));
1315 ssize_t s = readlink("/proc/self/exe", proc_self_exec_buffer, PATH_MAX);
1316 if (s > 0) {
1317 // If the readlink call fails, the worst thing that happens is that we don't
1318 // automatically find the config next to the binary. VLOG to make it easier
1319 // to debug.
1320 std::string_view proc_self_exec(proc_self_exec_buffer);
1321
1322 extra_import_paths_with_exe.emplace_back(
1323 proc_self_exec.substr(0, proc_self_exec.rfind("/")));
1324 } else {
1325 VLOG(1) << "Failed to read /proc/self/exe";
1326 }
1327
Austin Schuhcb108412019-10-13 16:09:54 -07001328 // We only want to read a file once. So track the visited files in a set.
1329 absl::btree_set<std::string> visited_paths;
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001330 std::optional<FlatbufferDetachedBuffer<Configuration>> read_config =
1331 MaybeReadConfig(path, &visited_paths, extra_import_paths_with_exe);
1332
1333 if (read_config == std::nullopt) {
1334 return read_config;
1335 }
Austin Schuh15182322020-10-10 15:25:21 -07001336
1337 // If we only read one file, and it had a .bfbs extension, it has to be a
1338 // fully formatted config. Do a quick verification and return it.
1339 if (visited_paths.size() == 1 && EndsWith(*visited_paths.begin(), ".bfbs")) {
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001340 ValidateConfiguration(*read_config);
Austin Schuh15182322020-10-10 15:25:21 -07001341 return read_config;
1342 }
1343
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001344 return MergeConfiguration(*read_config);
1345}
1346
1347FlatbufferDetachedBuffer<Configuration> ReadConfig(
1348 const std::string_view path,
1349 const std::vector<std::string_view> &extra_import_paths) {
1350 auto optional_config = MaybeReadConfig(path, extra_import_paths);
1351 CHECK(optional_config) << "Could not read config. See above errors";
1352 return std::move(*optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -07001353}
1354
Austin Schuh8d6cea82020-02-28 12:17:16 -08001355FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Brian Silverman24f5aa82020-06-23 16:21:28 -07001356 const Configuration *config, const Flatbuffer<Configuration> &addition) {
1357 return MergeConfiguration(MergeFlatBuffers(config, &addition.message()));
1358}
1359
1360FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Austin Schuh8d6cea82020-02-28 12:17:16 -08001361 const Configuration *config, std::string_view json) {
1362 FlatbufferDetachedBuffer<Configuration> addition =
1363 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1364
Brian Silverman24f5aa82020-06-23 16:21:28 -07001365 return MergeWithConfig(config, addition);
Austin Schuh8d6cea82020-02-28 12:17:16 -08001366}
1367
James Kuszmaul3ae42262019-11-08 12:33:41 -08001368const Channel *GetChannel(const Configuration *config, std::string_view name,
1369 std::string_view type,
Austin Schuh0de30f32020-12-06 12:44:28 -08001370 std::string_view application_name, const Node *node,
1371 bool quiet) {
Brian Silverman9fcf2c72020-12-21 18:30:58 -08001372 if (!config->has_channels()) {
1373 return nullptr;
1374 }
1375
Austin Schuhbca6cf02019-12-22 17:28:34 -08001376 const std::string_view original_name = name;
Austin Schuhf1fff282020-03-28 16:57:32 -07001377 std::string mutable_name;
Austin Schuh4c3b9702020-08-30 11:34:55 -07001378 if (node != nullptr) {
1379 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
1380 << "\" } on " << aos::FlatbufferToJson(node);
1381 } else {
1382 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
1383 << "\" }";
1384 }
Austin Schuhcb108412019-10-13 16:09:54 -07001385
1386 // First handle application specific maps. Only do this if we have a matching
1387 // application name, and it has maps.
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001388 {
1389 const Application *application =
1390 GetApplication(config, node, application_name);
1391 if (application != nullptr && application->has_maps()) {
1392 mutable_name = std::string(name);
1393 HandleMaps(application->maps(), &mutable_name, type, node);
1394 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -07001395 }
1396 }
1397
1398 // Now do global maps.
Austin Schuh40485ed2019-10-26 21:51:44 -07001399 if (config->has_maps()) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001400 mutable_name = std::string(name);
1401 HandleMaps(config->maps(), &mutable_name, type, node);
1402 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -07001403 }
1404
Austin Schuhbca6cf02019-12-22 17:28:34 -08001405 if (original_name != name) {
1406 VLOG(1) << "Remapped to { \"name\": \"" << name << "\", \"type\": \""
1407 << type << "\" }";
1408 }
Alex Perrycb7da4b2019-08-28 19:35:56 -07001409
James Kuszmaul71a81932020-12-15 21:08:01 -08001410 // Then look for the channel (note that this relies on the channels being
1411 // sorted in the config).
Austin Schuh40485ed2019-10-26 21:51:44 -07001412 auto channel_iterator =
Austin Schuhf1fff282020-03-28 16:57:32 -07001413 std::lower_bound(config->channels()->cbegin(), config->channels()->cend(),
Austin Schuh40485ed2019-10-26 21:51:44 -07001414 std::make_pair(name, type), CompareChannels);
Austin Schuhcb108412019-10-13 16:09:54 -07001415
1416 // Make sure we actually found it, and it matches.
Austin Schuh40485ed2019-10-26 21:51:44 -07001417 if (channel_iterator != config->channels()->cend() &&
1418 EqualsChannels(*channel_iterator, std::make_pair(name, type))) {
Austin Schuhbca6cf02019-12-22 17:28:34 -08001419 if (VLOG_IS_ON(2)) {
1420 VLOG(2) << "Found: " << FlatbufferToJson(*channel_iterator);
1421 } else if (VLOG_IS_ON(1)) {
1422 VLOG(1) << "Found: " << CleanedChannelToString(*channel_iterator);
1423 }
Austin Schuh40485ed2019-10-26 21:51:44 -07001424 return *channel_iterator;
Austin Schuhcb108412019-10-13 16:09:54 -07001425 } else {
1426 VLOG(1) << "No match for { \"name\": \"" << name << "\", \"type\": \""
1427 << type << "\" }";
Austin Schuh0de30f32020-12-06 12:44:28 -08001428 if (original_name != name && !quiet) {
Maxwell Gumleya28d6502023-11-17 10:43:36 -07001429 VLOG(1) << "Remapped from {\"name\": \"" << original_name
1430 << "\", \"type\": \"" << type << "\"}, to {\"name\": \"" << name
1431 << "\", \"type\": \"" << type
1432 << "\"}, but no channel by that name exists.";
Austin Schuh4b42b252020-10-19 11:35:20 -07001433 }
Austin Schuhcb108412019-10-13 16:09:54 -07001434 return nullptr;
1435 }
1436}
1437
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001438size_t ChannelIndex(const Configuration *configuration,
1439 const Channel *channel) {
1440 CHECK(configuration->channels() != nullptr) << ": No channels";
1441
Brian Silvermana9698c92021-11-10 12:27:04 -08001442 const auto c = std::lower_bound(
1443 configuration->channels()->cbegin(), configuration->channels()->cend(),
1444 std::make_pair(channel->name()->string_view(),
1445 channel->type()->string_view()),
1446 CompareChannels);
1447 CHECK(c != configuration->channels()->cend())
1448 << ": Channel pointer not found in configuration()->channels()";
1449 CHECK(*c == channel)
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001450 << ": Channel pointer not found in configuration()->channels()";
1451
Brian Silvermana9698c92021-11-10 12:27:04 -08001452 return std::distance(configuration->channels()->cbegin(), c);
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001453}
1454
Austin Schuhbca6cf02019-12-22 17:28:34 -08001455std::string CleanedChannelToString(const Channel *channel) {
1456 FlatbufferDetachedBuffer<Channel> cleaned_channel = CopyFlatBuffer(channel);
1457 cleaned_channel.mutable_message()->clear_schema();
1458 return FlatbufferToJson(cleaned_channel);
1459}
1460
Austin Schuha81454b2020-05-12 19:58:36 -07001461std::string StrippedChannelToString(const Channel *channel) {
1462 return absl::StrCat("{ \"name\": \"", channel->name()->string_view(),
1463 "\", \"type\": \"", channel->type()->string_view(),
1464 "\" }");
1465}
1466
Alex Perrycb7da4b2019-08-28 19:35:56 -07001467FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
1468 const Flatbuffer<Configuration> &config,
Austin Schuh0de30f32020-12-06 12:44:28 -08001469 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
Austin Schuh605d5ff2024-05-10 15:59:54 -07001470 MutableConfiguration unpacked_config;
1471 UnpackConfiguration(&config.message(), &unpacked_config);
1472
1473 // Now, add the schemas in so they will get packed.
1474 for (const aos::FlatbufferVector<reflection::Schema> &schema : schemas) {
1475 CHECK(schema.message().has_root_table());
1476 CHECK(schema.message().root_table()->has_name());
1477 unpacked_config.schemas.emplace(
1478 schema.message().root_table()->name()->string_view(),
1479 &schema.message());
1480 }
1481
Alex Perrycb7da4b2019-08-28 19:35:56 -07001482 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -08001483 fbb.ForceDefaults(true);
Alex Perrycb7da4b2019-08-28 19:35:56 -07001484
Austin Schuh605d5ff2024-05-10 15:59:54 -07001485 fbb.Finish(PackConfiguration(unpacked_config, &fbb));
Austin Schuh68d98592020-11-01 23:22:57 -08001486
Austin Schuh605d5ff2024-05-10 15:59:54 -07001487 return aos::FlatbufferDetachedBuffer<aos::Configuration>(fbb.Release());
Alex Perrycb7da4b2019-08-28 19:35:56 -07001488}
1489
Austin Schuh217a9782019-12-21 23:02:50 -08001490const Node *GetNodeFromHostname(const Configuration *config,
1491 std::string_view hostname) {
1492 for (const Node *node : *config->nodes()) {
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001493 if (node->has_hostname() && node->hostname()->string_view() == hostname) {
Austin Schuh217a9782019-12-21 23:02:50 -08001494 return node;
1495 }
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001496 if (node->has_hostnames()) {
1497 for (const auto &candidate : *node->hostnames()) {
1498 if (candidate->string_view() == hostname) {
1499 return node;
1500 }
1501 }
1502 }
Austin Schuh217a9782019-12-21 23:02:50 -08001503 }
1504 return nullptr;
1505}
Austin Schuhac0771c2020-01-07 18:36:30 -08001506
Maxwell Gumleyf84aca32024-05-10 13:51:59 -06001507bool IsNodeFromConfiguration(const Configuration *config, const Node *node) {
1508 if (config == nullptr) {
1509 return false;
1510 }
1511
1512 // Check if is multinode
1513 if (MultiNode(config)) {
1514 if (node == nullptr) {
1515 return false;
1516 }
1517 for (const Node *node_from_config : *config->nodes()) {
1518 if (node_from_config == node) {
1519 return true;
1520 }
1521 }
1522 return false;
1523 } else {
1524 // nullptr is the node for all single node configurations. Return true so
1525 // this function can be used in the same way for single or multinode
1526 // configurations.
1527 return node == nullptr;
1528 }
1529}
1530
Austin Schuh63097262023-08-16 17:04:29 -07001531std::string_view NodeName(const Configuration *config, size_t node_index) {
1532 if (!configuration::MultiNode(config)) {
1533 return "(singlenode)";
1534 }
1535 return config->nodes()->Get(node_index)->name()->string_view();
1536}
1537
Austin Schuh217a9782019-12-21 23:02:50 -08001538const Node *GetMyNode(const Configuration *config) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -07001539 const std::string hostname =
1540 (absl::GetFlag(FLAGS_override_hostname).size() > 0)
1541 ? absl::GetFlag(FLAGS_override_hostname)
1542 : network::GetHostname();
Austin Schuh217a9782019-12-21 23:02:50 -08001543 const Node *node = GetNodeFromHostname(config, hostname);
1544 if (node != nullptr) return node;
1545
1546 LOG(FATAL) << "Unknown node for host: " << hostname
1547 << ". Consider using --override_hostname if hostname detection "
1548 "is wrong.";
1549 return nullptr;
1550}
1551
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001552const Node *GetNode(const Configuration *config, const Node *node) {
1553 if (!MultiNode(config)) {
1554 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1555 return nullptr;
1556 } else {
1557 CHECK(node != nullptr);
1558 CHECK(node->has_name());
1559 return GetNode(config, node->name()->string_view());
1560 }
1561}
1562
Austin Schuh217a9782019-12-21 23:02:50 -08001563const Node *GetNode(const Configuration *config, std::string_view name) {
Alexei Strots4b1e1442023-05-01 22:11:05 -07001564 if (!MultiNode(config)) {
1565 if (name.empty()) {
1566 return nullptr;
1567 }
1568 LOG(FATAL) << ": Asking for a named node from a single node configuration.";
1569 }
Austin Schuh217a9782019-12-21 23:02:50 -08001570 for (const Node *node : *config->nodes()) {
Austin Schuhfd960622020-01-01 13:22:55 -08001571 CHECK(node->has_name()) << ": Malformed node " << FlatbufferToJson(node);
Austin Schuh217a9782019-12-21 23:02:50 -08001572 if (node->name()->string_view() == name) {
1573 return node;
1574 }
1575 }
1576 return nullptr;
1577}
1578
Austin Schuh0ca1fd32020-12-18 22:53:05 -08001579const Node *GetNode(const Configuration *config, size_t node_index) {
1580 if (!MultiNode(config)) {
1581 CHECK_EQ(node_index, 0u) << ": Invalid node in a single node world.";
1582 return nullptr;
1583 } else {
1584 CHECK_LT(node_index, config->nodes()->size());
1585 return config->nodes()->Get(node_index);
1586 }
1587}
1588
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001589const Node *GetNodeOrDie(const Configuration *config, const Node *node) {
1590 if (!MultiNode(config)) {
1591 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1592 return nullptr;
1593 } else {
1594 const Node *config_node = GetNode(config, node);
1595 if (config_node == nullptr) {
1596 LOG(FATAL) << "Couldn't find node matching " << FlatbufferToJson(node);
1597 }
1598 return config_node;
1599 }
1600}
1601
Austin Schuh8bd96322020-02-13 21:18:22 -08001602namespace {
1603int GetNodeIndexFromConfig(const Configuration *config, const Node *node) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001604 int node_index = 0;
1605 for (const Node *iterated_node : *config->nodes()) {
1606 if (iterated_node == node) {
1607 return node_index;
1608 }
1609 ++node_index;
1610 }
Austin Schuh8bd96322020-02-13 21:18:22 -08001611 return -1;
1612}
1613} // namespace
1614
Austin Schuha9df9ad2021-06-16 14:49:39 -07001615aos::FlatbufferDetachedBuffer<aos::Configuration> AddSchema(
1616 std::string_view json,
1617 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
1618 FlatbufferDetachedBuffer<Configuration> addition =
1619 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1620 return MergeConfiguration(addition, schemas);
1621}
1622
Austin Schuh8bd96322020-02-13 21:18:22 -08001623int GetNodeIndex(const Configuration *config, const Node *node) {
1624 if (!MultiNode(config)) {
1625 return 0;
1626 }
1627
1628 {
1629 int node_index = GetNodeIndexFromConfig(config, node);
1630 if (node_index != -1) {
1631 return node_index;
1632 }
1633 }
1634
1635 const Node *result = GetNode(config, node);
1636 CHECK(result != nullptr);
1637
1638 {
Austin Schuh04408fc2020-02-16 21:48:54 -08001639 int node_index = GetNodeIndexFromConfig(config, result);
Austin Schuh8bd96322020-02-13 21:18:22 -08001640 if (node_index != -1) {
1641 return node_index;
1642 }
1643 }
1644
1645 LOG(FATAL) << "Node " << FlatbufferToJson(node)
1646 << " not found in the configuration.";
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001647}
1648
Austin Schuh04408fc2020-02-16 21:48:54 -08001649int GetNodeIndex(const Configuration *config, std::string_view name) {
1650 if (!MultiNode(config)) {
1651 return 0;
1652 }
1653
1654 {
1655 int node_index = 0;
1656 for (const Node *iterated_node : *config->nodes()) {
1657 if (iterated_node->name()->string_view() == name) {
1658 return node_index;
1659 }
1660 ++node_index;
1661 }
1662 }
1663 LOG(FATAL) << "Node " << name << " not found in the configuration.";
1664}
1665
Austin Schuh681a2472020-12-31 23:55:40 -08001666size_t NodesCount(const Configuration *config) {
1667 if (!MultiNode(config)) {
1668 return 1u;
1669 }
1670
1671 return config->nodes()->size();
1672}
1673
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001674std::vector<const Node *> GetNodes(const Configuration *config) {
1675 std::vector<const Node *> nodes;
Austin Schuh8bd96322020-02-13 21:18:22 -08001676 if (MultiNode(config)) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001677 for (const Node *node : *config->nodes()) {
1678 nodes.emplace_back(node);
1679 }
1680 } else {
1681 nodes.emplace_back(nullptr);
1682 }
1683 return nodes;
1684}
1685
Austin Schuh65465332020-11-05 17:36:53 -08001686std::vector<const Node *> GetNodesWithTag(const Configuration *config,
1687 std::string_view tag) {
1688 std::vector<const Node *> nodes;
1689 if (!MultiNode(config)) {
1690 nodes.emplace_back(nullptr);
1691 } else {
1692 for (const Node *node : *config->nodes()) {
1693 if (!node->has_tags()) {
1694 continue;
1695 }
1696 bool did_found_tag = false;
1697 for (const flatbuffers::String *found_tag : *node->tags()) {
1698 if (found_tag->string_view() == tag) {
1699 did_found_tag = true;
1700 break;
1701 }
1702 }
1703 if (did_found_tag) {
1704 nodes.emplace_back(node);
1705 }
1706 }
1707 }
1708 return nodes;
1709}
1710
Brian Silverman631b6262021-11-10 12:25:08 -08001711bool NodeHasTag(const Node *node, std::string_view tag) {
1712 if (node == nullptr) {
1713 return true;
1714 }
1715
Austin Schuh97a52432022-08-17 15:02:59 -07001716 if (!node->has_tags()) {
1717 return false;
1718 }
1719
Brian Silverman631b6262021-11-10 12:25:08 -08001720 const auto *const tags = node->tags();
1721 return std::find_if(tags->begin(), tags->end(),
1722 [tag](const flatbuffers::String *candidate) {
1723 return candidate->string_view() == tag;
1724 }) != tags->end();
1725}
1726
Austin Schuhac0771c2020-01-07 18:36:30 -08001727bool MultiNode(const Configuration *config) { return config->has_nodes(); }
1728
Austin Schuh217a9782019-12-21 23:02:50 -08001729bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001730 if (node == nullptr) {
1731 return true;
1732 }
Austin Schuh3c5dae52020-10-06 18:55:18 -07001733 CHECK(channel->has_source_node()) << FlatbufferToJson(channel);
1734 CHECK(node->has_name()) << FlatbufferToJson(node);
Austin Schuh6bdcc372024-06-27 14:49:11 -07001735 CHECK(channel != nullptr);
1736 return (channel->source_node()->string_view() == node->name()->string_view());
Austin Schuh217a9782019-12-21 23:02:50 -08001737}
1738
1739bool ChannelIsReadableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001740 if (node == nullptr) {
1741 return true;
1742 }
1743
Austin Schuh217a9782019-12-21 23:02:50 -08001744 if (channel->source_node()->string_view() == node->name()->string_view()) {
1745 return true;
1746 }
1747
1748 if (!channel->has_destination_nodes()) {
1749 return false;
1750 }
1751
Austin Schuh719946b2019-12-28 14:51:01 -08001752 for (const Connection *connection : *channel->destination_nodes()) {
1753 CHECK(connection->has_name());
1754 if (connection->name()->string_view() == node->name()->string_view()) {
Austin Schuh217a9782019-12-21 23:02:50 -08001755 return true;
1756 }
1757 }
1758
1759 return false;
1760}
1761
James Kuszmaul24db2d32023-05-26 11:40:12 -07001762bool ChannelIsForwardedFromNode(const Channel *channel, const Node *node) {
1763 if (node == nullptr) {
1764 return false;
1765 }
1766 return ChannelIsSendableOnNode(channel, node) &&
1767 channel->has_destination_nodes() &&
1768 channel->destination_nodes()->size() > 0u;
1769}
1770
Austin Schuh719946b2019-12-28 14:51:01 -08001771bool ChannelMessageIsLoggedOnNode(const Channel *channel, const Node *node) {
Austin Schuh48e94502021-06-18 18:35:53 -07001772 if (node == nullptr) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001773 // Single node world. If there is a local logger, then we want to use
1774 // it.
Austin Schuh48e94502021-06-18 18:35:53 -07001775 if (channel->logger() == LoggerConfig::LOCAL_LOGGER) {
1776 return true;
1777 } else if (channel->logger() == LoggerConfig::NOT_LOGGED) {
1778 return false;
1779 }
1780 LOG(FATAL) << "Unsupported logging configuration in a single node world: "
1781 << CleanedChannelToString(channel);
Austin Schuh2bb80e02021-03-20 21:46:17 -07001782 }
Austin Schuh6bdcc372024-06-27 14:49:11 -07001783 CHECK(node != nullptr);
1784 return ChannelMessageIsLoggedOnNode(channel, node->name()->string_view());
Austin Schuh2bb80e02021-03-20 21:46:17 -07001785}
1786
1787bool ChannelMessageIsLoggedOnNode(const Channel *channel,
1788 std::string_view node_name) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001789 switch (channel->logger()) {
Austin Schuh719946b2019-12-28 14:51:01 -08001790 case LoggerConfig::LOCAL_LOGGER:
Austin Schuh2bb80e02021-03-20 21:46:17 -07001791 return channel->source_node()->string_view() == node_name;
Austin Schuh719946b2019-12-28 14:51:01 -08001792 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001793 CHECK(channel->has_logger_nodes())
1794 << "Missing logger nodes on " << StrippedChannelToString(channel);
1795 CHECK_GT(channel->logger_nodes()->size(), 0u)
1796 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuh719946b2019-12-28 14:51:01 -08001797
Austin Schuh2bb80e02021-03-20 21:46:17 -07001798 if (channel->source_node()->string_view() == node_name) {
Austin Schuh719946b2019-12-28 14:51:01 -08001799 return true;
1800 }
Austin Schuhda40e472020-03-28 15:15:29 -07001801
1802 [[fallthrough]];
1803 case LoggerConfig::REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001804 CHECK(channel->has_logger_nodes())
1805 << "Missing logger nodes on " << StrippedChannelToString(channel);
1806 CHECK_GT(channel->logger_nodes()->size(), 0u)
1807 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuhda40e472020-03-28 15:15:29 -07001808 for (const flatbuffers::String *logger_node : *channel->logger_nodes()) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001809 if (logger_node->string_view() == node_name) {
Austin Schuhda40e472020-03-28 15:15:29 -07001810 return true;
1811 }
Austin Schuh719946b2019-12-28 14:51:01 -08001812 }
1813
1814 return false;
1815 case LoggerConfig::NOT_LOGGED:
1816 return false;
1817 }
1818
1819 LOG(FATAL) << "Unknown logger config " << static_cast<int>(channel->logger());
1820}
1821
Austin Schuh58646e22021-08-23 23:51:46 -07001822size_t ConnectionCount(const Channel *channel) {
1823 if (!channel->has_destination_nodes()) {
1824 return 0;
1825 }
1826 return channel->destination_nodes()->size();
1827}
1828
Austin Schuh719946b2019-12-28 14:51:01 -08001829const Connection *ConnectionToNode(const Channel *channel, const Node *node) {
1830 if (!channel->has_destination_nodes()) {
1831 return nullptr;
1832 }
1833 for (const Connection *connection : *channel->destination_nodes()) {
1834 if (connection->name()->string_view() == node->name()->string_view()) {
1835 return connection;
1836 }
1837 }
1838 return nullptr;
1839}
1840
1841bool ConnectionDeliveryTimeIsLoggedOnNode(const Channel *channel,
1842 const Node *node,
1843 const Node *logger_node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001844 return ConnectionDeliveryTimeIsLoggedOnNode(ConnectionToNode(channel, node),
1845 logger_node);
Austin Schuh719946b2019-12-28 14:51:01 -08001846}
1847
1848bool ConnectionDeliveryTimeIsLoggedOnNode(const Connection *connection,
1849 const Node *node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001850 if (connection == nullptr) {
1851 return false;
1852 }
Austin Schuh719946b2019-12-28 14:51:01 -08001853 switch (connection->timestamp_logger()) {
1854 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
Austin Schuhda40e472020-03-28 15:15:29 -07001855 CHECK(connection->has_timestamp_logger_nodes());
1856 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
Austin Schuh719946b2019-12-28 14:51:01 -08001857 if (connection->name()->string_view() == node->name()->string_view()) {
1858 return true;
1859 }
1860
Austin Schuhda40e472020-03-28 15:15:29 -07001861 [[fallthrough]];
1862 case LoggerConfig::REMOTE_LOGGER:
1863 CHECK(connection->has_timestamp_logger_nodes());
1864 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1865 for (const flatbuffers::String *timestamp_logger_node :
1866 *connection->timestamp_logger_nodes()) {
1867 if (timestamp_logger_node->string_view() ==
1868 node->name()->string_view()) {
1869 return true;
1870 }
Austin Schuh719946b2019-12-28 14:51:01 -08001871 }
1872
1873 return false;
1874 case LoggerConfig::LOCAL_LOGGER:
1875 return connection->name()->string_view() == node->name()->string_view();
Austin Schuh719946b2019-12-28 14:51:01 -08001876 case LoggerConfig::NOT_LOGGED:
1877 return false;
1878 }
1879
1880 LOG(FATAL) << "Unknown logger config "
1881 << static_cast<int>(connection->timestamp_logger());
1882}
1883
Austin Schuhe84c3ed2019-12-14 15:29:48 -08001884std::vector<std::string_view> SourceNodeNames(const Configuration *config,
1885 const Node *my_node) {
1886 std::set<std::string_view> result_set;
1887
1888 for (const Channel *channel : *config->channels()) {
1889 if (channel->has_destination_nodes()) {
1890 for (const Connection *connection : *channel->destination_nodes()) {
1891 if (connection->name()->string_view() ==
1892 my_node->name()->string_view()) {
1893 result_set.insert(channel->source_node()->string_view());
1894 }
1895 }
1896 }
1897 }
1898
1899 std::vector<std::string_view> result;
1900 for (const std::string_view source : result_set) {
1901 VLOG(1) << "Found a source node of " << source;
1902 result.emplace_back(source);
1903 }
1904 return result;
1905}
1906
1907std::vector<std::string_view> DestinationNodeNames(const Configuration *config,
1908 const Node *my_node) {
1909 std::vector<std::string_view> result;
1910
1911 for (const Channel *channel : *config->channels()) {
1912 if (channel->has_source_node() && channel->source_node()->string_view() ==
1913 my_node->name()->string_view()) {
1914 if (!channel->has_destination_nodes()) continue;
1915
1916 if (channel->source_node()->string_view() !=
1917 my_node->name()->string_view()) {
1918 continue;
1919 }
1920
1921 for (const Connection *connection : *channel->destination_nodes()) {
1922 if (std::find(result.begin(), result.end(),
1923 connection->name()->string_view()) == result.end()) {
1924 result.emplace_back(connection->name()->string_view());
1925 }
1926 }
1927 }
1928 }
1929
1930 for (const std::string_view destination : result) {
1931 VLOG(1) << "Found a destination node of " << destination;
1932 }
1933 return result;
1934}
1935
Austin Schuh8d7e0bb2020-10-02 17:57:00 -07001936std::vector<const Node *> TimestampNodes(const Configuration *config,
1937 const Node *my_node) {
1938 if (!configuration::MultiNode(config)) {
1939 CHECK(my_node == nullptr);
1940 return std::vector<const Node *>{};
1941 }
1942
1943 std::set<const Node *> timestamp_logger_nodes;
1944 for (const Channel *channel : *config->channels()) {
1945 if (!configuration::ChannelIsSendableOnNode(channel, my_node)) {
1946 continue;
1947 }
1948 if (!channel->has_destination_nodes()) {
1949 continue;
1950 }
1951 for (const Connection *connection : *channel->destination_nodes()) {
1952 const Node *other_node =
1953 configuration::GetNode(config, connection->name()->string_view());
1954
1955 if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(connection,
1956 my_node)) {
1957 VLOG(1) << "Timestamps are logged from "
1958 << FlatbufferToJson(other_node);
1959 timestamp_logger_nodes.insert(other_node);
1960 }
1961 }
1962 }
1963
1964 std::vector<const Node *> result;
1965 for (const Node *node : timestamp_logger_nodes) {
1966 result.emplace_back(node);
1967 }
1968 return result;
1969}
1970
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001971bool ApplicationShouldStart(const Configuration *config, const Node *my_node,
1972 const Application *application) {
1973 if (MultiNode(config)) {
1974 // Ok, we need
1975 CHECK(application->has_nodes());
1976 CHECK(my_node != nullptr);
1977 for (const flatbuffers::String *str : *application->nodes()) {
1978 if (str->string_view() == my_node->name()->string_view()) {
1979 return true;
1980 }
1981 }
1982 return false;
1983 } else {
1984 return true;
1985 }
1986}
1987
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001988const Application *GetApplication(const Configuration *config,
1989 const Node *my_node,
1990 std::string_view application_name) {
1991 if (config->has_applications()) {
1992 auto application_iterator = std::lower_bound(
1993 config->applications()->cbegin(), config->applications()->cend(),
1994 application_name, CompareApplications);
1995 if (application_iterator != config->applications()->cend() &&
1996 EqualsApplications(*application_iterator, application_name)) {
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001997 if (ApplicationShouldStart(config, my_node, *application_iterator)) {
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001998 return *application_iterator;
1999 }
2000 }
2001 }
2002 return nullptr;
2003}
2004
Austin Schuh1ccc3a12024-04-30 17:46:29 -07002005const Node *SourceNode(const Configuration *config, const Channel *channel) {
2006 if (!MultiNode(config)) {
2007 return nullptr;
2008 }
2009 return GetNode(config, channel->source_node()->string_view());
2010}
2011
Austin Schuhfc7b6a02021-07-12 21:19:07 -07002012std::vector<size_t> SourceNodeIndex(const Configuration *config) {
2013 CHECK(config->has_channels());
2014 std::vector<size_t> result;
2015 result.resize(config->channels()->size(), 0u);
2016 if (MultiNode(config)) {
2017 for (size_t i = 0; i < config->channels()->size(); ++i) {
2018 result[i] = GetNodeIndex(
2019 config, config->channels()->Get(i)->source_node()->string_view());
2020 }
2021 }
2022 return result;
2023}
2024
Austin Schuhfff9c3a2023-06-16 18:48:23 -07002025chrono::nanoseconds ChannelStorageDuration(const Configuration *config,
2026 const Channel *channel) {
2027 CHECK(channel != nullptr);
Austin Schuhdda6db72023-06-21 17:02:34 -07002028 if (channel->has_channel_storage_duration()) {
2029 return chrono::nanoseconds(channel->channel_storage_duration());
2030 }
Austin Schuhfff9c3a2023-06-16 18:48:23 -07002031 return chrono::nanoseconds(config->channel_storage_duration());
2032}
2033
Austin Schuh83cbb1e2023-06-23 12:59:02 -07002034size_t QueueSize(const Configuration *config, const Channel *channel) {
Austin Schuhfb37c612022-08-11 15:24:51 -07002035 return QueueSize(channel->frequency(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -07002036 ChannelStorageDuration(config, channel));
Austin Schuhfb37c612022-08-11 15:24:51 -07002037}
2038
Austin Schuh83cbb1e2023-06-23 12:59:02 -07002039size_t QueueSize(size_t frequency,
2040 chrono::nanoseconds channel_storage_duration) {
Austin Schuhfb37c612022-08-11 15:24:51 -07002041 // Use integer arithmetic and round up at all cost.
2042 return static_cast<int>(
Philipp Schrader0434f902023-09-06 08:41:32 -07002043 (999'999'999 +
2044 static_cast<int64_t>(frequency) *
2045 static_cast<int64_t>(channel_storage_duration.count())) /
2046 static_cast<int64_t>(1'000'000'000));
Austin Schuhfb37c612022-08-11 15:24:51 -07002047}
2048
2049int QueueScratchBufferSize(const Channel *channel) {
2050 return channel->num_readers() + channel->num_senders();
2051}
2052
Nathan Leong307c9692022-10-08 15:25:03 -07002053// Searches through configurations for schemas that include a certain type
2054const reflection::Schema *GetSchema(const Configuration *config,
2055 std::string_view schema_type) {
2056 if (config->has_channels()) {
2057 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
2058 for (const Channel *c : *config->channels()) {
2059 if (schema_type == c->type()->string_view()) {
2060 return c->schema();
2061 }
2062 }
2063 }
2064 return nullptr;
2065}
2066
2067// Copy schema reflection into detached flatbuffer
2068std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
2069GetSchemaDetachedBuffer(const Configuration *config,
2070 std::string_view schema_type) {
2071 const reflection::Schema *found_schema = GetSchema(config, schema_type);
2072 if (found_schema == nullptr) {
2073 return std::nullopt;
2074 }
2075 return RecursiveCopyFlatBuffer(found_schema);
2076}
2077
James Kuszmaul741a4d02023-01-05 14:59:21 -08002078aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
2079 const Configuration *config, std::string_view name,
2080 aos::FlatbufferVector<reflection::Schema> schema, const aos::Node *node,
2081 ChannelT overrides) {
2082 overrides.name = name;
James Kuszmaul80d6c422023-01-06 14:16:04 -08002083 CHECK(schema.message().has_root_table());
James Kuszmaul741a4d02023-01-05 14:59:21 -08002084 overrides.type = schema.message().root_table()->name()->string_view();
2085 if (node != nullptr) {
2086 CHECK(node->has_name());
2087 overrides.source_node = node->name()->string_view();
2088 }
Austin Schuh605d5ff2024-05-10 15:59:54 -07002089
2090 // TODO(austin): Use MutableConfiguration to represent this transform more
2091 // efficiently.
James Kuszmaul741a4d02023-01-05 14:59:21 -08002092 flatbuffers::FlatBufferBuilder fbb;
2093 // Don't populate fields from overrides that the user doesn't explicitly
2094 // override.
2095 fbb.ForceDefaults(false);
2096 const flatbuffers::Offset<Channel> channel_offset =
2097 Channel::Pack(fbb, &overrides);
2098 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
2099 channels_offset = fbb.CreateVector({channel_offset});
2100 Configuration::Builder config_builder(fbb);
2101 config_builder.add_channels(channels_offset);
2102 fbb.Finish(config_builder.Finish());
2103 FlatbufferDetachedBuffer<Configuration> new_channel_config = fbb.Release();
2104 new_channel_config = MergeConfiguration(new_channel_config, {schema});
2105 return MergeWithConfig(config, new_channel_config);
2106}
2107
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07002108FlatbufferDetachedBuffer<Configuration> GetPartialConfiguration(
2109 const Configuration &configuration,
2110 std::function<bool(const Channel &)> should_include_channel) {
Austin Schuh605d5ff2024-05-10 15:59:54 -07002111 // TODO(austin): Use MutableConfiguration to represent this better.
2112 //
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07002113 // create new_configuration1, containing everything except the `channels`
2114 // field.
2115 FlatbufferDetachedBuffer<Configuration> new_configuration1 =
2116 RecursiveCopyFlatBuffer(&configuration);
2117 new_configuration1.mutable_message()->clear_channels();
2118
2119 // create new_configuration2, containing only the `channels` field.
2120 flatbuffers::FlatBufferBuilder fbb;
2121 std::vector<flatbuffers::Offset<Channel>> new_channels_vec;
2122 for (const auto &channel : *configuration.channels()) {
Austin Schuh6bdcc372024-06-27 14:49:11 -07002123 CHECK(channel != nullptr);
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07002124 if (should_include_channel(*channel)) {
2125 new_channels_vec.push_back(RecursiveCopyFlatBuffer(channel, &fbb));
2126 }
2127 }
2128 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
2129 new_channels_offset = fbb.CreateVector(new_channels_vec);
2130 Configuration::Builder new_configuration2_builder(fbb);
2131 new_configuration2_builder.add_channels(new_channels_offset);
2132 fbb.Finish(new_configuration2_builder.Finish());
2133 FlatbufferDetachedBuffer<Configuration> new_configuration2 = fbb.Release();
2134
2135 // Merge the configuration containing channels with the configuration
2136 // containing everything else, creating a complete configuration.
2137 const aos::FlatbufferDetachedBuffer<Configuration> raw_subset_configuration =
2138 MergeFlatBuffers(&new_configuration1.message(),
2139 &new_configuration2.message());
2140
2141 // Use MergeConfiguration to clean up redundant schemas.
2142 return configuration::MergeConfiguration(raw_subset_configuration);
2143}
Brian Silverman66f079a2013-08-26 16:24:30 -07002144} // namespace configuration
2145} // namespace aos