blob: 68984d58d69a1bc3136c5410a247ba950d3359c7 [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 Schuha81454b2020-05-12 19:58:36 -070019#include "absl/strings/str_cat.h"
Austin Schuhef38cd22021-07-21 15:24:23 -070020#include "absl/strings/str_join.h"
21#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -070022#include "gflags/gflags.h"
23#include "glog/logging.h"
24
Austin Schuhcb108412019-10-13 16:09:54 -070025#include "aos/configuration_generated.h"
26#include "aos/flatbuffer_merge.h"
Austin Schuh83cbb1e2023-06-23 12:59:02 -070027#include "aos/ipc_lib/index.h"
Austin Schuhcb108412019-10-13 16:09:54 -070028#include "aos/json_to_flatbuffer.h"
Austin Schuh217a9782019-12-21 23:02:50 -080029#include "aos/network/team_number.h"
Austin Schuhcb108412019-10-13 16:09:54 -070030#include "aos/unique_malloc_ptr.h"
31#include "aos/util/file.h"
Austin Schuh217a9782019-12-21 23:02:50 -080032
Austin Schuh83cbb1e2023-06-23 12:59:02 -070033DEFINE_uint32(max_queue_size_override, 0,
34 "If nonzero, this is the max number of elements in a queue to "
35 "enforce. If zero, use the number that the processor that this "
36 "application is compiled for can support. This is mostly useful "
37 "for config validation, and shouldn't be touched.");
38
Brian Silverman66f079a2013-08-26 16:24:30 -070039namespace aos {
Austin Schuh605d5ff2024-05-10 15:59:54 -070040namespace configuration {
Austin Schuh15182322020-10-10 15:25:21 -070041namespace {
Austin Schuhfb37c612022-08-11 15:24:51 -070042namespace chrono = std::chrono;
43
Austin Schuh15182322020-10-10 15:25:21 -070044bool EndsWith(std::string_view str, std::string_view end) {
45 if (str.size() < end.size()) {
46 return false;
47 }
48 if (str.substr(str.size() - end.size(), end.size()) != end) {
49 return false;
50 }
51 return true;
52}
53
54std::string MaybeReplaceExtension(std::string_view filename,
55 std::string_view extension,
56 std::string_view replacement) {
57 if (!EndsWith(filename, extension)) {
58 return std::string(filename);
59 }
60 filename.remove_suffix(extension.size());
61 return absl::StrCat(filename, replacement);
62}
63
Austin Schuh605d5ff2024-05-10 15:59:54 -070064void ValidateUnmergedConfiguration(const Flatbuffer<Configuration> &config);
65
Austin Schuh15182322020-10-10 15:25:21 -070066FlatbufferDetachedBuffer<Configuration> ReadConfigFile(std::string_view path,
67 bool binary) {
68 if (binary) {
69 FlatbufferVector<Configuration> config =
70 FileToFlatbuffer<Configuration>(path);
71 return CopySpanAsDetachedBuffer(config.span());
72 }
73
74 flatbuffers::DetachedBuffer buffer = JsonToFlatbuffer(
75 util::ReadFileToStringOrDie(path), ConfigurationTypeTable());
76
Austin Schuh84a039a2021-11-03 16:50:34 -070077 CHECK_GT(buffer.size(), 0u) << ": Failed to parse JSON file: " << path;
Austin Schuh15182322020-10-10 15:25:21 -070078
Austin Schuh605d5ff2024-05-10 15:59:54 -070079 FlatbufferDetachedBuffer<Configuration> result(std::move(buffer));
80 configuration::ValidateUnmergedConfiguration(result);
81 return result;
Austin Schuh15182322020-10-10 15:25:21 -070082}
83
Austin Schuh605d5ff2024-05-10 15:59:54 -070084// Struct representing a Connection in a channel in a way that is easy to work
85// with.
86struct MutableConnection {
87 // See configuration.fbs for a description of what each of these fields
88 // represents.
89 std::string_view name;
90 std::optional<LoggerConfig> timestamp_logger;
91 absl::btree_set<std::string_view> timestamp_logger_nodes;
92 std::optional<uint16_t> priority;
93 std::optional<uint32_t> time_to_live;
94};
95
96// The name of a channel.
97struct MutableChannelName {
98 std::string_view name;
99 std::string_view type;
100
101 bool operator==(const MutableChannelName &other) const {
102 return std::make_tuple(name, type) ==
103 std::make_tuple(other.name, other.type);
Austin Schuhcb108412019-10-13 16:09:54 -0700104 }
Austin Schuh605d5ff2024-05-10 15:59:54 -0700105 std::strong_ordering operator<=>(const MutableChannelName &other) const {
106 return std::make_tuple(name, type) <=>
107 std::make_tuple(other.name, other.type);
Adam Snaider13d48d92023-08-03 12:20:15 -0700108 }
109};
110
Austin Schuh605d5ff2024-05-10 15:59:54 -0700111// Struct representing a Channel in a way that is easy to work with.
112struct MutableChannel {
113 // See configuration.fbs for a description of what each of these fields
114 // represents.
115 std::string_view name;
116 std::string_view type;
117 std::optional<int32_t> frequency;
118 std::optional<int32_t> max_size;
119 std::optional<int32_t> num_senders;
120 std::optional<int32_t> num_watchers;
121
122 std::string_view source_node;
123 absl::btree_map<std::string_view, MutableConnection> destination_nodes;
124 std::optional<LoggerConfig> logger;
125 absl::btree_set<std::string_view> logger_nodes;
126 std::optional<ReadMethod> read_method;
127 std::optional<int32_t> num_readers;
128
129 std::optional<int64_t> channel_storage_duration;
130};
131
132// Struct representing a Node in a way that is easy to work with.
133struct MutableNode {
134 // See configuration.fbs for a description of what each of these fields
135 // represents.
136 std::string_view name;
137 std::string_view hostname;
138 std::optional<uint16_t> port;
139 absl::btree_set<std::string_view> hostnames;
140 absl::btree_set<std::string_view> tags;
141};
142
143// Struct representing a Map in a way that is easy to work with.
144struct MutableMap {
145 // See configuration.fbs for a description of what each of these fields
146 // represents.
147 MutableChannel match;
148 MutableChannel rename;
149};
150
151// Struct representing an Application in a way that is easy to work with.
152struct MutableApplication {
153 // See configuration.fbs for a description of what each of these fields
154 // represents.
155 std::string_view name;
156 std::string_view executable_name;
157 std::vector<MutableMap> maps;
158 absl::btree_set<std::string_view> nodes;
159 std::string_view user;
160 std::vector<std::string_view> args;
161 std::optional<bool> autostart;
162 std::optional<bool> autorestart;
163 std::optional<uint64_t> memory_limit;
164 std::optional<int64_t> stop_time;
165
166 bool operator==(const MutableApplication &other) const {
167 return name == other.name;
168 }
169 std::strong_ordering operator<=>(const MutableApplication &other) const {
170 return name <=> other.name;
171 }
172};
173
174// Struct representing a Configuration in a way that is easy to work with. To
175// use this class, start with a flatbuffer configuration, call
176// UnpackConfiguration() on it, and then manipulate from there. (Note: this API
177// generally assumes that the lifetime of strings is managed by something else,
178// and a string_view is good enough).
179// PackConfiguration() creates the corresponding flatbuffer from a mutable
180// configuration for downstream use.
181struct MutableConfiguration {
182 // See configuration.fbs for a description of what each of these fields
183 // represents.
184 // TODO(austin): Is this a better object for LogReader to manipulate than raw
185 // configs?
186 absl::btree_map<MutableChannelName, MutableChannel> channels;
187 std::vector<MutableMap> maps;
188 absl::btree_map<std::string_view, MutableNode> nodes;
189 absl::btree_map<std::string_view, MutableApplication> applications;
190 std::optional<uint64_t> channel_storage_duration;
191
192 absl::btree_map<std::string_view, const reflection::Schema *> schemas;
193};
194
195// Unpacks a vector of strings into a set.
196void UnpackStringSet(
197 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>
198 *strings,
199 absl::btree_set<std::string_view> *result) {
200 for (const flatbuffers::String *str : *strings) {
201 result->insert(str->string_view());
202 }
203}
204
205void UnpackConnection(const Connection *destination_node,
206 MutableConnection *result) {
207 CHECK_EQ(Connection::MiniReflectTypeTable()->num_elems, 5u)
208 << ": Merging logic needs to be updated when the number of connection "
209 "fields changes.";
210 if (destination_node->has_timestamp_logger()) {
211 result->timestamp_logger = destination_node->timestamp_logger();
212 }
213
214 if (destination_node->has_timestamp_logger_nodes()) {
215 UnpackStringSet(destination_node->timestamp_logger_nodes(),
216 &result->timestamp_logger_nodes);
217 }
218 if (destination_node->has_priority()) {
219 result->priority = destination_node->priority();
220 }
221 if (destination_node->has_time_to_live()) {
222 result->time_to_live = destination_node->time_to_live();
223 }
224}
225
226void UnpackChannel(const Channel *channel, MutableChannel *result) {
227 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
228 << ": Merging logic needs to be updated when the number of channel "
229 "fields changes.";
230
231 if (channel->has_name()) {
232 result->name = channel->name()->string_view();
233 }
234 if (channel->has_type()) {
235 result->type = channel->type()->string_view();
236 }
237 if (channel->has_frequency()) {
238 result->frequency = channel->frequency();
239 }
240 if (channel->has_max_size()) {
241 result->max_size = channel->max_size();
242 }
243 if (channel->has_num_senders()) {
244 result->num_senders = channel->num_senders();
245 }
246 if (channel->has_num_watchers()) {
247 result->num_watchers = channel->num_watchers();
248 }
249 if (channel->has_source_node()) {
250 result->source_node = channel->source_node()->string_view();
251 }
252 if (channel->has_destination_nodes()) {
253 for (const Connection *destination_node : *channel->destination_nodes()) {
254 MutableConnection &destination =
255 result->destination_nodes
256 .try_emplace(destination_node->name()->string_view(),
257 MutableConnection{
258 .name = destination_node->name()->string_view(),
259 })
260 .first->second;
261 UnpackConnection(destination_node, &destination);
262 }
263 }
264 if (channel->has_logger()) {
265 result->logger = channel->logger();
266 }
267 if (channel->has_logger_nodes()) {
268 UnpackStringSet(channel->logger_nodes(), &(result->logger_nodes));
269 }
270
271 if (channel->has_read_method()) {
272 result->read_method = channel->read_method();
273 }
274 if (channel->has_num_readers()) {
275 result->num_readers = channel->num_readers();
276 }
277 if (channel->has_channel_storage_duration()) {
278 result->channel_storage_duration = channel->channel_storage_duration();
279 }
280}
281
282void UnpackNode(const Node *node, MutableNode *result) {
283 CHECK_EQ(node->name()->string_view(), result->name);
284 CHECK_EQ(Node::MiniReflectTypeTable()->num_elems, 5u)
285 << ": Merging logic needs to be updated when the number of node "
286 "fields changes.";
287 if (node->has_hostname()) {
288 result->hostname = node->hostname()->string_view();
289 }
290 if (node->has_port()) {
291 result->port = node->port();
292 }
293
294 if (node->has_hostnames()) {
295 UnpackStringSet(node->hostnames(), &(result->hostnames));
296 }
297 if (node->has_tags()) {
298 UnpackStringSet(node->tags(), &(result->tags));
299 }
300}
301
302void UnpackMap(const Map *map, MutableMap *result) {
303 CHECK(map->has_match());
304 CHECK(map->has_rename());
305 UnpackChannel(map->match(), &(result->match));
306 UnpackChannel(map->rename(), &(result->rename));
307}
308
309void UnpackApplication(const Application *application,
310 MutableApplication *result) {
311 CHECK_EQ(application->name()->string_view(), result->name);
312
313 if (application->has_executable_name()) {
314 result->executable_name = application->executable_name()->string_view();
315 }
316 if (application->has_maps()) {
317 result->maps.reserve(application->maps()->size());
318 for (const Map *map : *application->maps()) {
319 result->maps.emplace_back();
320 UnpackMap(map, &(result->maps.back()));
321 }
322 }
323
324 if (application->has_nodes()) {
325 UnpackStringSet(application->nodes(), &(result->nodes));
326 }
327
328 if (application->has_user()) {
329 result->user = application->user()->string_view();
330 }
331
332 if (application->has_args()) {
333 // Very important, arguments replace old arguments.
334 result->args.clear();
335
336 result->args.reserve(application->args()->size());
337 for (const flatbuffers::String *arg : *application->args()) {
338 result->args.emplace_back(arg->string_view());
339 }
340 }
341
342 if (application->has_autostart()) {
343 result->autostart = application->autostart();
344 }
345 if (application->has_autorestart()) {
346 result->autorestart = application->autorestart();
347 }
348 if (application->has_memory_limit()) {
349 result->memory_limit = application->memory_limit();
350 }
351 if (application->has_stop_time()) {
352 result->stop_time = application->stop_time();
353 }
354}
355
356void UnpackConfiguration(const Configuration *configuration,
357 MutableConfiguration *result) {
358 if (configuration->has_channels()) {
359 for (const Channel *channel : *configuration->channels()) {
360 // Explode on malformed entries.
361 CHECK(channel->has_name() && channel->has_type());
362
363 // Attempt to insert the channel.
364 MutableChannel &unpacked_channel =
365 result->channels
366 .try_emplace(
367 MutableChannelName{
368 .name = channel->name()->string_view(),
369 .type = channel->type()->string_view(),
370 },
371 MutableChannel{
372 .name = channel->name()->string_view(),
373 .type = channel->type()->string_view(),
374 })
375 .first->second;
376
377 UnpackChannel(channel, &unpacked_channel);
378 if (channel->has_schema()) {
379 result->schemas.emplace(channel->type()->string_view(),
380 channel->schema());
381 }
382 }
383 }
384
385 if (configuration->has_maps()) {
386 result->maps.reserve(configuration->maps()->size());
387
388 for (const Map *map : *configuration->maps()) {
389 CHECK(map->has_match());
390 CHECK(map->has_rename());
391
392 result->maps.emplace_back();
393 UnpackMap(map, &(result->maps.back()));
394 }
395 }
396
397 if (configuration->has_nodes()) {
398 for (const Node *node : *configuration->nodes()) {
399 CHECK(node->has_name());
400
401 MutableNode &unpacked_node =
402 result->nodes
403 .try_emplace(node->name()->string_view(),
404 MutableNode{
405 .name = node->name()->string_view(),
406 })
407 .first->second;
408 UnpackNode(node, &unpacked_node);
409 }
410 }
411
412 if (configuration->has_applications()) {
413 for (const Application *application : *configuration->applications()) {
414 CHECK(application->has_name());
415
416 MutableApplication &unpacked_application =
417 result->applications
418 .try_emplace(application->name()->string_view(),
419 MutableApplication{
420 .name = application->name()->string_view(),
421 })
422 .first->second;
423 UnpackApplication(application, &unpacked_application);
424 }
425 }
426
427 if (configuration->has_channel_storage_duration()) {
428 result->channel_storage_duration =
429 configuration->channel_storage_duration();
430 }
431}
432
433flatbuffers::Offset<
434 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
435PackStringSet(const absl::btree_set<std::string_view> &set,
436 flatbuffers::FlatBufferBuilder *fbb) {
437 std::vector<flatbuffers::Offset<flatbuffers::String>> strings_offsets;
438 for (const std::string_view &str : set) {
439 strings_offsets.push_back(fbb->CreateSharedString(str));
440 }
441
442 flatbuffers::Offset<
443 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
444 result;
445
446 if (!strings_offsets.empty()) {
447 result = fbb->CreateVector(strings_offsets);
448 }
449 return result;
450}
451
452flatbuffers::Offset<Connection> PackConnection(
453 const MutableConnection &destination_node,
454 flatbuffers::FlatBufferBuilder *fbb) {
455 flatbuffers::Offset<flatbuffers::String> name_offset =
456 fbb->CreateSharedString(destination_node.name);
457
458 flatbuffers::Offset<
459 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
460 timestamp_logger_nodes_offset =
461 PackStringSet(destination_node.timestamp_logger_nodes, fbb);
462
463 Connection::Builder connection_builder(*fbb);
464 connection_builder.add_name(name_offset);
465 if (destination_node.timestamp_logger.has_value()) {
466 connection_builder.add_timestamp_logger(
467 destination_node.timestamp_logger.value());
468 }
469
470 if (!timestamp_logger_nodes_offset.IsNull()) {
471 connection_builder.add_timestamp_logger_nodes(
472 timestamp_logger_nodes_offset);
473 }
474 if (destination_node.priority.has_value()) {
475 connection_builder.add_priority(destination_node.priority.value());
476 }
477 if (destination_node.time_to_live.has_value()) {
478 connection_builder.add_time_to_live(destination_node.time_to_live.value());
479 }
480 return connection_builder.Finish();
481}
482
483flatbuffers::Offset<Channel> PackChannel(
484 const MutableChannel &channel,
485 flatbuffers::Offset<reflection::Schema> schema_offset,
486 flatbuffers::FlatBufferBuilder *fbb) {
487 std::vector<flatbuffers::Offset<Connection>> connection_offsets;
488
489 for (const std::pair<const std::string_view, MutableConnection>
490 &destination_node : channel.destination_nodes) {
491 CHECK_EQ(destination_node.first, destination_node.second.name);
492 connection_offsets.push_back(PackConnection(destination_node.second, fbb));
493 }
494
495 flatbuffers::Offset<flatbuffers::String> name_offset;
496 if (!channel.name.empty()) {
497 name_offset = fbb->CreateSharedString(channel.name);
498 }
499 flatbuffers::Offset<flatbuffers::String> type_offset;
500 if (!channel.type.empty()) {
501 type_offset = fbb->CreateSharedString(channel.type);
502 }
503 flatbuffers::Offset<flatbuffers::String> source_node_offset;
504 if (!channel.source_node.empty()) {
505 source_node_offset = fbb->CreateSharedString(channel.source_node);
506 }
507 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Connection>>>
508 destination_nodes_offset;
509 if (!connection_offsets.empty()) {
510 destination_nodes_offset = fbb->CreateVector(connection_offsets);
511 }
512
513 flatbuffers::Offset<
514 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
515 logger_nodes_offset = PackStringSet(channel.logger_nodes, fbb);
516
517 Channel::Builder channel_builder(*fbb);
518
519 if (!name_offset.IsNull()) {
520 channel_builder.add_name(name_offset);
521 }
522 if (!type_offset.IsNull()) {
523 channel_builder.add_type(type_offset);
524 }
525 if (channel.frequency.has_value()) {
526 channel_builder.add_frequency(channel.frequency.value());
527 }
528 if (channel.max_size.has_value()) {
529 channel_builder.add_max_size(channel.max_size.value());
530 }
531 if (channel.num_senders.has_value()) {
532 channel_builder.add_num_senders(channel.num_senders.value());
533 }
534 if (channel.num_watchers.has_value()) {
535 channel_builder.add_num_watchers(channel.num_watchers.value());
536 }
537
538 if (!schema_offset.IsNull()) {
539 channel_builder.add_schema(schema_offset);
540 }
541
542 if (!source_node_offset.IsNull()) {
543 channel_builder.add_source_node(source_node_offset);
544 }
545 if (!destination_nodes_offset.IsNull()) {
546 channel_builder.add_destination_nodes(destination_nodes_offset);
547 }
548
549 if (channel.logger.has_value()) {
550 channel_builder.add_logger(channel.logger.value());
551 }
552 if (!logger_nodes_offset.IsNull()) {
553 channel_builder.add_logger_nodes(logger_nodes_offset);
554 }
555 if (channel.read_method.has_value()) {
556 channel_builder.add_read_method(channel.read_method.value());
557 }
558 if (channel.num_readers.has_value()) {
559 channel_builder.add_num_readers(channel.num_readers.value());
560 }
561 if (channel.channel_storage_duration.has_value()) {
562 channel_builder.add_channel_storage_duration(
563 channel.channel_storage_duration.value());
564 }
565
566 return channel_builder.Finish();
567}
568
569flatbuffers::Offset<Node> PackNode(const MutableNode &node,
570 flatbuffers::FlatBufferBuilder *fbb) {
571 flatbuffers::Offset<flatbuffers::String> name_offset =
572 fbb->CreateSharedString(node.name);
573 flatbuffers::Offset<flatbuffers::String> hostname_offset;
574 if (!node.hostname.empty()) {
575 hostname_offset = fbb->CreateSharedString(node.hostname);
576 }
577 flatbuffers::Offset<
578 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
579 hostnames_offset = PackStringSet(node.hostnames, fbb);
580 flatbuffers::Offset<
581 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
582 tags_offset = PackStringSet(node.tags, fbb);
583 Node::Builder node_builder(*fbb);
584 node_builder.add_name(name_offset);
585 if (!hostname_offset.IsNull()) {
586 node_builder.add_hostname(hostname_offset);
587 }
588 if (node.port) {
589 node_builder.add_port(node.port.value());
590 }
591 if (!hostnames_offset.IsNull()) {
592 node_builder.add_hostnames(hostnames_offset);
593 }
594 if (!tags_offset.IsNull()) {
595 node_builder.add_tags(tags_offset);
596 }
597 return node_builder.Finish();
598}
599
600flatbuffers::Offset<Map> PackMap(const MutableMap &map,
601 flatbuffers::FlatBufferBuilder *fbb) {
602 flatbuffers::Offset<Channel> match_offset =
603 PackChannel(map.match, flatbuffers::Offset<reflection::Schema>(), fbb);
604 flatbuffers::Offset<Channel> rename_offset =
605 PackChannel(map.rename, flatbuffers::Offset<reflection::Schema>(), fbb);
606 Map::Builder map_builder(*fbb);
607 map_builder.add_match(match_offset);
608 map_builder.add_rename(rename_offset);
609 return map_builder.Finish();
610}
611
612flatbuffers::Offset<Application> PackApplication(
613 const MutableApplication &application,
614 flatbuffers::FlatBufferBuilder *fbb) {
615 flatbuffers::Offset<flatbuffers::String> name_offset =
616 fbb->CreateSharedString(application.name);
617
618 flatbuffers::Offset<flatbuffers::String> executable_name_offset;
619 if (!application.executable_name.empty()) {
620 executable_name_offset =
621 fbb->CreateSharedString(application.executable_name);
622 }
623
624 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
625 maps_offset;
626 if (!application.maps.empty()) {
627 std::vector<flatbuffers::Offset<Map>> maps_offsets;
628 maps_offsets.reserve(application.maps.size());
629 for (const MutableMap &map : application.maps) {
630 maps_offsets.emplace_back(PackMap(map, fbb));
631 }
632 maps_offset = fbb->CreateVector(maps_offsets);
633 }
634
635 flatbuffers::Offset<
636 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
637 nodes_offset = PackStringSet(application.nodes, fbb);
638
639 flatbuffers::Offset<flatbuffers::String> user_offset;
640 if (!application.user.empty()) {
641 user_offset = fbb->CreateSharedString(application.user);
642 }
643
644 flatbuffers::Offset<
645 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
646 args_offset;
647 if (!application.args.empty()) {
648 std::vector<flatbuffers::Offset<flatbuffers::String>> args_offsets;
649 for (const std::string_view arg : application.args) {
650 args_offsets.emplace_back(fbb->CreateSharedString(arg));
651 }
652 args_offset = fbb->CreateVector(args_offsets);
653 }
654
655 Application::Builder application_builder(*fbb);
656 application_builder.add_name(name_offset);
657 if (!executable_name_offset.IsNull()) {
658 application_builder.add_executable_name(executable_name_offset);
659 }
660 if (!maps_offset.IsNull()) {
661 application_builder.add_maps(maps_offset);
662 }
663 if (!nodes_offset.IsNull()) {
664 application_builder.add_nodes(nodes_offset);
665 }
666 if (!user_offset.IsNull()) {
667 application_builder.add_user(user_offset);
668 }
669 if (!args_offset.IsNull()) {
670 application_builder.add_args(args_offset);
671 }
672 if (application.autostart) {
673 application_builder.add_autostart(application.autostart.value());
674 }
675 if (application.autorestart) {
676 application_builder.add_autorestart(application.autorestart.value());
677 }
678 if (application.memory_limit) {
679 application_builder.add_memory_limit(application.memory_limit.value());
680 }
681 if (application.stop_time) {
682 application_builder.add_stop_time(application.stop_time.value());
683 }
684 return application_builder.Finish();
685}
686
687flatbuffers::Offset<Configuration> PackConfiguration(
688 const MutableConfiguration &configuration,
689 flatbuffers::FlatBufferBuilder *fbb) {
690 // Start by building the vectors. They need to come before the final table.
691 // Channels
692 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
693 channels_offset;
694 {
695 // We want to add channels unconditionally since everyone expects them to be
696 // there.
697 std::map<std::string_view, flatbuffers::Offset<reflection::Schema>>
698 schema_cache;
699
700 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
701 for (const std::pair<const MutableChannelName, MutableChannel>
702 &channel_key : configuration.channels) {
703 CHECK_EQ(channel_key.first.name, channel_key.second.name);
704 CHECK_EQ(channel_key.first.type, channel_key.second.type);
705 const MutableChannel &channel = channel_key.second;
706 auto cached_schema = schema_cache.find(channel.type);
707 flatbuffers::Offset<reflection::Schema> schema_offset;
708 if (cached_schema != schema_cache.end()) {
709 schema_offset = cached_schema->second;
710 } else {
711 auto schema_to_copy_it = configuration.schemas.find(channel.type);
712 if (schema_to_copy_it != configuration.schemas.end()) {
713 schema_offset = RecursiveCopyFlatBuffer<reflection::Schema>(
714 schema_to_copy_it->second, fbb);
715 schema_cache.emplace(channel.type, schema_offset);
716 }
717 }
718
719 channel_offsets.emplace_back(PackChannel(channel, schema_offset, fbb));
720 }
721 channels_offset = fbb->CreateVector(channel_offsets);
722 }
723
724 // Maps
725 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
726 maps_offset;
727 if (!configuration.maps.empty()) {
728 std::vector<flatbuffers::Offset<Map>> map_offsets;
729 for (const MutableMap &map : configuration.maps) {
730 map_offsets.emplace_back(PackMap(map, fbb));
731 }
732 maps_offset = fbb->CreateVector(map_offsets);
733 }
734
735 // Nodes
736 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
737 nodes_offset;
738 if (!configuration.nodes.empty()) {
739 std::vector<flatbuffers::Offset<Node>> node_offsets;
740 for (const std::pair<const std::string_view, MutableNode> &node :
741 configuration.nodes) {
742 CHECK_EQ(node.first, node.second.name);
743 node_offsets.emplace_back(PackNode(node.second, fbb));
744 }
745 nodes_offset = fbb->CreateVector(node_offsets);
746 }
747
748 // Applications
749 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
750 applications_offset;
751 if (!configuration.applications.empty()) {
752 std::vector<flatbuffers::Offset<Application>> applications_offsets;
753 for (const std::pair<const std::string_view, MutableApplication>
754 &application : configuration.applications) {
755 CHECK_EQ(application.first, application.second.name);
756 applications_offsets.emplace_back(
757 PackApplication(application.second, fbb));
758 }
759 applications_offset = fbb->CreateVector(applications_offsets);
760 }
761
762 // And then build a Configuration with them all.
763 ConfigurationBuilder configuration_builder(*fbb);
764 configuration_builder.add_channels(channels_offset);
765 if (!maps_offset.IsNull()) {
766 configuration_builder.add_maps(maps_offset);
767 }
768 if (!nodes_offset.IsNull()) {
769 configuration_builder.add_nodes(nodes_offset);
770 }
771 if (!applications_offset.IsNull()) {
772 configuration_builder.add_applications(applications_offset);
773 }
774 if (configuration.channel_storage_duration) {
775 configuration_builder.add_channel_storage_duration(
776 configuration.channel_storage_duration.value());
777 }
778
779 return configuration_builder.Finish();
780}
Adam Snaider13d48d92023-08-03 12:20:15 -0700781
Austin Schuhcb108412019-10-13 16:09:54 -0700782// Extracts the folder part of a path. Returns ./ if there is no path.
Austin Schuhf1fff282020-03-28 16:57:32 -0700783std::string_view ExtractFolder(const std::string_view filename) {
Austin Schuhcb108412019-10-13 16:09:54 -0700784 auto last_slash_pos = filename.find_last_of("/\\");
785
James Kuszmaul3ae42262019-11-08 12:33:41 -0800786 return last_slash_pos == std::string_view::npos
787 ? std::string_view("./")
Austin Schuhcb108412019-10-13 16:09:54 -0700788 : filename.substr(0, last_slash_pos + 1);
789}
790
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700791std::string AbsolutePath(const std::string_view filename) {
792 // Uses an std::string so that we know the input will be null-terminated.
793 const std::string terminated_file(filename);
794 char buffer[PATH_MAX];
795 PCHECK(NULL != realpath(terminated_file.c_str(), buffer));
796 return buffer;
797}
798
Austin Schuhef38cd22021-07-21 15:24:23 -0700799std::string RemoveDotDots(const std::string_view filename) {
800 std::vector<std::string> split = absl::StrSplit(filename, '/');
801 auto iterator = split.begin();
802 while (iterator != split.end()) {
803 if (iterator->empty()) {
804 iterator = split.erase(iterator);
805 } else if (*iterator == ".") {
806 iterator = split.erase(iterator);
807 } else if (*iterator == "..") {
808 CHECK(iterator != split.begin())
809 << ": Import path may not start with ..: " << filename;
810 auto previous = iterator;
811 --previous;
812 split.erase(iterator);
813 iterator = split.erase(previous);
814 } else {
815 ++iterator;
816 }
817 }
818 return absl::StrJoin(split, "/");
819}
820
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700821std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700822 const std::string_view path, absl::btree_set<std::string> *visited_paths,
823 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh15182322020-10-10 15:25:21 -0700824 std::string binary_path = MaybeReplaceExtension(path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700825 VLOG(1) << "Looking up: " << path << ", starting with: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700826 bool binary_path_exists = util::PathExists(binary_path);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700827 std::string raw_path(path);
Austin Schuh15182322020-10-10 15:25:21 -0700828 // For each .json file, look and see if we can find a .bfbs file next to it
829 // with the same base name. If we can, assume it is the same and use it
830 // instead. It is much faster to load .bfbs files than .json files.
831 if (!binary_path_exists && !util::PathExists(raw_path)) {
832 const bool path_is_absolute = raw_path.size() > 0 && raw_path[0] == '/';
Brian Silvermand0588192022-07-26 00:35:22 -0700833 if (path_is_absolute) {
834 // Nowhere else to look up an absolute path, so fail now. Note that we
835 // always have at least one extra import path based on /proc/self/exe, so
836 // warning about those paths existing isn't helpful.
837 LOG(ERROR) << ": Failed to find file " << path << ".";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700838 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700839 }
840
841 bool found_path = false;
842 for (const auto &import_path : extra_import_paths) {
Austin Schuhef38cd22021-07-21 15:24:23 -0700843 raw_path = std::string(import_path) + "/" + RemoveDotDots(path);
Austin Schuh15182322020-10-10 15:25:21 -0700844 binary_path = MaybeReplaceExtension(raw_path, ".json", ".bfbs");
Austin Schuhef38cd22021-07-21 15:24:23 -0700845 VLOG(1) << "Checking: " << binary_path;
Austin Schuh15182322020-10-10 15:25:21 -0700846 binary_path_exists = util::PathExists(binary_path);
847 if (binary_path_exists) {
848 found_path = true;
849 break;
850 }
Austin Schuhef38cd22021-07-21 15:24:23 -0700851 VLOG(1) << "Checking: " << raw_path;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700852 if (util::PathExists(raw_path)) {
853 found_path = true;
854 break;
855 }
856 }
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700857 if (!found_path) {
858 LOG(ERROR) << ": Failed to find file " << path << ".";
859 return std::nullopt;
860 }
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700861 }
Alex Perrycb7da4b2019-08-28 19:35:56 -0700862
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700863 std::optional<FlatbufferDetachedBuffer<Configuration>> config =
864 ReadConfigFile(binary_path_exists ? binary_path : raw_path,
865 binary_path_exists);
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700866
Austin Schuhcb108412019-10-13 16:09:54 -0700867 // Depth first. Take the following example:
868 //
869 // config1.json:
870 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700871 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700872 // {
873 // "name": "/foo",
874 // "type": ".aos.bar",
875 // "max_size": 5
876 // }
877 // ],
878 // "imports": [
879 // "config2.json",
880 // ]
881 // }
882 //
883 // config2.json:
884 // {
Austin Schuh40485ed2019-10-26 21:51:44 -0700885 // "channels": [
Austin Schuhcb108412019-10-13 16:09:54 -0700886 // {
887 // "name": "/foo",
888 // "type": ".aos.bar",
889 // "max_size": 7
890 // }
891 // ],
892 // }
893 //
894 // We want the main config (config1.json) to be able to override the imported
895 // config. That means that it needs to be merged into the imported configs,
896 // not the other way around.
897
Austin Schuh15182322020-10-10 15:25:21 -0700898 const std::string absolute_path =
899 AbsolutePath(binary_path_exists ? binary_path : raw_path);
900 // Track that we have seen this file before recursing. Track the path we
901 // actually loaded (which should be consistent if imported twice).
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700902 if (!visited_paths->insert(absolute_path).second) {
903 for (const auto &visited_path : *visited_paths) {
904 LOG(INFO) << "Already visited: " << visited_path;
905 }
906 LOG(FATAL)
907 << "Already imported " << path << " (i.e. " << absolute_path
908 << "). See above for the files that have already been processed.";
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700909 return std::nullopt;
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700910 }
Austin Schuhcb108412019-10-13 16:09:54 -0700911
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700912 if (config->message().has_imports()) {
Austin Schuhcb108412019-10-13 16:09:54 -0700913 // Capture the imports.
914 const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>> *v =
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700915 config->message().imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700916
917 // And then wipe them. This gets GCed when we merge later.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700918 config->mutable_message()->clear_imports();
Austin Schuhcb108412019-10-13 16:09:54 -0700919
920 // Start with an empty configuration to merge into.
Austin Schuh40485ed2019-10-26 21:51:44 -0700921 FlatbufferDetachedBuffer<Configuration> merged_config =
922 FlatbufferDetachedBuffer<Configuration>::Empty();
Austin Schuhcb108412019-10-13 16:09:54 -0700923
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700924 const std::string path_folder(ExtractFolder(path));
Austin Schuhcb108412019-10-13 16:09:54 -0700925 for (const flatbuffers::String *str : *v) {
James Kuszmaulc0c08da2020-05-10 18:56:07 -0700926 const std::string included_config =
927 path_folder + "/" + std::string(str->string_view());
Austin Schuhcb108412019-10-13 16:09:54 -0700928
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700929 const auto optional_config =
930 MaybeReadConfig(included_config, visited_paths, extra_import_paths);
931 if (!optional_config.has_value()) {
932 return std::nullopt;
933 }
Austin Schuhcb108412019-10-13 16:09:54 -0700934 // And them merge everything in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700935 merged_config = MergeFlatBuffers(merged_config, *optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -0700936 }
937
938 // Finally, merge this file in.
Milind Upadhyay17098ba2022-04-15 22:18:50 -0700939 config = MergeFlatBuffers(merged_config, *config);
Austin Schuhcb108412019-10-13 16:09:54 -0700940 }
941 return config;
942}
943
Alex Perrycb7da4b2019-08-28 19:35:56 -0700944// Compares (c < p) a channel, and a name, type tuple.
945bool CompareChannels(const Channel *c,
James Kuszmaul3ae42262019-11-08 12:33:41 -0800946 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700947 int name_compare = c->name()->string_view().compare(p.first);
948 if (name_compare == 0) {
949 return c->type()->string_view() < p.second;
950 } else if (name_compare < 0) {
951 return true;
952 } else {
953 return false;
954 }
955};
956
957// Compares for equality (c == p) a channel, and a name, type tuple.
958bool EqualsChannels(const Channel *c,
Austin Schuhf1fff282020-03-28 16:57:32 -0700959 ::std::pair<std::string_view, std::string_view> p) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700960 return c->name()->string_view() == p.first &&
961 c->type()->string_view() == p.second;
962}
963
964// Compares (c < p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800965bool CompareApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700966 return a->name()->string_view() < name;
967};
968
969// Compares for equality (c == p) an application, and a name;
James Kuszmaul3ae42262019-11-08 12:33:41 -0800970bool EqualsApplications(const Application *a, std::string_view name) {
Alex Perrycb7da4b2019-08-28 19:35:56 -0700971 return a->name()->string_view() == name;
972}
973
Austin Schuh605d5ff2024-05-10 15:59:54 -0700974void ValidateUnmergedConfiguration(const Flatbuffer<Configuration> &config) {
Austin Schuh15182322020-10-10 15:25:21 -0700975 // Check that if there is a node list, all the source nodes are filled out and
976 // valid, and all the destination nodes are valid (and not the source). This
977 // is a basic consistency check.
978 if (config.message().has_channels()) {
Austin Schuh15182322020-10-10 15:25:21 -0700979 for (const Channel *c : *config.message().channels()) {
980 CHECK(c->has_name());
981 CHECK(c->has_type());
982 if (c->name()->string_view().back() == '/') {
983 LOG(FATAL) << "Channel names can't end with '/'";
984 }
Austin Schuh47e382e2023-05-28 11:20:56 -0700985 if (c->name()->string_view().front() != '/') {
986 LOG(FATAL) << "Channel names must start with '/'";
987 }
Austin Schuh15182322020-10-10 15:25:21 -0700988 if (c->name()->string_view().find("//") != std::string_view::npos) {
989 LOG(FATAL) << ": Invalid channel name " << c->name()->string_view()
990 << ", can't use //.";
991 }
992 for (const char data : c->name()->string_view()) {
993 if (data >= '0' && data <= '9') {
994 continue;
995 }
996 if (data >= 'a' && data <= 'z') {
997 continue;
998 }
999 if (data >= 'A' && data <= 'Z') {
1000 continue;
1001 }
1002 if (data == '-' || data == '_' || data == '/') {
1003 continue;
1004 }
1005 LOG(FATAL) << "Invalid channel name " << c->name()->string_view()
1006 << ", can only use [-a-zA-Z0-9_/]";
1007 }
1008
Austin Schuhfb37c612022-08-11 15:24:51 -07001009 CHECK_LT(QueueSize(&config.message(), c) + QueueScratchBufferSize(c),
Austin Schuh83cbb1e2023-06-23 12:59:02 -07001010 FLAGS_max_queue_size_override != 0
1011 ? FLAGS_max_queue_size_override
1012 : std::numeric_limits<
1013 ipc_lib::QueueIndex::PackedIndexType>::max())
Austin Schuhfb37c612022-08-11 15:24:51 -07001014 << ": More messages/second configured than the queue can hold on "
1015 << CleanedChannelToString(c) << ", " << c->frequency() << "hz for "
Austin Schuhfff9c3a2023-06-16 18:48:23 -07001016 << ChannelStorageDuration(&config.message(), c).count() << "ns";
Austin Schuhfb37c612022-08-11 15:24:51 -07001017
Austin Schuha156fb22021-10-11 19:23:21 -07001018 if (c->has_logger_nodes()) {
1019 // Confirm that we don't have duplicate logger nodes.
1020 absl::btree_set<std::string_view> logger_nodes;
1021 for (const flatbuffers::String *s : *c->logger_nodes()) {
1022 logger_nodes.insert(s->string_view());
1023 }
1024 CHECK_EQ(static_cast<size_t>(logger_nodes.size()),
1025 c->logger_nodes()->size())
1026 << ": Found duplicate logger_nodes in "
1027 << CleanedChannelToString(c);
1028 }
1029
1030 if (c->has_destination_nodes()) {
1031 // Confirm that we don't have duplicate timestamp logger nodes.
Austin Schuh5e95bd62021-10-11 18:40:22 -07001032 for (const Connection *d : *c->destination_nodes()) {
Austin Schuha156fb22021-10-11 19:23:21 -07001033 if (d->has_timestamp_logger_nodes()) {
1034 absl::btree_set<std::string_view> timestamp_logger_nodes;
1035 for (const flatbuffers::String *s : *d->timestamp_logger_nodes()) {
1036 timestamp_logger_nodes.insert(s->string_view());
1037 }
1038 CHECK_EQ(static_cast<size_t>(timestamp_logger_nodes.size()),
1039 d->timestamp_logger_nodes()->size())
1040 << ": Found duplicate timestamp_logger_nodes in "
1041 << CleanedChannelToString(c);
1042 }
1043 }
1044
1045 // There is no good use case today for logging timestamps but not the
1046 // corresponding data. Instead of plumbing through all of this on the
1047 // reader side, let'd just disallow it for now.
1048 if (c->logger() == LoggerConfig::NOT_LOGGED) {
1049 for (const Connection *d : *c->destination_nodes()) {
1050 CHECK(d->timestamp_logger() == LoggerConfig::NOT_LOGGED)
1051 << ": Logging timestamps without data is not supported. If "
1052 "you have a good use case, let's talk. "
1053 << CleanedChannelToString(c);
1054 }
Austin Schuh5e95bd62021-10-11 18:40:22 -07001055 }
1056 }
Austin Schuh605d5ff2024-05-10 15:59:54 -07001057 CHECK_EQ(c->read_method() == ReadMethod::PIN, c->num_readers() != 0)
1058 << ": num_readers may be set if and only if read_method is PIN,"
1059 " if you want 0 readers do not set PIN: "
1060 << CleanedChannelToString(c);
1061 }
1062 }
1063}
Austin Schuh5e95bd62021-10-11 18:40:22 -07001064
Austin Schuh605d5ff2024-05-10 15:59:54 -07001065void ValidateConfiguration(const Flatbuffer<Configuration> &config) {
1066 // No imports should be left.
1067 CHECK(!config.message().has_imports());
1068
1069 ValidateUnmergedConfiguration(config);
1070
1071 // A final config is also sorted. Lookups in the config assume it is sorted.
1072 if (config.message().has_channels()) {
1073 const Channel *last_channel = nullptr;
1074 for (const Channel *c : *config.message().channels()) {
Austin Schuh15182322020-10-10 15:25:21 -07001075 if (last_channel != nullptr) {
1076 CHECK(CompareChannels(
1077 last_channel,
1078 std::make_pair(c->name()->string_view(), c->type()->string_view())))
1079 << ": Channels not sorted!";
1080 }
1081 last_channel = c;
1082 }
1083 }
1084
1085 if (config.message().has_nodes() && config.message().has_channels()) {
1086 for (const Channel *c : *config.message().channels()) {
1087 CHECK(c->has_source_node()) << ": Channel " << FlatbufferToJson(c)
1088 << " is missing \"source_node\"";
1089 CHECK(GetNode(&config.message(), c->source_node()->string_view()) !=
1090 nullptr)
1091 << ": Channel " << FlatbufferToJson(c)
1092 << " has an unknown \"source_node\"";
1093
1094 if (c->has_destination_nodes()) {
1095 for (const Connection *connection : *c->destination_nodes()) {
1096 CHECK(connection->has_name());
1097 CHECK(GetNode(&config.message(), connection->name()->string_view()) !=
1098 nullptr)
1099 << ": Channel " << FlatbufferToJson(c)
1100 << " has an unknown \"destination_nodes\" "
1101 << connection->name()->string_view();
1102
1103 switch (connection->timestamp_logger()) {
1104 case LoggerConfig::LOCAL_LOGGER:
1105 case LoggerConfig::NOT_LOGGED:
Austin Schuhb98d02d2022-08-16 13:27:25 -07001106 CHECK(!connection->has_timestamp_logger_nodes())
1107 << ": " << CleanedChannelToString(c);
Austin Schuh15182322020-10-10 15:25:21 -07001108 break;
1109 case LoggerConfig::REMOTE_LOGGER:
1110 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
1111 CHECK(connection->has_timestamp_logger_nodes());
1112 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1113 for (const flatbuffers::String *timestamp_logger_node :
1114 *connection->timestamp_logger_nodes()) {
1115 CHECK(GetNode(&config.message(),
1116 timestamp_logger_node->string_view()) != nullptr)
1117 << ": Channel " << FlatbufferToJson(c)
1118 << " has an unknown \"timestamp_logger_node\""
1119 << connection->name()->string_view();
1120 }
1121 break;
1122 }
1123
1124 CHECK_NE(connection->name()->string_view(),
1125 c->source_node()->string_view())
1126 << ": Channel " << FlatbufferToJson(c)
1127 << " is forwarding data to itself";
1128 }
1129 }
1130 }
1131 }
1132}
1133
James Kuszmaulc8503f32022-06-25 16:17:12 -07001134void HandleReverseMaps(
1135 const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
1136 std::string_view type, const Node *node, std::set<std::string> *names) {
1137 for (const Map *map : *maps) {
1138 CHECK_NOTNULL(map);
1139 const Channel *const match = CHECK_NOTNULL(map->match());
1140 const Channel *const rename = CHECK_NOTNULL(map->rename());
1141
1142 // Handle type specific maps.
1143 const flatbuffers::String *const match_type_string = match->type();
1144 if (match_type_string != nullptr &&
1145 match_type_string->string_view() != type) {
1146 continue;
1147 }
1148
1149 // Now handle node specific maps.
1150 const flatbuffers::String *const match_source_node_string =
1151 match->source_node();
1152 if (node != nullptr && match_source_node_string != nullptr &&
1153 match_source_node_string->string_view() !=
1154 node->name()->string_view()) {
1155 continue;
1156 }
1157
1158 const flatbuffers::String *const match_name_string = match->name();
1159 const flatbuffers::String *const rename_name_string = rename->name();
1160 if (match_name_string == nullptr || rename_name_string == nullptr) {
1161 continue;
1162 }
1163
1164 const std::string rename_name = rename_name_string->str();
1165 const std::string_view match_name = match_name_string->string_view();
1166
1167 std::set<std::string> possible_renames;
1168
1169 // Check if the current name(s) could have been reached using the provided
1170 // rename.
1171 if (match_name.back() == '*') {
1172 for (const std::string &option : *names) {
1173 if (option.substr(0, rename_name.size()) == rename_name) {
1174 possible_renames.insert(
1175 absl::StrCat(match_name.substr(0, match_name.size() - 1),
1176 option.substr(rename_name.size())));
1177 }
1178 }
1179 names->insert(possible_renames.begin(), possible_renames.end());
1180 } else if (names->count(rename_name) != 0) {
1181 names->insert(std::string(match_name));
1182 }
1183 }
1184}
1185
Alex Perrycb7da4b2019-08-28 19:35:56 -07001186} // namespace
1187
Austin Schuh006a9f52021-04-07 16:24:18 -07001188// Maps name for the provided maps. Modifies name.
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001189//
1190// This is called many times during startup, and it dereferences a lot of
1191// pointers. These combine to make it a performance hotspot during many tests
1192// under msan, so there is some optimizing around caching intermediates instead
1193// of dereferencing the pointer multiple times.
James Kuszmaulc8503f32022-06-25 16:17:12 -07001194//
1195// Deliberately not in an anonymous namespace so that the log-reading code can
1196// reference it.
Austin Schuh006a9f52021-04-07 16:24:18 -07001197void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
1198 std::string *name, std::string_view type, const Node *node) {
1199 // For the same reason we merge configs in reverse order, we want to process
1200 // maps in reverse order. That lets the outer config overwrite channels from
1201 // the inner configs.
1202 for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001203 const Channel *const match = i->match();
1204 if (!match) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001205 continue;
1206 }
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001207 const flatbuffers::String *const match_name_string = match->name();
1208 if (!match_name_string) {
1209 continue;
1210 }
1211 const Channel *const rename = i->rename();
1212 if (!rename) {
1213 continue;
1214 }
1215 const flatbuffers::String *const rename_name_string = rename->name();
1216 if (!rename_name_string) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001217 continue;
1218 }
1219
1220 // Handle normal maps (now that we know that match and rename are filled
1221 // out).
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001222 const std::string_view match_name = match_name_string->string_view();
Austin Schuh006a9f52021-04-07 16:24:18 -07001223 if (match_name != *name) {
1224 if (match_name.back() == '*' &&
1225 std::string_view(*name).substr(
1226 0, std::min(name->size(), match_name.size() - 1)) ==
1227 match_name.substr(0, match_name.size() - 1)) {
1228 CHECK_EQ(match_name.find('*'), match_name.size() - 1);
1229 } else {
1230 continue;
1231 }
1232 }
1233
1234 // Handle type specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001235 const flatbuffers::String *const match_type_string = match->type();
1236 if (match_type_string && match_type_string->string_view() != type) {
Austin Schuh006a9f52021-04-07 16:24:18 -07001237 continue;
1238 }
1239
1240 // Now handle node specific maps.
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001241 const flatbuffers::String *const match_source_node_string =
1242 match->source_node();
1243 if (node && match_source_node_string &&
1244 match_source_node_string->string_view() !=
Austin Schuh006a9f52021-04-07 16:24:18 -07001245 node->name()->string_view()) {
1246 continue;
1247 }
1248
Brian Silvermanf3798cb2021-11-10 12:26:34 -08001249 std::string new_name(rename_name_string->string_view());
Austin Schuh006a9f52021-04-07 16:24:18 -07001250 if (match_name.back() == '*') {
1251 new_name += std::string(name->substr(match_name.size() - 1));
1252 }
1253 VLOG(1) << "Renamed \"" << *name << "\" to \"" << new_name << "\"";
1254 *name = std::move(new_name);
1255 }
1256}
1257
James Kuszmaulc8503f32022-06-25 16:17:12 -07001258std::set<std::string> GetChannelAliases(const Configuration *config,
1259 std::string_view name,
1260 std::string_view type,
1261 const std::string_view application_name,
1262 const Node *node) {
1263 std::set<std::string> names{std::string(name)};
1264 if (config->has_maps()) {
1265 HandleReverseMaps(config->maps(), type, node, &names);
1266 }
1267 {
1268 const Application *application =
1269 GetApplication(config, node, application_name);
1270 if (application != nullptr && application->has_maps()) {
1271 HandleReverseMaps(application->maps(), type, node, &names);
1272 }
1273 }
1274 return names;
1275}
1276
Austin Schuh40485ed2019-10-26 21:51:44 -07001277FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
Austin Schuhcb108412019-10-13 16:09:54 -07001278 const Flatbuffer<Configuration> &config) {
Austin Schuh605d5ff2024-05-10 15:59:54 -07001279 MutableConfiguration unpacked_config;
James Kuszmaul3c998592020-07-27 21:04:47 -07001280
Austin Schuh605d5ff2024-05-10 15:59:54 -07001281 // The act of unpacking a config merges everything.
1282 UnpackConfiguration(&config.message(), &unpacked_config);
Austin Schuh217a9782019-12-21 23:02:50 -08001283
Austin Schuhcb108412019-10-13 16:09:54 -07001284 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -08001285 fbb.ForceDefaults(true);
Austin Schuhcb108412019-10-13 16:09:54 -07001286
Austin Schuh605d5ff2024-05-10 15:59:54 -07001287 fbb.Finish(PackConfiguration(unpacked_config, &fbb));
Austin Schuhcb108412019-10-13 16:09:54 -07001288
Austin Schuh605d5ff2024-05-10 15:59:54 -07001289 FlatbufferDetachedBuffer<aos::Configuration> result(fbb.Release());
Austin Schuh217a9782019-12-21 23:02:50 -08001290
Austin Schuh15182322020-10-10 15:25:21 -07001291 ValidateConfiguration(result);
Austin Schuh217a9782019-12-21 23:02:50 -08001292
1293 return result;
Austin Schuhcb108412019-10-13 16:09:54 -07001294}
1295
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001296std::optional<FlatbufferDetachedBuffer<Configuration>> MaybeReadConfig(
James Kuszmaulc0c08da2020-05-10 18:56:07 -07001297 const std::string_view path,
Austin Schuhef38cd22021-07-21 15:24:23 -07001298 const std::vector<std::string_view> &extra_import_paths) {
Austin Schuh89026f52022-02-25 14:24:04 -08001299 // Add the executable directory to the search path. That makes it so that
1300 // tools can be run from any directory without hard-coding an absolute path to
1301 // the config into all binaries.
1302 std::vector<std::string_view> extra_import_paths_with_exe =
1303 extra_import_paths;
1304 char proc_self_exec_buffer[PATH_MAX + 1];
1305 std::memset(proc_self_exec_buffer, 0, sizeof(proc_self_exec_buffer));
1306 ssize_t s = readlink("/proc/self/exe", proc_self_exec_buffer, PATH_MAX);
1307 if (s > 0) {
1308 // If the readlink call fails, the worst thing that happens is that we don't
1309 // automatically find the config next to the binary. VLOG to make it easier
1310 // to debug.
1311 std::string_view proc_self_exec(proc_self_exec_buffer);
1312
1313 extra_import_paths_with_exe.emplace_back(
1314 proc_self_exec.substr(0, proc_self_exec.rfind("/")));
1315 } else {
1316 VLOG(1) << "Failed to read /proc/self/exe";
1317 }
1318
Austin Schuhcb108412019-10-13 16:09:54 -07001319 // We only want to read a file once. So track the visited files in a set.
1320 absl::btree_set<std::string> visited_paths;
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001321 std::optional<FlatbufferDetachedBuffer<Configuration>> read_config =
1322 MaybeReadConfig(path, &visited_paths, extra_import_paths_with_exe);
1323
1324 if (read_config == std::nullopt) {
1325 return read_config;
1326 }
Austin Schuh15182322020-10-10 15:25:21 -07001327
1328 // If we only read one file, and it had a .bfbs extension, it has to be a
1329 // fully formatted config. Do a quick verification and return it.
1330 if (visited_paths.size() == 1 && EndsWith(*visited_paths.begin(), ".bfbs")) {
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001331 ValidateConfiguration(*read_config);
Austin Schuh15182322020-10-10 15:25:21 -07001332 return read_config;
1333 }
1334
Milind Upadhyay17098ba2022-04-15 22:18:50 -07001335 return MergeConfiguration(*read_config);
1336}
1337
1338FlatbufferDetachedBuffer<Configuration> ReadConfig(
1339 const std::string_view path,
1340 const std::vector<std::string_view> &extra_import_paths) {
1341 auto optional_config = MaybeReadConfig(path, extra_import_paths);
1342 CHECK(optional_config) << "Could not read config. See above errors";
1343 return std::move(*optional_config);
Austin Schuhcb108412019-10-13 16:09:54 -07001344}
1345
Austin Schuh8d6cea82020-02-28 12:17:16 -08001346FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Brian Silverman24f5aa82020-06-23 16:21:28 -07001347 const Configuration *config, const Flatbuffer<Configuration> &addition) {
1348 return MergeConfiguration(MergeFlatBuffers(config, &addition.message()));
1349}
1350
1351FlatbufferDetachedBuffer<Configuration> MergeWithConfig(
Austin Schuh8d6cea82020-02-28 12:17:16 -08001352 const Configuration *config, std::string_view json) {
1353 FlatbufferDetachedBuffer<Configuration> addition =
1354 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1355
Brian Silverman24f5aa82020-06-23 16:21:28 -07001356 return MergeWithConfig(config, addition);
Austin Schuh8d6cea82020-02-28 12:17:16 -08001357}
1358
James Kuszmaul3ae42262019-11-08 12:33:41 -08001359const Channel *GetChannel(const Configuration *config, std::string_view name,
1360 std::string_view type,
Austin Schuh0de30f32020-12-06 12:44:28 -08001361 std::string_view application_name, const Node *node,
1362 bool quiet) {
Brian Silverman9fcf2c72020-12-21 18:30:58 -08001363 if (!config->has_channels()) {
1364 return nullptr;
1365 }
1366
Austin Schuhbca6cf02019-12-22 17:28:34 -08001367 const std::string_view original_name = name;
Austin Schuhf1fff282020-03-28 16:57:32 -07001368 std::string mutable_name;
Austin Schuh4c3b9702020-08-30 11:34:55 -07001369 if (node != nullptr) {
1370 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
1371 << "\" } on " << aos::FlatbufferToJson(node);
1372 } else {
1373 VLOG(1) << "Looking up { \"name\": \"" << name << "\", \"type\": \"" << type
1374 << "\" }";
1375 }
Austin Schuhcb108412019-10-13 16:09:54 -07001376
1377 // First handle application specific maps. Only do this if we have a matching
1378 // application name, and it has maps.
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001379 {
1380 const Application *application =
1381 GetApplication(config, node, application_name);
1382 if (application != nullptr && application->has_maps()) {
1383 mutable_name = std::string(name);
1384 HandleMaps(application->maps(), &mutable_name, type, node);
1385 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -07001386 }
1387 }
1388
1389 // Now do global maps.
Austin Schuh40485ed2019-10-26 21:51:44 -07001390 if (config->has_maps()) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001391 mutable_name = std::string(name);
1392 HandleMaps(config->maps(), &mutable_name, type, node);
1393 name = std::string_view(mutable_name);
Austin Schuhcb108412019-10-13 16:09:54 -07001394 }
1395
Austin Schuhbca6cf02019-12-22 17:28:34 -08001396 if (original_name != name) {
1397 VLOG(1) << "Remapped to { \"name\": \"" << name << "\", \"type\": \""
1398 << type << "\" }";
1399 }
Alex Perrycb7da4b2019-08-28 19:35:56 -07001400
James Kuszmaul71a81932020-12-15 21:08:01 -08001401 // Then look for the channel (note that this relies on the channels being
1402 // sorted in the config).
Austin Schuh40485ed2019-10-26 21:51:44 -07001403 auto channel_iterator =
Austin Schuhf1fff282020-03-28 16:57:32 -07001404 std::lower_bound(config->channels()->cbegin(), config->channels()->cend(),
Austin Schuh40485ed2019-10-26 21:51:44 -07001405 std::make_pair(name, type), CompareChannels);
Austin Schuhcb108412019-10-13 16:09:54 -07001406
1407 // Make sure we actually found it, and it matches.
Austin Schuh40485ed2019-10-26 21:51:44 -07001408 if (channel_iterator != config->channels()->cend() &&
1409 EqualsChannels(*channel_iterator, std::make_pair(name, type))) {
Austin Schuhbca6cf02019-12-22 17:28:34 -08001410 if (VLOG_IS_ON(2)) {
1411 VLOG(2) << "Found: " << FlatbufferToJson(*channel_iterator);
1412 } else if (VLOG_IS_ON(1)) {
1413 VLOG(1) << "Found: " << CleanedChannelToString(*channel_iterator);
1414 }
Austin Schuh40485ed2019-10-26 21:51:44 -07001415 return *channel_iterator;
Austin Schuhcb108412019-10-13 16:09:54 -07001416 } else {
1417 VLOG(1) << "No match for { \"name\": \"" << name << "\", \"type\": \""
1418 << type << "\" }";
Austin Schuh0de30f32020-12-06 12:44:28 -08001419 if (original_name != name && !quiet) {
Maxwell Gumleya28d6502023-11-17 10:43:36 -07001420 VLOG(1) << "Remapped from {\"name\": \"" << original_name
1421 << "\", \"type\": \"" << type << "\"}, to {\"name\": \"" << name
1422 << "\", \"type\": \"" << type
1423 << "\"}, but no channel by that name exists.";
Austin Schuh4b42b252020-10-19 11:35:20 -07001424 }
Austin Schuhcb108412019-10-13 16:09:54 -07001425 return nullptr;
1426 }
1427}
1428
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001429size_t ChannelIndex(const Configuration *configuration,
1430 const Channel *channel) {
1431 CHECK(configuration->channels() != nullptr) << ": No channels";
1432
Brian Silvermana9698c92021-11-10 12:27:04 -08001433 const auto c = std::lower_bound(
1434 configuration->channels()->cbegin(), configuration->channels()->cend(),
1435 std::make_pair(channel->name()->string_view(),
1436 channel->type()->string_view()),
1437 CompareChannels);
1438 CHECK(c != configuration->channels()->cend())
1439 << ": Channel pointer not found in configuration()->channels()";
1440 CHECK(*c == channel)
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001441 << ": Channel pointer not found in configuration()->channels()";
1442
Brian Silvermana9698c92021-11-10 12:27:04 -08001443 return std::distance(configuration->channels()->cbegin(), c);
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001444}
1445
Austin Schuhbca6cf02019-12-22 17:28:34 -08001446std::string CleanedChannelToString(const Channel *channel) {
1447 FlatbufferDetachedBuffer<Channel> cleaned_channel = CopyFlatBuffer(channel);
1448 cleaned_channel.mutable_message()->clear_schema();
1449 return FlatbufferToJson(cleaned_channel);
1450}
1451
Austin Schuha81454b2020-05-12 19:58:36 -07001452std::string StrippedChannelToString(const Channel *channel) {
1453 return absl::StrCat("{ \"name\": \"", channel->name()->string_view(),
1454 "\", \"type\": \"", channel->type()->string_view(),
1455 "\" }");
1456}
1457
Alex Perrycb7da4b2019-08-28 19:35:56 -07001458FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
1459 const Flatbuffer<Configuration> &config,
Austin Schuh0de30f32020-12-06 12:44:28 -08001460 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
Austin Schuh605d5ff2024-05-10 15:59:54 -07001461 MutableConfiguration unpacked_config;
1462 UnpackConfiguration(&config.message(), &unpacked_config);
1463
1464 // Now, add the schemas in so they will get packed.
1465 for (const aos::FlatbufferVector<reflection::Schema> &schema : schemas) {
1466 CHECK(schema.message().has_root_table());
1467 CHECK(schema.message().root_table()->has_name());
1468 unpacked_config.schemas.emplace(
1469 schema.message().root_table()->name()->string_view(),
1470 &schema.message());
1471 }
1472
Alex Perrycb7da4b2019-08-28 19:35:56 -07001473 flatbuffers::FlatBufferBuilder fbb;
Austin Schuhd7b15da2020-02-17 15:06:11 -08001474 fbb.ForceDefaults(true);
Alex Perrycb7da4b2019-08-28 19:35:56 -07001475
Austin Schuh605d5ff2024-05-10 15:59:54 -07001476 fbb.Finish(PackConfiguration(unpacked_config, &fbb));
Austin Schuh68d98592020-11-01 23:22:57 -08001477
Austin Schuh605d5ff2024-05-10 15:59:54 -07001478 return aos::FlatbufferDetachedBuffer<aos::Configuration>(fbb.Release());
Alex Perrycb7da4b2019-08-28 19:35:56 -07001479}
1480
Austin Schuh217a9782019-12-21 23:02:50 -08001481const Node *GetNodeFromHostname(const Configuration *config,
1482 std::string_view hostname) {
1483 for (const Node *node : *config->nodes()) {
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001484 if (node->has_hostname() && node->hostname()->string_view() == hostname) {
Austin Schuh217a9782019-12-21 23:02:50 -08001485 return node;
1486 }
Brian Silvermanaa2633f2020-02-17 21:04:14 -08001487 if (node->has_hostnames()) {
1488 for (const auto &candidate : *node->hostnames()) {
1489 if (candidate->string_view() == hostname) {
1490 return node;
1491 }
1492 }
1493 }
Austin Schuh217a9782019-12-21 23:02:50 -08001494 }
1495 return nullptr;
1496}
Austin Schuhac0771c2020-01-07 18:36:30 -08001497
Maxwell Gumleyf84aca32024-05-10 13:51:59 -06001498bool IsNodeFromConfiguration(const Configuration *config, const Node *node) {
1499 if (config == nullptr) {
1500 return false;
1501 }
1502
1503 // Check if is multinode
1504 if (MultiNode(config)) {
1505 if (node == nullptr) {
1506 return false;
1507 }
1508 for (const Node *node_from_config : *config->nodes()) {
1509 if (node_from_config == node) {
1510 return true;
1511 }
1512 }
1513 return false;
1514 } else {
1515 // nullptr is the node for all single node configurations. Return true so
1516 // this function can be used in the same way for single or multinode
1517 // configurations.
1518 return node == nullptr;
1519 }
1520}
1521
Austin Schuh63097262023-08-16 17:04:29 -07001522std::string_view NodeName(const Configuration *config, size_t node_index) {
1523 if (!configuration::MultiNode(config)) {
1524 return "(singlenode)";
1525 }
1526 return config->nodes()->Get(node_index)->name()->string_view();
1527}
1528
Austin Schuh217a9782019-12-21 23:02:50 -08001529const Node *GetMyNode(const Configuration *config) {
1530 const std::string hostname = (FLAGS_override_hostname.size() > 0)
1531 ? FLAGS_override_hostname
1532 : network::GetHostname();
1533 const Node *node = GetNodeFromHostname(config, hostname);
1534 if (node != nullptr) return node;
1535
1536 LOG(FATAL) << "Unknown node for host: " << hostname
1537 << ". Consider using --override_hostname if hostname detection "
1538 "is wrong.";
1539 return nullptr;
1540}
1541
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001542const Node *GetNode(const Configuration *config, const Node *node) {
1543 if (!MultiNode(config)) {
1544 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1545 return nullptr;
1546 } else {
1547 CHECK(node != nullptr);
1548 CHECK(node->has_name());
1549 return GetNode(config, node->name()->string_view());
1550 }
1551}
1552
Austin Schuh217a9782019-12-21 23:02:50 -08001553const Node *GetNode(const Configuration *config, std::string_view name) {
Alexei Strots4b1e1442023-05-01 22:11:05 -07001554 if (!MultiNode(config)) {
1555 if (name.empty()) {
1556 return nullptr;
1557 }
1558 LOG(FATAL) << ": Asking for a named node from a single node configuration.";
1559 }
Austin Schuh217a9782019-12-21 23:02:50 -08001560 for (const Node *node : *config->nodes()) {
Austin Schuhfd960622020-01-01 13:22:55 -08001561 CHECK(node->has_name()) << ": Malformed node " << FlatbufferToJson(node);
Austin Schuh217a9782019-12-21 23:02:50 -08001562 if (node->name()->string_view() == name) {
1563 return node;
1564 }
1565 }
1566 return nullptr;
1567}
1568
Austin Schuh0ca1fd32020-12-18 22:53:05 -08001569const Node *GetNode(const Configuration *config, size_t node_index) {
1570 if (!MultiNode(config)) {
1571 CHECK_EQ(node_index, 0u) << ": Invalid node in a single node world.";
1572 return nullptr;
1573 } else {
1574 CHECK_LT(node_index, config->nodes()->size());
1575 return config->nodes()->Get(node_index);
1576 }
1577}
1578
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001579const Node *GetNodeOrDie(const Configuration *config, const Node *node) {
1580 if (!MultiNode(config)) {
1581 CHECK(node == nullptr) << ": Provided a node in a single node world.";
1582 return nullptr;
1583 } else {
1584 const Node *config_node = GetNode(config, node);
1585 if (config_node == nullptr) {
1586 LOG(FATAL) << "Couldn't find node matching " << FlatbufferToJson(node);
1587 }
1588 return config_node;
1589 }
1590}
1591
Austin Schuh8bd96322020-02-13 21:18:22 -08001592namespace {
1593int GetNodeIndexFromConfig(const Configuration *config, const Node *node) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001594 int node_index = 0;
1595 for (const Node *iterated_node : *config->nodes()) {
1596 if (iterated_node == node) {
1597 return node_index;
1598 }
1599 ++node_index;
1600 }
Austin Schuh8bd96322020-02-13 21:18:22 -08001601 return -1;
1602}
1603} // namespace
1604
Austin Schuha9df9ad2021-06-16 14:49:39 -07001605aos::FlatbufferDetachedBuffer<aos::Configuration> AddSchema(
1606 std::string_view json,
1607 const std::vector<aos::FlatbufferVector<reflection::Schema>> &schemas) {
1608 FlatbufferDetachedBuffer<Configuration> addition =
1609 JsonToFlatbuffer(json, Configuration::MiniReflectTypeTable());
1610 return MergeConfiguration(addition, schemas);
1611}
1612
Austin Schuh8bd96322020-02-13 21:18:22 -08001613int GetNodeIndex(const Configuration *config, const Node *node) {
1614 if (!MultiNode(config)) {
1615 return 0;
1616 }
1617
1618 {
1619 int node_index = GetNodeIndexFromConfig(config, node);
1620 if (node_index != -1) {
1621 return node_index;
1622 }
1623 }
1624
1625 const Node *result = GetNode(config, node);
1626 CHECK(result != nullptr);
1627
1628 {
Austin Schuh04408fc2020-02-16 21:48:54 -08001629 int node_index = GetNodeIndexFromConfig(config, result);
Austin Schuh8bd96322020-02-13 21:18:22 -08001630 if (node_index != -1) {
1631 return node_index;
1632 }
1633 }
1634
1635 LOG(FATAL) << "Node " << FlatbufferToJson(node)
1636 << " not found in the configuration.";
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001637}
1638
Austin Schuh04408fc2020-02-16 21:48:54 -08001639int GetNodeIndex(const Configuration *config, std::string_view name) {
1640 if (!MultiNode(config)) {
1641 return 0;
1642 }
1643
1644 {
1645 int node_index = 0;
1646 for (const Node *iterated_node : *config->nodes()) {
1647 if (iterated_node->name()->string_view() == name) {
1648 return node_index;
1649 }
1650 ++node_index;
1651 }
1652 }
1653 LOG(FATAL) << "Node " << name << " not found in the configuration.";
1654}
1655
Austin Schuh681a2472020-12-31 23:55:40 -08001656size_t NodesCount(const Configuration *config) {
1657 if (!MultiNode(config)) {
1658 return 1u;
1659 }
1660
1661 return config->nodes()->size();
1662}
1663
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001664std::vector<const Node *> GetNodes(const Configuration *config) {
1665 std::vector<const Node *> nodes;
Austin Schuh8bd96322020-02-13 21:18:22 -08001666 if (MultiNode(config)) {
Austin Schuhc9e10ec2020-01-26 16:08:28 -08001667 for (const Node *node : *config->nodes()) {
1668 nodes.emplace_back(node);
1669 }
1670 } else {
1671 nodes.emplace_back(nullptr);
1672 }
1673 return nodes;
1674}
1675
Austin Schuh65465332020-11-05 17:36:53 -08001676std::vector<const Node *> GetNodesWithTag(const Configuration *config,
1677 std::string_view tag) {
1678 std::vector<const Node *> nodes;
1679 if (!MultiNode(config)) {
1680 nodes.emplace_back(nullptr);
1681 } else {
1682 for (const Node *node : *config->nodes()) {
1683 if (!node->has_tags()) {
1684 continue;
1685 }
1686 bool did_found_tag = false;
1687 for (const flatbuffers::String *found_tag : *node->tags()) {
1688 if (found_tag->string_view() == tag) {
1689 did_found_tag = true;
1690 break;
1691 }
1692 }
1693 if (did_found_tag) {
1694 nodes.emplace_back(node);
1695 }
1696 }
1697 }
1698 return nodes;
1699}
1700
Brian Silverman631b6262021-11-10 12:25:08 -08001701bool NodeHasTag(const Node *node, std::string_view tag) {
1702 if (node == nullptr) {
1703 return true;
1704 }
1705
Austin Schuh97a52432022-08-17 15:02:59 -07001706 if (!node->has_tags()) {
1707 return false;
1708 }
1709
Brian Silverman631b6262021-11-10 12:25:08 -08001710 const auto *const tags = node->tags();
1711 return std::find_if(tags->begin(), tags->end(),
1712 [tag](const flatbuffers::String *candidate) {
1713 return candidate->string_view() == tag;
1714 }) != tags->end();
1715}
1716
Austin Schuhac0771c2020-01-07 18:36:30 -08001717bool MultiNode(const Configuration *config) { return config->has_nodes(); }
1718
Austin Schuh217a9782019-12-21 23:02:50 -08001719bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001720 if (node == nullptr) {
1721 return true;
1722 }
Austin Schuh3c5dae52020-10-06 18:55:18 -07001723 CHECK(channel->has_source_node()) << FlatbufferToJson(channel);
1724 CHECK(node->has_name()) << FlatbufferToJson(node);
Austin Schuh196a4452020-03-15 23:12:03 -07001725 return (CHECK_NOTNULL(channel)->source_node()->string_view() ==
1726 node->name()->string_view());
Austin Schuh217a9782019-12-21 23:02:50 -08001727}
1728
1729bool ChannelIsReadableOnNode(const Channel *channel, const Node *node) {
Austin Schuhca4828c2019-12-28 14:21:35 -08001730 if (node == nullptr) {
1731 return true;
1732 }
1733
Austin Schuh217a9782019-12-21 23:02:50 -08001734 if (channel->source_node()->string_view() == node->name()->string_view()) {
1735 return true;
1736 }
1737
1738 if (!channel->has_destination_nodes()) {
1739 return false;
1740 }
1741
Austin Schuh719946b2019-12-28 14:51:01 -08001742 for (const Connection *connection : *channel->destination_nodes()) {
1743 CHECK(connection->has_name());
1744 if (connection->name()->string_view() == node->name()->string_view()) {
Austin Schuh217a9782019-12-21 23:02:50 -08001745 return true;
1746 }
1747 }
1748
1749 return false;
1750}
1751
James Kuszmaul24db2d32023-05-26 11:40:12 -07001752bool ChannelIsForwardedFromNode(const Channel *channel, const Node *node) {
1753 if (node == nullptr) {
1754 return false;
1755 }
1756 return ChannelIsSendableOnNode(channel, node) &&
1757 channel->has_destination_nodes() &&
1758 channel->destination_nodes()->size() > 0u;
1759}
1760
Austin Schuh719946b2019-12-28 14:51:01 -08001761bool ChannelMessageIsLoggedOnNode(const Channel *channel, const Node *node) {
Austin Schuh48e94502021-06-18 18:35:53 -07001762 if (node == nullptr) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001763 // Single node world. If there is a local logger, then we want to use
1764 // it.
Austin Schuh48e94502021-06-18 18:35:53 -07001765 if (channel->logger() == LoggerConfig::LOCAL_LOGGER) {
1766 return true;
1767 } else if (channel->logger() == LoggerConfig::NOT_LOGGED) {
1768 return false;
1769 }
1770 LOG(FATAL) << "Unsupported logging configuration in a single node world: "
1771 << CleanedChannelToString(channel);
Austin Schuh2bb80e02021-03-20 21:46:17 -07001772 }
1773 return ChannelMessageIsLoggedOnNode(
1774 channel, CHECK_NOTNULL(node)->name()->string_view());
1775}
1776
1777bool ChannelMessageIsLoggedOnNode(const Channel *channel,
1778 std::string_view node_name) {
Austin Schuhf1fff282020-03-28 16:57:32 -07001779 switch (channel->logger()) {
Austin Schuh719946b2019-12-28 14:51:01 -08001780 case LoggerConfig::LOCAL_LOGGER:
Austin Schuh2bb80e02021-03-20 21:46:17 -07001781 return channel->source_node()->string_view() == node_name;
Austin Schuh719946b2019-12-28 14:51:01 -08001782 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001783 CHECK(channel->has_logger_nodes())
1784 << "Missing logger nodes on " << StrippedChannelToString(channel);
1785 CHECK_GT(channel->logger_nodes()->size(), 0u)
1786 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuh719946b2019-12-28 14:51:01 -08001787
Austin Schuh2bb80e02021-03-20 21:46:17 -07001788 if (channel->source_node()->string_view() == node_name) {
Austin Schuh719946b2019-12-28 14:51:01 -08001789 return true;
1790 }
Austin Schuhda40e472020-03-28 15:15:29 -07001791
1792 [[fallthrough]];
1793 case LoggerConfig::REMOTE_LOGGER:
James Kuszmaulf645c7d2023-02-23 14:21:04 -08001794 CHECK(channel->has_logger_nodes())
1795 << "Missing logger nodes on " << StrippedChannelToString(channel);
1796 CHECK_GT(channel->logger_nodes()->size(), 0u)
1797 << "Missing logger nodes on " << StrippedChannelToString(channel);
Austin Schuhda40e472020-03-28 15:15:29 -07001798 for (const flatbuffers::String *logger_node : *channel->logger_nodes()) {
Austin Schuh2bb80e02021-03-20 21:46:17 -07001799 if (logger_node->string_view() == node_name) {
Austin Schuhda40e472020-03-28 15:15:29 -07001800 return true;
1801 }
Austin Schuh719946b2019-12-28 14:51:01 -08001802 }
1803
1804 return false;
1805 case LoggerConfig::NOT_LOGGED:
1806 return false;
1807 }
1808
1809 LOG(FATAL) << "Unknown logger config " << static_cast<int>(channel->logger());
1810}
1811
Austin Schuh58646e22021-08-23 23:51:46 -07001812size_t ConnectionCount(const Channel *channel) {
1813 if (!channel->has_destination_nodes()) {
1814 return 0;
1815 }
1816 return channel->destination_nodes()->size();
1817}
1818
Austin Schuh719946b2019-12-28 14:51:01 -08001819const Connection *ConnectionToNode(const Channel *channel, const Node *node) {
1820 if (!channel->has_destination_nodes()) {
1821 return nullptr;
1822 }
1823 for (const Connection *connection : *channel->destination_nodes()) {
1824 if (connection->name()->string_view() == node->name()->string_view()) {
1825 return connection;
1826 }
1827 }
1828 return nullptr;
1829}
1830
1831bool ConnectionDeliveryTimeIsLoggedOnNode(const Channel *channel,
1832 const Node *node,
1833 const Node *logger_node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001834 return ConnectionDeliveryTimeIsLoggedOnNode(ConnectionToNode(channel, node),
1835 logger_node);
Austin Schuh719946b2019-12-28 14:51:01 -08001836}
1837
1838bool ConnectionDeliveryTimeIsLoggedOnNode(const Connection *connection,
1839 const Node *node) {
Austin Schuh72211ae2021-08-05 14:02:30 -07001840 if (connection == nullptr) {
1841 return false;
1842 }
Austin Schuh719946b2019-12-28 14:51:01 -08001843 switch (connection->timestamp_logger()) {
1844 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
Austin Schuhda40e472020-03-28 15:15:29 -07001845 CHECK(connection->has_timestamp_logger_nodes());
1846 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
Austin Schuh719946b2019-12-28 14:51:01 -08001847 if (connection->name()->string_view() == node->name()->string_view()) {
1848 return true;
1849 }
1850
Austin Schuhda40e472020-03-28 15:15:29 -07001851 [[fallthrough]];
1852 case LoggerConfig::REMOTE_LOGGER:
1853 CHECK(connection->has_timestamp_logger_nodes());
1854 CHECK_GT(connection->timestamp_logger_nodes()->size(), 0u);
1855 for (const flatbuffers::String *timestamp_logger_node :
1856 *connection->timestamp_logger_nodes()) {
1857 if (timestamp_logger_node->string_view() ==
1858 node->name()->string_view()) {
1859 return true;
1860 }
Austin Schuh719946b2019-12-28 14:51:01 -08001861 }
1862
1863 return false;
1864 case LoggerConfig::LOCAL_LOGGER:
1865 return connection->name()->string_view() == node->name()->string_view();
Austin Schuh719946b2019-12-28 14:51:01 -08001866 case LoggerConfig::NOT_LOGGED:
1867 return false;
1868 }
1869
1870 LOG(FATAL) << "Unknown logger config "
1871 << static_cast<int>(connection->timestamp_logger());
1872}
1873
Austin Schuhe84c3ed2019-12-14 15:29:48 -08001874std::vector<std::string_view> SourceNodeNames(const Configuration *config,
1875 const Node *my_node) {
1876 std::set<std::string_view> result_set;
1877
1878 for (const Channel *channel : *config->channels()) {
1879 if (channel->has_destination_nodes()) {
1880 for (const Connection *connection : *channel->destination_nodes()) {
1881 if (connection->name()->string_view() ==
1882 my_node->name()->string_view()) {
1883 result_set.insert(channel->source_node()->string_view());
1884 }
1885 }
1886 }
1887 }
1888
1889 std::vector<std::string_view> result;
1890 for (const std::string_view source : result_set) {
1891 VLOG(1) << "Found a source node of " << source;
1892 result.emplace_back(source);
1893 }
1894 return result;
1895}
1896
1897std::vector<std::string_view> DestinationNodeNames(const Configuration *config,
1898 const Node *my_node) {
1899 std::vector<std::string_view> result;
1900
1901 for (const Channel *channel : *config->channels()) {
1902 if (channel->has_source_node() && channel->source_node()->string_view() ==
1903 my_node->name()->string_view()) {
1904 if (!channel->has_destination_nodes()) continue;
1905
1906 if (channel->source_node()->string_view() !=
1907 my_node->name()->string_view()) {
1908 continue;
1909 }
1910
1911 for (const Connection *connection : *channel->destination_nodes()) {
1912 if (std::find(result.begin(), result.end(),
1913 connection->name()->string_view()) == result.end()) {
1914 result.emplace_back(connection->name()->string_view());
1915 }
1916 }
1917 }
1918 }
1919
1920 for (const std::string_view destination : result) {
1921 VLOG(1) << "Found a destination node of " << destination;
1922 }
1923 return result;
1924}
1925
Austin Schuh8d7e0bb2020-10-02 17:57:00 -07001926std::vector<const Node *> TimestampNodes(const Configuration *config,
1927 const Node *my_node) {
1928 if (!configuration::MultiNode(config)) {
1929 CHECK(my_node == nullptr);
1930 return std::vector<const Node *>{};
1931 }
1932
1933 std::set<const Node *> timestamp_logger_nodes;
1934 for (const Channel *channel : *config->channels()) {
1935 if (!configuration::ChannelIsSendableOnNode(channel, my_node)) {
1936 continue;
1937 }
1938 if (!channel->has_destination_nodes()) {
1939 continue;
1940 }
1941 for (const Connection *connection : *channel->destination_nodes()) {
1942 const Node *other_node =
1943 configuration::GetNode(config, connection->name()->string_view());
1944
1945 if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(connection,
1946 my_node)) {
1947 VLOG(1) << "Timestamps are logged from "
1948 << FlatbufferToJson(other_node);
1949 timestamp_logger_nodes.insert(other_node);
1950 }
1951 }
1952 }
1953
1954 std::vector<const Node *> result;
1955 for (const Node *node : timestamp_logger_nodes) {
1956 result.emplace_back(node);
1957 }
1958 return result;
1959}
1960
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001961bool ApplicationShouldStart(const Configuration *config, const Node *my_node,
1962 const Application *application) {
1963 if (MultiNode(config)) {
1964 // Ok, we need
1965 CHECK(application->has_nodes());
1966 CHECK(my_node != nullptr);
1967 for (const flatbuffers::String *str : *application->nodes()) {
1968 if (str->string_view() == my_node->name()->string_view()) {
1969 return true;
1970 }
1971 }
1972 return false;
1973 } else {
1974 return true;
1975 }
1976}
1977
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001978const Application *GetApplication(const Configuration *config,
1979 const Node *my_node,
1980 std::string_view application_name) {
1981 if (config->has_applications()) {
1982 auto application_iterator = std::lower_bound(
1983 config->applications()->cbegin(), config->applications()->cend(),
1984 application_name, CompareApplications);
1985 if (application_iterator != config->applications()->cend() &&
1986 EqualsApplications(*application_iterator, application_name)) {
Austin Schuhc41fa3c2021-10-16 14:35:35 -07001987 if (ApplicationShouldStart(config, my_node, *application_iterator)) {
Austin Schuhd2e2f6a2021-02-07 20:46:16 -08001988 return *application_iterator;
1989 }
1990 }
1991 }
1992 return nullptr;
1993}
1994
Austin Schuh1ccc3a12024-04-30 17:46:29 -07001995const Node *SourceNode(const Configuration *config, const Channel *channel) {
1996 if (!MultiNode(config)) {
1997 return nullptr;
1998 }
1999 return GetNode(config, channel->source_node()->string_view());
2000}
2001
Austin Schuhfc7b6a02021-07-12 21:19:07 -07002002std::vector<size_t> SourceNodeIndex(const Configuration *config) {
2003 CHECK(config->has_channels());
2004 std::vector<size_t> result;
2005 result.resize(config->channels()->size(), 0u);
2006 if (MultiNode(config)) {
2007 for (size_t i = 0; i < config->channels()->size(); ++i) {
2008 result[i] = GetNodeIndex(
2009 config, config->channels()->Get(i)->source_node()->string_view());
2010 }
2011 }
2012 return result;
2013}
2014
Austin Schuhfff9c3a2023-06-16 18:48:23 -07002015chrono::nanoseconds ChannelStorageDuration(const Configuration *config,
2016 const Channel *channel) {
2017 CHECK(channel != nullptr);
Austin Schuhdda6db72023-06-21 17:02:34 -07002018 if (channel->has_channel_storage_duration()) {
2019 return chrono::nanoseconds(channel->channel_storage_duration());
2020 }
Austin Schuhfff9c3a2023-06-16 18:48:23 -07002021 return chrono::nanoseconds(config->channel_storage_duration());
2022}
2023
Austin Schuh83cbb1e2023-06-23 12:59:02 -07002024size_t QueueSize(const Configuration *config, const Channel *channel) {
Austin Schuhfb37c612022-08-11 15:24:51 -07002025 return QueueSize(channel->frequency(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -07002026 ChannelStorageDuration(config, channel));
Austin Schuhfb37c612022-08-11 15:24:51 -07002027}
2028
Austin Schuh83cbb1e2023-06-23 12:59:02 -07002029size_t QueueSize(size_t frequency,
2030 chrono::nanoseconds channel_storage_duration) {
Austin Schuhfb37c612022-08-11 15:24:51 -07002031 // Use integer arithmetic and round up at all cost.
2032 return static_cast<int>(
Philipp Schrader0434f902023-09-06 08:41:32 -07002033 (999'999'999 +
2034 static_cast<int64_t>(frequency) *
2035 static_cast<int64_t>(channel_storage_duration.count())) /
2036 static_cast<int64_t>(1'000'000'000));
Austin Schuhfb37c612022-08-11 15:24:51 -07002037}
2038
2039int QueueScratchBufferSize(const Channel *channel) {
2040 return channel->num_readers() + channel->num_senders();
2041}
2042
Nathan Leong307c9692022-10-08 15:25:03 -07002043// Searches through configurations for schemas that include a certain type
2044const reflection::Schema *GetSchema(const Configuration *config,
2045 std::string_view schema_type) {
2046 if (config->has_channels()) {
2047 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
2048 for (const Channel *c : *config->channels()) {
2049 if (schema_type == c->type()->string_view()) {
2050 return c->schema();
2051 }
2052 }
2053 }
2054 return nullptr;
2055}
2056
2057// Copy schema reflection into detached flatbuffer
2058std::optional<FlatbufferDetachedBuffer<reflection::Schema>>
2059GetSchemaDetachedBuffer(const Configuration *config,
2060 std::string_view schema_type) {
2061 const reflection::Schema *found_schema = GetSchema(config, schema_type);
2062 if (found_schema == nullptr) {
2063 return std::nullopt;
2064 }
2065 return RecursiveCopyFlatBuffer(found_schema);
2066}
2067
James Kuszmaul741a4d02023-01-05 14:59:21 -08002068aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
2069 const Configuration *config, std::string_view name,
2070 aos::FlatbufferVector<reflection::Schema> schema, const aos::Node *node,
2071 ChannelT overrides) {
2072 overrides.name = name;
James Kuszmaul80d6c422023-01-06 14:16:04 -08002073 CHECK(schema.message().has_root_table());
James Kuszmaul741a4d02023-01-05 14:59:21 -08002074 overrides.type = schema.message().root_table()->name()->string_view();
2075 if (node != nullptr) {
2076 CHECK(node->has_name());
2077 overrides.source_node = node->name()->string_view();
2078 }
Austin Schuh605d5ff2024-05-10 15:59:54 -07002079
2080 // TODO(austin): Use MutableConfiguration to represent this transform more
2081 // efficiently.
James Kuszmaul741a4d02023-01-05 14:59:21 -08002082 flatbuffers::FlatBufferBuilder fbb;
2083 // Don't populate fields from overrides that the user doesn't explicitly
2084 // override.
2085 fbb.ForceDefaults(false);
2086 const flatbuffers::Offset<Channel> channel_offset =
2087 Channel::Pack(fbb, &overrides);
2088 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
2089 channels_offset = fbb.CreateVector({channel_offset});
2090 Configuration::Builder config_builder(fbb);
2091 config_builder.add_channels(channels_offset);
2092 fbb.Finish(config_builder.Finish());
2093 FlatbufferDetachedBuffer<Configuration> new_channel_config = fbb.Release();
2094 new_channel_config = MergeConfiguration(new_channel_config, {schema});
2095 return MergeWithConfig(config, new_channel_config);
2096}
2097
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07002098FlatbufferDetachedBuffer<Configuration> GetPartialConfiguration(
2099 const Configuration &configuration,
2100 std::function<bool(const Channel &)> should_include_channel) {
Austin Schuh605d5ff2024-05-10 15:59:54 -07002101 // TODO(austin): Use MutableConfiguration to represent this better.
2102 //
Maxwell Gumley8c1b87f2024-02-13 17:54:52 -07002103 // create new_configuration1, containing everything except the `channels`
2104 // field.
2105 FlatbufferDetachedBuffer<Configuration> new_configuration1 =
2106 RecursiveCopyFlatBuffer(&configuration);
2107 new_configuration1.mutable_message()->clear_channels();
2108
2109 // create new_configuration2, containing only the `channels` field.
2110 flatbuffers::FlatBufferBuilder fbb;
2111 std::vector<flatbuffers::Offset<Channel>> new_channels_vec;
2112 for (const auto &channel : *configuration.channels()) {
2113 CHECK_NOTNULL(channel);
2114 if (should_include_channel(*channel)) {
2115 new_channels_vec.push_back(RecursiveCopyFlatBuffer(channel, &fbb));
2116 }
2117 }
2118 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
2119 new_channels_offset = fbb.CreateVector(new_channels_vec);
2120 Configuration::Builder new_configuration2_builder(fbb);
2121 new_configuration2_builder.add_channels(new_channels_offset);
2122 fbb.Finish(new_configuration2_builder.Finish());
2123 FlatbufferDetachedBuffer<Configuration> new_configuration2 = fbb.Release();
2124
2125 // Merge the configuration containing channels with the configuration
2126 // containing everything else, creating a complete configuration.
2127 const aos::FlatbufferDetachedBuffer<Configuration> raw_subset_configuration =
2128 MergeFlatBuffers(&new_configuration1.message(),
2129 &new_configuration2.message());
2130
2131 // Use MergeConfiguration to clean up redundant schemas.
2132 return configuration::MergeConfiguration(raw_subset_configuration);
2133}
Brian Silverman66f079a2013-08-26 16:24:30 -07002134} // namespace configuration
2135} // namespace aos