blob: 088d3936bcc8fd6f094b5369e31a52cd6b6c3abb [file] [log] [blame]
Eric Schmiedeberge279b532023-04-19 16:36:02 -06001#include "aos/events/logging/config_remapper.h"
2
3#include <vector>
4
5#include "absl/strings/escaping.h"
6#include "flatbuffers/flatbuffers.h"
7
8#include "aos/events/logging/logger_generated.h"
9#include "aos/flatbuffer_merge.h"
10#include "aos/json_to_flatbuffer.h"
11#include "aos/network/multinode_timestamp_filter.h"
12#include "aos/network/remote_message_generated.h"
13#include "aos/network/remote_message_schema.h"
14#include "aos/network/team_number.h"
15#include "aos/network/timestamp_channel.h"
16
17namespace aos {
18using message_bridge::RemoteMessage;
19
20namespace {
21// Checks if the specified channel name/type exists in the config and, depending
22// on the value of conflict_handling, calls conflict_handler or just dies.
23template <typename F>
24void CheckAndHandleRemapConflict(
25 std::string_view new_name, std::string_view new_type,
26 const Configuration *config,
27 ConfigRemapper::RemapConflict conflict_handling, F conflict_handler) {
28 const Channel *existing_channel =
29 configuration::GetChannel(config, new_name, new_type, "", nullptr, true);
30 if (existing_channel != nullptr) {
31 switch (conflict_handling) {
32 case ConfigRemapper::RemapConflict::kDisallow:
33 LOG(FATAL)
34 << "Channel "
35 << configuration::StrippedChannelToString(existing_channel)
36 << " is already used--you can't remap an original channel to it.";
37 break;
38 case ConfigRemapper::RemapConflict::kCascade:
39 VLOG(1) << "Automatically remapping "
40 << configuration::StrippedChannelToString(existing_channel)
41 << " to avoid conflicts.";
42 conflict_handler();
43 break;
44 }
45 }
46}
47} // namespace
48
49namespace configuration {
50// We don't really want to expose this publicly, but log reader doesn't really
51// want to re-implement it.
52void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<Map>> *maps,
53 std::string *name, std::string_view type, const Node *node);
54} // namespace configuration
55
56bool CompareChannels(const Channel *c,
57 ::std::pair<std::string_view, std::string_view> p) {
58 int name_compare = c->name()->string_view().compare(p.first);
59 if (name_compare == 0) {
60 return c->type()->string_view() < p.second;
61 } else if (name_compare < 0) {
62 return true;
63 } else {
64 return false;
65 }
66}
67
68bool EqualsChannels(const Channel *c,
69 ::std::pair<std::string_view, std::string_view> p) {
70 return c->name()->string_view() == p.first &&
71 c->type()->string_view() == p.second;
72}
73// Copies the channel, removing the schema as we go. If new_name is provided,
74// it is used instead of the name inside the channel. If new_type is provided,
75// it is used instead of the type in the channel.
76flatbuffers::Offset<Channel> CopyChannel(const Channel *c,
77 std::string_view new_name,
78 std::string_view new_type,
79 flatbuffers::FlatBufferBuilder *fbb) {
80 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
81 << ": Merging logic needs to be updated when the number of channel "
82 "fields changes.";
83
84 flatbuffers::Offset<flatbuffers::String> name_offset =
85 fbb->CreateSharedString(new_name.empty() ? c->name()->string_view()
86 : new_name);
87 flatbuffers::Offset<flatbuffers::String> type_offset =
88 fbb->CreateSharedString(new_type.empty() ? c->type()->str() : new_type);
89 flatbuffers::Offset<flatbuffers::String> source_node_offset =
90 c->has_source_node() ? fbb->CreateSharedString(c->source_node()->str())
91 : 0;
92
93 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Connection>>>
94 destination_nodes_offset =
95 RecursiveCopyVectorTable(c->destination_nodes(), fbb);
96
97 flatbuffers::Offset<
98 flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
99 logger_nodes_offset = CopyVectorSharedString(c->logger_nodes(), fbb);
100
101 Channel::Builder channel_builder(*fbb);
102 channel_builder.add_name(name_offset);
103 channel_builder.add_type(type_offset);
104 if (c->has_frequency()) {
105 channel_builder.add_frequency(c->frequency());
106 }
107 if (c->has_max_size()) {
108 channel_builder.add_max_size(c->max_size());
109 }
110 if (c->has_num_senders()) {
111 channel_builder.add_num_senders(c->num_senders());
112 }
113 if (c->has_num_watchers()) {
114 channel_builder.add_num_watchers(c->num_watchers());
115 }
116 if (!source_node_offset.IsNull()) {
117 channel_builder.add_source_node(source_node_offset);
118 }
119 if (!destination_nodes_offset.IsNull()) {
120 channel_builder.add_destination_nodes(destination_nodes_offset);
121 }
122 if (c->has_logger()) {
123 channel_builder.add_logger(c->logger());
124 }
125 if (!logger_nodes_offset.IsNull()) {
126 channel_builder.add_logger_nodes(logger_nodes_offset);
127 }
128 if (c->has_read_method()) {
129 channel_builder.add_read_method(c->read_method());
130 }
131 if (c->has_num_readers()) {
132 channel_builder.add_num_readers(c->num_readers());
133 }
134 if (c->has_channel_storage_duration()) {
135 channel_builder.add_channel_storage_duration(c->channel_storage_duration());
136 }
137 return channel_builder.Finish();
138}
139
140ConfigRemapper::ConfigRemapper(const Configuration *config,
141 const Configuration *replay_config,
142 const logger::ReplayChannels *replay_channels)
143 : remapped_configuration_(config),
144 original_configuration_(config),
145 replay_configuration_(replay_config),
146 replay_channels_(replay_channels) {
147 MakeRemappedConfig();
148
149 // If any remote timestamp channel was not marked NOT_LOGGED, then remap that
150 // channel to avoid the redundant logged data. Also, this loop handles the
151 // MessageHeader to RemoteMessae name change.
152 // Note: This path is mainly for backwards compatibility reasons, and should
153 // not be necessary for any new logs.
154 for (const Node *node : configuration::GetNodes(original_configuration())) {
155 message_bridge::ChannelTimestampFinder finder(original_configuration(),
156 "log_reader", node);
157
158 absl::btree_set<std::string_view> remote_nodes;
159
160 for (const Channel *channel : *original_configuration()->channels()) {
161 if (!configuration::ChannelIsSendableOnNode(channel, node)) {
162 continue;
163 }
164 if (!channel->has_destination_nodes()) {
165 continue;
166 }
167 for (const Connection *connection : *channel->destination_nodes()) {
168 if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(connection,
169 node)) {
170 // Start by seeing if the split timestamp channels are being used for
171 // this message.
172 const Channel *timestamp_channel = configuration::GetChannel(
173 original_configuration(),
174 finder.SplitChannelName(channel, connection),
175 RemoteMessage::GetFullyQualifiedName(), "", node, true);
176
177 if (timestamp_channel != nullptr) {
178 // If for some reason a timestamp channel is not NOT_LOGGED (which
179 // is unusual), then remap the channel so that the replayed channel
180 // doesn't overlap with the special separate replay we do for
181 // timestamps.
182 if (timestamp_channel->logger() != LoggerConfig::NOT_LOGGED) {
183 RemapOriginalChannel<RemoteMessage>(
184 timestamp_channel->name()->string_view(), node);
185 }
186 continue;
187 }
188
189 // Otherwise collect this one up as a node to look for a combined
190 // channel from. It is more efficient to compare nodes than channels.
191 LOG(WARNING) << "Failed to find channel "
192 << finder.SplitChannelName(channel, connection)
193 << " on node " << FlatbufferToJson(node);
194 remote_nodes.insert(connection->name()->string_view());
195 }
196 }
197 }
198
199 std::vector<const Node *> timestamp_logger_nodes =
200 configuration::TimestampNodes(original_configuration(), node);
201 for (const std::string_view remote_node : remote_nodes) {
202 const std::string channel = finder.CombinedChannelName(remote_node);
203
204 // See if the log file is an old log with logger::MessageHeader channels
205 // in it, or a newer log with RemoteMessage. If we find an older log,
206 // rename the type too along with the name.
207 if (HasChannel<logger::MessageHeader>(channel, node)) {
208 CHECK(!HasChannel<RemoteMessage>(channel, node))
209 << ": Can't have both a logger::MessageHeader and RemoteMessage "
210 "remote "
211 "timestamp channel.";
212 // In theory, we should check NOT_LOGGED like RemoteMessage and be more
213 // careful about updating the config, but there are fewer and fewer logs
214 // with logger::MessageHeader remote messages, so it isn't worth the
215 // effort.
216 RemapOriginalChannel<logger::MessageHeader>(
217 channel, node, "/original", "aos.message_bridge.RemoteMessage");
218 } else {
219 CHECK(HasChannel<RemoteMessage>(channel, node))
220 << ": Failed to find {\"name\": \"" << channel << "\", \"type\": \""
221 << RemoteMessage::GetFullyQualifiedName() << "\"} for node "
222 << node->name()->string_view();
223 // Only bother to remap if there's something on the channel. We can
224 // tell if the channel was marked NOT_LOGGED or not. This makes the
225 // config not change un-necesarily when we replay a log with NOT_LOGGED
226 // messages.
227 if (HasOriginalChannel<RemoteMessage>(channel, node)) {
228 RemapOriginalChannel<RemoteMessage>(channel, node);
229 }
230 }
231 }
232 }
233 if (replay_configuration_) {
234 CHECK_EQ(configuration::MultiNode(remapped_configuration()),
235 configuration::MultiNode(replay_configuration_))
236 << ": Log file and replay config need to both be multi or single "
237 "node.";
238 }
239}
240
241ConfigRemapper::~ConfigRemapper() {
242 // Zero out some buffers. It's easy to do use-after-frees on these, so make
243 // it more obvious.
244 if (remapped_configuration_buffer_) {
245 remapped_configuration_buffer_->Wipe();
246 }
247}
248
249const Configuration *ConfigRemapper::original_configuration() const {
250 return original_configuration_;
251}
252
253const Configuration *ConfigRemapper::remapped_configuration() const {
254 return remapped_configuration_;
255}
256
257void ConfigRemapper::set_configuration(const Configuration *configuration) {
258 remapped_configuration_ = configuration;
259}
260
261std::vector<const Channel *> ConfigRemapper::RemappedChannels() const {
262 std::vector<const Channel *> result;
263 result.reserve(remapped_channels_.size());
264 for (auto &pair : remapped_channels_) {
265 const Channel *const original_channel =
Austin Schuh6bdcc372024-06-27 14:49:11 -0700266 original_configuration()->channels()->Get(pair.first);
267 CHECK(original_channel != nullptr);
Eric Schmiedeberge279b532023-04-19 16:36:02 -0600268
269 auto channel_iterator = std::lower_bound(
270 remapped_configuration_->channels()->cbegin(),
271 remapped_configuration_->channels()->cend(),
272 std::make_pair(std::string_view(pair.second.remapped_name),
273 original_channel->type()->string_view()),
274 CompareChannels);
275
276 CHECK(channel_iterator != remapped_configuration_->channels()->cend());
277 CHECK(EqualsChannels(
278 *channel_iterator,
279 std::make_pair(std::string_view(pair.second.remapped_name),
280 original_channel->type()->string_view())));
281 result.push_back(*channel_iterator);
282 }
283 return result;
284}
285
286const Channel *ConfigRemapper::RemapChannel(const EventLoop *event_loop,
287 const Node *node,
288 const Channel *channel) {
289 std::string_view channel_name = channel->name()->string_view();
290 std::string_view channel_type = channel->type()->string_view();
291 const int channel_index =
292 configuration::ChannelIndex(original_configuration(), channel);
293 // If the channel is remapped, find the correct channel name to use.
294 if (remapped_channels_.count(channel_index) > 0) {
295 VLOG(3) << "Got remapped channel on "
296 << configuration::CleanedChannelToString(channel);
297 channel_name = remapped_channels_[channel_index].remapped_name;
298 }
299
300 VLOG(2) << "Going to remap channel " << channel_name << " " << channel_type;
301 const Channel *remapped_channel = configuration::GetChannel(
302 remapped_configuration(), channel_name, channel_type,
303 event_loop ? event_loop->name() : "log_reader", node);
304
305 CHECK(remapped_channel != nullptr)
306 << ": Unable to send {\"name\": \"" << channel_name << "\", \"type\": \""
307 << channel_type << "\"} because it is not in the provided configuration.";
308
309 return remapped_channel;
310}
311
312void ConfigRemapper::RemapOriginalChannel(std::string_view name,
313 std::string_view type,
314 std::string_view add_prefix,
315 std::string_view new_type,
316 RemapConflict conflict_handling) {
317 RemapOriginalChannel(name, type, nullptr, add_prefix, new_type,
318 conflict_handling);
319}
320
321void ConfigRemapper::RemapOriginalChannel(std::string_view name,
322 std::string_view type,
323 const Node *node,
324 std::string_view add_prefix,
325 std::string_view new_type,
326 RemapConflict conflict_handling) {
327 if (node != nullptr) {
328 VLOG(1) << "Node is " << FlatbufferToJson(node);
329 }
330 if (replay_channels_ != nullptr) {
331 CHECK(std::find(replay_channels_->begin(), replay_channels_->end(),
332 std::make_pair(std::string{name}, std::string{type})) !=
333 replay_channels_->end())
334 << "Attempted to remap channel " << name << " " << type
335 << " which is not included in the replay channels passed to "
336 "ConfigRemapper.";
337 }
338 const Channel *remapped_channel =
339 configuration::GetChannel(original_configuration(), name, type, "", node);
340 CHECK(remapped_channel != nullptr) << ": Failed to find {\"name\": \"" << name
341 << "\", \"type\": \"" << type << "\"}";
342 VLOG(1) << "Original {\"name\": \"" << name << "\", \"type\": \"" << type
343 << "\"}";
344 VLOG(1) << "Remapped "
345 << configuration::StrippedChannelToString(remapped_channel);
346
347 // We want to make /spray on node 0 go to /0/spray by snooping the maps. And
348 // we want it to degrade if the heuristics fail to just work.
349 //
350 // The easiest way to do this is going to be incredibly specific and verbose.
351 // Look up /spray, to /0/spray. Then, prefix the result with /original to get
352 // /original/0/spray. Then, create a map from /original/spray to
353 // /original/0/spray for just the type we were asked for.
354 if (name != remapped_channel->name()->string_view()) {
355 MapT new_map;
356 new_map.match = std::make_unique<ChannelT>();
357 new_map.match->name = absl::StrCat(add_prefix, name);
358 new_map.match->type = type;
359 if (node != nullptr) {
360 new_map.match->source_node = node->name()->str();
361 }
362 new_map.rename = std::make_unique<ChannelT>();
363 new_map.rename->name =
364 absl::StrCat(add_prefix, remapped_channel->name()->string_view());
365 maps_.emplace_back(std::move(new_map));
366 }
367
368 // Then remap the original channel to the prefixed channel.
369 const size_t channel_index =
370 configuration::ChannelIndex(original_configuration(), remapped_channel);
371 CHECK_EQ(0u, remapped_channels_.count(channel_index))
372 << "Already remapped channel "
373 << configuration::CleanedChannelToString(remapped_channel);
374
375 RemappedChannel remapped_channel_struct;
376 remapped_channel_struct.remapped_name =
377 std::string(add_prefix) +
378 std::string(remapped_channel->name()->string_view());
379 remapped_channel_struct.new_type = new_type;
380 const std::string_view remapped_type = new_type.empty() ? type : new_type;
381 CheckAndHandleRemapConflict(
382 remapped_channel_struct.remapped_name, remapped_type,
383 remapped_configuration_, conflict_handling,
384 [this, &remapped_channel_struct, remapped_type, node, add_prefix,
385 conflict_handling]() {
386 RemapOriginalChannel(remapped_channel_struct.remapped_name,
387 remapped_type, node, add_prefix, "",
388 conflict_handling);
389 });
390 remapped_channels_[channel_index] = std::move(remapped_channel_struct);
391 MakeRemappedConfig();
392}
393
394void ConfigRemapper::RenameOriginalChannel(const std::string_view name,
395 const std::string_view type,
396 const std::string_view new_name,
397 const std::vector<MapT> &add_maps) {
398 RenameOriginalChannel(name, type, nullptr, new_name, add_maps);
399}
400
401void ConfigRemapper::RenameOriginalChannel(const std::string_view name,
402 const std::string_view type,
403 const Node *const node,
404 const std::string_view new_name,
405 const std::vector<MapT> &add_maps) {
406 if (node != nullptr) {
407 VLOG(1) << "Node is " << FlatbufferToJson(node);
408 }
409 // First find the channel and rename it.
410 const Channel *remapped_channel =
411 configuration::GetChannel(original_configuration(), name, type, "", node);
412 CHECK(remapped_channel != nullptr) << ": Failed to find {\"name\": \"" << name
413 << "\", \"type\": \"" << type << "\"}";
414 VLOG(1) << "Original {\"name\": \"" << name << "\", \"type\": \"" << type
415 << "\"}";
416 VLOG(1) << "Remapped "
417 << configuration::StrippedChannelToString(remapped_channel);
418
419 const size_t channel_index =
420 configuration::ChannelIndex(original_configuration(), remapped_channel);
421 CHECK_EQ(0u, remapped_channels_.count(channel_index))
422 << "Already remapped channel "
423 << configuration::CleanedChannelToString(remapped_channel);
424
425 RemappedChannel remapped_channel_struct;
426 remapped_channel_struct.remapped_name = new_name;
427 remapped_channel_struct.new_type.clear();
428 remapped_channels_[channel_index] = std::move(remapped_channel_struct);
429
430 // Then add any provided maps.
431 for (const MapT &map : add_maps) {
432 maps_.push_back(map);
433 }
434
435 // Finally rewrite the config.
436 MakeRemappedConfig();
437}
438
439void ConfigRemapper::MakeRemappedConfig() {
440 // If no remapping occurred and we are using the original config, then there
441 // is nothing interesting to do here.
442 if (remapped_channels_.empty() && replay_configuration_ == nullptr) {
443 remapped_configuration_ = original_configuration();
444 return;
445 }
446 // Config to copy Channel definitions from. Use the specified
447 // replay_configuration_ if it has been provided.
448 const Configuration *const base_config = replay_configuration_ == nullptr
449 ? original_configuration()
450 : replay_configuration_;
451
452 // Create a config with all the channels, but un-sorted/merged. Collect up
453 // the schemas while we do this. Call MergeConfiguration to sort everything,
454 // and then merge it all in together.
455
456 // This is the builder that we use for the config containing all the new
457 // channels.
458 flatbuffers::FlatBufferBuilder fbb;
459 fbb.ForceDefaults(true);
460 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
461
462 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
463 << ": Merging logic needs to be updated when the number of channel "
464 "fields changes.";
465
466 // List of schemas.
467 std::map<std::string_view, FlatbufferVector<reflection::Schema>> schema_map;
468 // Make sure our new RemoteMessage schema is in there for old logs without it.
469 schema_map.insert(std::make_pair(
470 message_bridge::RemoteMessage::GetFullyQualifiedName(),
471 FlatbufferVector<reflection::Schema>(FlatbufferSpan<reflection::Schema>(
472 message_bridge::RemoteMessageSchema()))));
473
474 // Reconstruct the remapped channels.
475 for (auto &pair : remapped_channels_) {
Austin Schuh6bdcc372024-06-27 14:49:11 -0700476 const Channel *const c = configuration::GetChannel(
Eric Schmiedeberge279b532023-04-19 16:36:02 -0600477 base_config, original_configuration()->channels()->Get(pair.first), "",
Austin Schuh6bdcc372024-06-27 14:49:11 -0700478 nullptr);
479 CHECK(c != nullptr);
Eric Schmiedeberge279b532023-04-19 16:36:02 -0600480 channel_offsets.emplace_back(
481 CopyChannel(c, pair.second.remapped_name, "", &fbb));
482
483 if (c->has_destination_nodes()) {
484 for (const Connection *connection : *c->destination_nodes()) {
485 switch (connection->timestamp_logger()) {
486 case LoggerConfig::LOCAL_LOGGER:
487 case LoggerConfig::NOT_LOGGED:
488 // There is no timestamp channel associated with this, so ignore it.
489 break;
490
491 case LoggerConfig::REMOTE_LOGGER:
492 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
493 // We want to make a split timestamp channel regardless of what type
494 // of log this used to be. No sense propagating the single
495 // timestamp channel.
496
497 CHECK(connection->has_timestamp_logger_nodes());
498 for (const flatbuffers::String *timestamp_logger_node :
499 *connection->timestamp_logger_nodes()) {
500 const Node *node =
501 configuration::GetNode(original_configuration(),
502 timestamp_logger_node->string_view());
503 message_bridge::ChannelTimestampFinder finder(
504 original_configuration(), "log_reader", node);
505
506 // We are assuming here that all the maps are setup correctly to
507 // handle arbitrary timestamps. Apply the maps for this node to
508 // see what name this ends up with.
509 std::string name = finder.SplitChannelName(
510 pair.second.remapped_name, c->type()->str(), connection);
511 std::string unmapped_name = name;
512 configuration::HandleMaps(original_configuration()->maps(), &name,
513 "aos.message_bridge.RemoteMessage",
514 node);
515 CHECK_NE(name, unmapped_name)
516 << ": Remote timestamp channel was not remapped, this is "
517 "very fishy";
518 flatbuffers::Offset<flatbuffers::String> channel_name_offset =
519 fbb.CreateString(name);
520 flatbuffers::Offset<flatbuffers::String> channel_type_offset =
521 fbb.CreateString("aos.message_bridge.RemoteMessage");
522 flatbuffers::Offset<flatbuffers::String> source_node_offset =
523 fbb.CreateString(timestamp_logger_node->string_view());
524
525 // Now, build a channel. Don't log it, 2 senders, and match the
526 // source frequency.
527 Channel::Builder channel_builder(fbb);
528 channel_builder.add_name(channel_name_offset);
529 channel_builder.add_type(channel_type_offset);
530 channel_builder.add_source_node(source_node_offset);
531 channel_builder.add_logger(LoggerConfig::NOT_LOGGED);
532 channel_builder.add_num_senders(2);
533 if (c->has_frequency()) {
534 channel_builder.add_frequency(c->frequency());
535 }
536 if (c->has_channel_storage_duration()) {
537 channel_builder.add_channel_storage_duration(
538 c->channel_storage_duration());
539 }
540 channel_offsets.emplace_back(channel_builder.Finish());
541 }
542 break;
543 }
544 }
545 }
546 }
547
548 // Now reconstruct the original channels, translating types as needed
549 for (const Channel *c : *base_config->channels()) {
550 // Search for a mapping channel.
551 std::string_view new_type = "";
552 for (auto &pair : remapped_channels_) {
553 const Channel *const remapped_channel =
554 original_configuration()->channels()->Get(pair.first);
555 if (remapped_channel->name()->string_view() == c->name()->string_view() &&
556 remapped_channel->type()->string_view() == c->type()->string_view()) {
557 new_type = pair.second.new_type;
558 break;
559 }
560 }
561
562 // Copy everything over.
563 channel_offsets.emplace_back(CopyChannel(c, "", new_type, &fbb));
564
565 // Add the schema if it doesn't exist.
566 if (schema_map.find(c->type()->string_view()) == schema_map.end()) {
567 CHECK(c->has_schema());
568 schema_map.insert(std::make_pair(c->type()->string_view(),
569 RecursiveCopyFlatBuffer(c->schema())));
570 }
571 }
572
573 // The MergeConfiguration API takes a vector, not a map. Convert.
574 std::vector<FlatbufferVector<reflection::Schema>> schemas;
575 while (!schema_map.empty()) {
576 schemas.emplace_back(std::move(schema_map.begin()->second));
577 schema_map.erase(schema_map.begin());
578 }
579
580 // Create the Configuration containing the new channels that we want to add.
581 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
582 channels_offset =
583 channel_offsets.empty() ? 0 : fbb.CreateVector(channel_offsets);
584
585 // Copy over the old maps.
586 std::vector<flatbuffers::Offset<Map>> map_offsets;
587 if (base_config->maps()) {
588 for (const Map *map : *base_config->maps()) {
589 map_offsets.emplace_back(RecursiveCopyFlatBuffer(map, &fbb));
590 }
591 }
592
593 // Now create the new maps. These are second so they take effect first.
594 for (const MapT &map : maps_) {
595 CHECK(!map.match->name.empty());
596 const flatbuffers::Offset<flatbuffers::String> match_name_offset =
597 fbb.CreateString(map.match->name);
598 flatbuffers::Offset<flatbuffers::String> match_type_offset;
599 if (!map.match->type.empty()) {
600 match_type_offset = fbb.CreateString(map.match->type);
601 }
602 flatbuffers::Offset<flatbuffers::String> match_source_node_offset;
603 if (!map.match->source_node.empty()) {
604 match_source_node_offset = fbb.CreateString(map.match->source_node);
605 }
606 CHECK(!map.rename->name.empty());
607 const flatbuffers::Offset<flatbuffers::String> rename_name_offset =
608 fbb.CreateString(map.rename->name);
609 Channel::Builder match_builder(fbb);
610 match_builder.add_name(match_name_offset);
611 if (!match_type_offset.IsNull()) {
612 match_builder.add_type(match_type_offset);
613 }
614 if (!match_source_node_offset.IsNull()) {
615 match_builder.add_source_node(match_source_node_offset);
616 }
617 const flatbuffers::Offset<Channel> match_offset = match_builder.Finish();
618
619 Channel::Builder rename_builder(fbb);
620 rename_builder.add_name(rename_name_offset);
621 const flatbuffers::Offset<Channel> rename_offset = rename_builder.Finish();
622
623 Map::Builder map_builder(fbb);
624 map_builder.add_match(match_offset);
625 map_builder.add_rename(rename_offset);
626 map_offsets.emplace_back(map_builder.Finish());
627 }
628
629 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
630 maps_offsets = map_offsets.empty() ? 0 : fbb.CreateVector(map_offsets);
631
632 // And copy everything else over.
633 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
634 nodes_offset = RecursiveCopyVectorTable(base_config->nodes(), &fbb);
635
636 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
637 applications_offset =
638 RecursiveCopyVectorTable(base_config->applications(), &fbb);
639
640 // Now insert everything else in unmodified.
641 ConfigurationBuilder configuration_builder(fbb);
642 if (!channels_offset.IsNull()) {
643 configuration_builder.add_channels(channels_offset);
644 }
645 if (!maps_offsets.IsNull()) {
646 configuration_builder.add_maps(maps_offsets);
647 }
648 if (!nodes_offset.IsNull()) {
649 configuration_builder.add_nodes(nodes_offset);
650 }
651 if (!applications_offset.IsNull()) {
652 configuration_builder.add_applications(applications_offset);
653 }
654
655 if (base_config->has_channel_storage_duration()) {
656 configuration_builder.add_channel_storage_duration(
657 base_config->channel_storage_duration());
658 }
659
660 CHECK_EQ(Configuration::MiniReflectTypeTable()->num_elems, 6u)
661 << ": Merging logic needs to be updated when the number of configuration "
662 "fields changes.";
663
664 fbb.Finish(configuration_builder.Finish());
665
666 // Clean it up and return it! By using MergeConfiguration here, we'll
667 // actually get a deduplicated config for free too.
668 FlatbufferDetachedBuffer<Configuration> new_merged_config =
669 configuration::MergeConfiguration(
670 FlatbufferDetachedBuffer<Configuration>(fbb.Release()));
671
672 remapped_configuration_buffer_ =
673 std::make_unique<FlatbufferDetachedBuffer<Configuration>>(
674 configuration::MergeConfiguration(new_merged_config, schemas));
675
676 remapped_configuration_ = &remapped_configuration_buffer_->message();
677
678 // TODO(austin): Lazily re-build to save CPU?
679}
680
681} // namespace aos