blob: 21f45d52e8f26b31fda771a471710674679acbce [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 =
266 CHECK_NOTNULL(original_configuration()->channels()->Get(pair.first));
267
268 auto channel_iterator = std::lower_bound(
269 remapped_configuration_->channels()->cbegin(),
270 remapped_configuration_->channels()->cend(),
271 std::make_pair(std::string_view(pair.second.remapped_name),
272 original_channel->type()->string_view()),
273 CompareChannels);
274
275 CHECK(channel_iterator != remapped_configuration_->channels()->cend());
276 CHECK(EqualsChannels(
277 *channel_iterator,
278 std::make_pair(std::string_view(pair.second.remapped_name),
279 original_channel->type()->string_view())));
280 result.push_back(*channel_iterator);
281 }
282 return result;
283}
284
285const Channel *ConfigRemapper::RemapChannel(const EventLoop *event_loop,
286 const Node *node,
287 const Channel *channel) {
288 std::string_view channel_name = channel->name()->string_view();
289 std::string_view channel_type = channel->type()->string_view();
290 const int channel_index =
291 configuration::ChannelIndex(original_configuration(), channel);
292 // If the channel is remapped, find the correct channel name to use.
293 if (remapped_channels_.count(channel_index) > 0) {
294 VLOG(3) << "Got remapped channel on "
295 << configuration::CleanedChannelToString(channel);
296 channel_name = remapped_channels_[channel_index].remapped_name;
297 }
298
299 VLOG(2) << "Going to remap channel " << channel_name << " " << channel_type;
300 const Channel *remapped_channel = configuration::GetChannel(
301 remapped_configuration(), channel_name, channel_type,
302 event_loop ? event_loop->name() : "log_reader", node);
303
304 CHECK(remapped_channel != nullptr)
305 << ": Unable to send {\"name\": \"" << channel_name << "\", \"type\": \""
306 << channel_type << "\"} because it is not in the provided configuration.";
307
308 return remapped_channel;
309}
310
311void ConfigRemapper::RemapOriginalChannel(std::string_view name,
312 std::string_view type,
313 std::string_view add_prefix,
314 std::string_view new_type,
315 RemapConflict conflict_handling) {
316 RemapOriginalChannel(name, type, nullptr, add_prefix, new_type,
317 conflict_handling);
318}
319
320void ConfigRemapper::RemapOriginalChannel(std::string_view name,
321 std::string_view type,
322 const Node *node,
323 std::string_view add_prefix,
324 std::string_view new_type,
325 RemapConflict conflict_handling) {
326 if (node != nullptr) {
327 VLOG(1) << "Node is " << FlatbufferToJson(node);
328 }
329 if (replay_channels_ != nullptr) {
330 CHECK(std::find(replay_channels_->begin(), replay_channels_->end(),
331 std::make_pair(std::string{name}, std::string{type})) !=
332 replay_channels_->end())
333 << "Attempted to remap channel " << name << " " << type
334 << " which is not included in the replay channels passed to "
335 "ConfigRemapper.";
336 }
337 const Channel *remapped_channel =
338 configuration::GetChannel(original_configuration(), name, type, "", node);
339 CHECK(remapped_channel != nullptr) << ": Failed to find {\"name\": \"" << name
340 << "\", \"type\": \"" << type << "\"}";
341 VLOG(1) << "Original {\"name\": \"" << name << "\", \"type\": \"" << type
342 << "\"}";
343 VLOG(1) << "Remapped "
344 << configuration::StrippedChannelToString(remapped_channel);
345
346 // We want to make /spray on node 0 go to /0/spray by snooping the maps. And
347 // we want it to degrade if the heuristics fail to just work.
348 //
349 // The easiest way to do this is going to be incredibly specific and verbose.
350 // Look up /spray, to /0/spray. Then, prefix the result with /original to get
351 // /original/0/spray. Then, create a map from /original/spray to
352 // /original/0/spray for just the type we were asked for.
353 if (name != remapped_channel->name()->string_view()) {
354 MapT new_map;
355 new_map.match = std::make_unique<ChannelT>();
356 new_map.match->name = absl::StrCat(add_prefix, name);
357 new_map.match->type = type;
358 if (node != nullptr) {
359 new_map.match->source_node = node->name()->str();
360 }
361 new_map.rename = std::make_unique<ChannelT>();
362 new_map.rename->name =
363 absl::StrCat(add_prefix, remapped_channel->name()->string_view());
364 maps_.emplace_back(std::move(new_map));
365 }
366
367 // Then remap the original channel to the prefixed channel.
368 const size_t channel_index =
369 configuration::ChannelIndex(original_configuration(), remapped_channel);
370 CHECK_EQ(0u, remapped_channels_.count(channel_index))
371 << "Already remapped channel "
372 << configuration::CleanedChannelToString(remapped_channel);
373
374 RemappedChannel remapped_channel_struct;
375 remapped_channel_struct.remapped_name =
376 std::string(add_prefix) +
377 std::string(remapped_channel->name()->string_view());
378 remapped_channel_struct.new_type = new_type;
379 const std::string_view remapped_type = new_type.empty() ? type : new_type;
380 CheckAndHandleRemapConflict(
381 remapped_channel_struct.remapped_name, remapped_type,
382 remapped_configuration_, conflict_handling,
383 [this, &remapped_channel_struct, remapped_type, node, add_prefix,
384 conflict_handling]() {
385 RemapOriginalChannel(remapped_channel_struct.remapped_name,
386 remapped_type, node, add_prefix, "",
387 conflict_handling);
388 });
389 remapped_channels_[channel_index] = std::move(remapped_channel_struct);
390 MakeRemappedConfig();
391}
392
393void ConfigRemapper::RenameOriginalChannel(const std::string_view name,
394 const std::string_view type,
395 const std::string_view new_name,
396 const std::vector<MapT> &add_maps) {
397 RenameOriginalChannel(name, type, nullptr, new_name, add_maps);
398}
399
400void ConfigRemapper::RenameOriginalChannel(const std::string_view name,
401 const std::string_view type,
402 const Node *const node,
403 const std::string_view new_name,
404 const std::vector<MapT> &add_maps) {
405 if (node != nullptr) {
406 VLOG(1) << "Node is " << FlatbufferToJson(node);
407 }
408 // First find the channel and rename it.
409 const Channel *remapped_channel =
410 configuration::GetChannel(original_configuration(), name, type, "", node);
411 CHECK(remapped_channel != nullptr) << ": Failed to find {\"name\": \"" << name
412 << "\", \"type\": \"" << type << "\"}";
413 VLOG(1) << "Original {\"name\": \"" << name << "\", \"type\": \"" << type
414 << "\"}";
415 VLOG(1) << "Remapped "
416 << configuration::StrippedChannelToString(remapped_channel);
417
418 const size_t channel_index =
419 configuration::ChannelIndex(original_configuration(), remapped_channel);
420 CHECK_EQ(0u, remapped_channels_.count(channel_index))
421 << "Already remapped channel "
422 << configuration::CleanedChannelToString(remapped_channel);
423
424 RemappedChannel remapped_channel_struct;
425 remapped_channel_struct.remapped_name = new_name;
426 remapped_channel_struct.new_type.clear();
427 remapped_channels_[channel_index] = std::move(remapped_channel_struct);
428
429 // Then add any provided maps.
430 for (const MapT &map : add_maps) {
431 maps_.push_back(map);
432 }
433
434 // Finally rewrite the config.
435 MakeRemappedConfig();
436}
437
438void ConfigRemapper::MakeRemappedConfig() {
439 // If no remapping occurred and we are using the original config, then there
440 // is nothing interesting to do here.
441 if (remapped_channels_.empty() && replay_configuration_ == nullptr) {
442 remapped_configuration_ = original_configuration();
443 return;
444 }
445 // Config to copy Channel definitions from. Use the specified
446 // replay_configuration_ if it has been provided.
447 const Configuration *const base_config = replay_configuration_ == nullptr
448 ? original_configuration()
449 : replay_configuration_;
450
451 // Create a config with all the channels, but un-sorted/merged. Collect up
452 // the schemas while we do this. Call MergeConfiguration to sort everything,
453 // and then merge it all in together.
454
455 // This is the builder that we use for the config containing all the new
456 // channels.
457 flatbuffers::FlatBufferBuilder fbb;
458 fbb.ForceDefaults(true);
459 std::vector<flatbuffers::Offset<Channel>> channel_offsets;
460
461 CHECK_EQ(Channel::MiniReflectTypeTable()->num_elems, 14u)
462 << ": Merging logic needs to be updated when the number of channel "
463 "fields changes.";
464
465 // List of schemas.
466 std::map<std::string_view, FlatbufferVector<reflection::Schema>> schema_map;
467 // Make sure our new RemoteMessage schema is in there for old logs without it.
468 schema_map.insert(std::make_pair(
469 message_bridge::RemoteMessage::GetFullyQualifiedName(),
470 FlatbufferVector<reflection::Schema>(FlatbufferSpan<reflection::Schema>(
471 message_bridge::RemoteMessageSchema()))));
472
473 // Reconstruct the remapped channels.
474 for (auto &pair : remapped_channels_) {
475 const Channel *const c = CHECK_NOTNULL(configuration::GetChannel(
476 base_config, original_configuration()->channels()->Get(pair.first), "",
477 nullptr));
478 channel_offsets.emplace_back(
479 CopyChannel(c, pair.second.remapped_name, "", &fbb));
480
481 if (c->has_destination_nodes()) {
482 for (const Connection *connection : *c->destination_nodes()) {
483 switch (connection->timestamp_logger()) {
484 case LoggerConfig::LOCAL_LOGGER:
485 case LoggerConfig::NOT_LOGGED:
486 // There is no timestamp channel associated with this, so ignore it.
487 break;
488
489 case LoggerConfig::REMOTE_LOGGER:
490 case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
491 // We want to make a split timestamp channel regardless of what type
492 // of log this used to be. No sense propagating the single
493 // timestamp channel.
494
495 CHECK(connection->has_timestamp_logger_nodes());
496 for (const flatbuffers::String *timestamp_logger_node :
497 *connection->timestamp_logger_nodes()) {
498 const Node *node =
499 configuration::GetNode(original_configuration(),
500 timestamp_logger_node->string_view());
501 message_bridge::ChannelTimestampFinder finder(
502 original_configuration(), "log_reader", node);
503
504 // We are assuming here that all the maps are setup correctly to
505 // handle arbitrary timestamps. Apply the maps for this node to
506 // see what name this ends up with.
507 std::string name = finder.SplitChannelName(
508 pair.second.remapped_name, c->type()->str(), connection);
509 std::string unmapped_name = name;
510 configuration::HandleMaps(original_configuration()->maps(), &name,
511 "aos.message_bridge.RemoteMessage",
512 node);
513 CHECK_NE(name, unmapped_name)
514 << ": Remote timestamp channel was not remapped, this is "
515 "very fishy";
516 flatbuffers::Offset<flatbuffers::String> channel_name_offset =
517 fbb.CreateString(name);
518 flatbuffers::Offset<flatbuffers::String> channel_type_offset =
519 fbb.CreateString("aos.message_bridge.RemoteMessage");
520 flatbuffers::Offset<flatbuffers::String> source_node_offset =
521 fbb.CreateString(timestamp_logger_node->string_view());
522
523 // Now, build a channel. Don't log it, 2 senders, and match the
524 // source frequency.
525 Channel::Builder channel_builder(fbb);
526 channel_builder.add_name(channel_name_offset);
527 channel_builder.add_type(channel_type_offset);
528 channel_builder.add_source_node(source_node_offset);
529 channel_builder.add_logger(LoggerConfig::NOT_LOGGED);
530 channel_builder.add_num_senders(2);
531 if (c->has_frequency()) {
532 channel_builder.add_frequency(c->frequency());
533 }
534 if (c->has_channel_storage_duration()) {
535 channel_builder.add_channel_storage_duration(
536 c->channel_storage_duration());
537 }
538 channel_offsets.emplace_back(channel_builder.Finish());
539 }
540 break;
541 }
542 }
543 }
544 }
545
546 // Now reconstruct the original channels, translating types as needed
547 for (const Channel *c : *base_config->channels()) {
548 // Search for a mapping channel.
549 std::string_view new_type = "";
550 for (auto &pair : remapped_channels_) {
551 const Channel *const remapped_channel =
552 original_configuration()->channels()->Get(pair.first);
553 if (remapped_channel->name()->string_view() == c->name()->string_view() &&
554 remapped_channel->type()->string_view() == c->type()->string_view()) {
555 new_type = pair.second.new_type;
556 break;
557 }
558 }
559
560 // Copy everything over.
561 channel_offsets.emplace_back(CopyChannel(c, "", new_type, &fbb));
562
563 // Add the schema if it doesn't exist.
564 if (schema_map.find(c->type()->string_view()) == schema_map.end()) {
565 CHECK(c->has_schema());
566 schema_map.insert(std::make_pair(c->type()->string_view(),
567 RecursiveCopyFlatBuffer(c->schema())));
568 }
569 }
570
571 // The MergeConfiguration API takes a vector, not a map. Convert.
572 std::vector<FlatbufferVector<reflection::Schema>> schemas;
573 while (!schema_map.empty()) {
574 schemas.emplace_back(std::move(schema_map.begin()->second));
575 schema_map.erase(schema_map.begin());
576 }
577
578 // Create the Configuration containing the new channels that we want to add.
579 const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
580 channels_offset =
581 channel_offsets.empty() ? 0 : fbb.CreateVector(channel_offsets);
582
583 // Copy over the old maps.
584 std::vector<flatbuffers::Offset<Map>> map_offsets;
585 if (base_config->maps()) {
586 for (const Map *map : *base_config->maps()) {
587 map_offsets.emplace_back(RecursiveCopyFlatBuffer(map, &fbb));
588 }
589 }
590
591 // Now create the new maps. These are second so they take effect first.
592 for (const MapT &map : maps_) {
593 CHECK(!map.match->name.empty());
594 const flatbuffers::Offset<flatbuffers::String> match_name_offset =
595 fbb.CreateString(map.match->name);
596 flatbuffers::Offset<flatbuffers::String> match_type_offset;
597 if (!map.match->type.empty()) {
598 match_type_offset = fbb.CreateString(map.match->type);
599 }
600 flatbuffers::Offset<flatbuffers::String> match_source_node_offset;
601 if (!map.match->source_node.empty()) {
602 match_source_node_offset = fbb.CreateString(map.match->source_node);
603 }
604 CHECK(!map.rename->name.empty());
605 const flatbuffers::Offset<flatbuffers::String> rename_name_offset =
606 fbb.CreateString(map.rename->name);
607 Channel::Builder match_builder(fbb);
608 match_builder.add_name(match_name_offset);
609 if (!match_type_offset.IsNull()) {
610 match_builder.add_type(match_type_offset);
611 }
612 if (!match_source_node_offset.IsNull()) {
613 match_builder.add_source_node(match_source_node_offset);
614 }
615 const flatbuffers::Offset<Channel> match_offset = match_builder.Finish();
616
617 Channel::Builder rename_builder(fbb);
618 rename_builder.add_name(rename_name_offset);
619 const flatbuffers::Offset<Channel> rename_offset = rename_builder.Finish();
620
621 Map::Builder map_builder(fbb);
622 map_builder.add_match(match_offset);
623 map_builder.add_rename(rename_offset);
624 map_offsets.emplace_back(map_builder.Finish());
625 }
626
627 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Map>>>
628 maps_offsets = map_offsets.empty() ? 0 : fbb.CreateVector(map_offsets);
629
630 // And copy everything else over.
631 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Node>>>
632 nodes_offset = RecursiveCopyVectorTable(base_config->nodes(), &fbb);
633
634 flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Application>>>
635 applications_offset =
636 RecursiveCopyVectorTable(base_config->applications(), &fbb);
637
638 // Now insert everything else in unmodified.
639 ConfigurationBuilder configuration_builder(fbb);
640 if (!channels_offset.IsNull()) {
641 configuration_builder.add_channels(channels_offset);
642 }
643 if (!maps_offsets.IsNull()) {
644 configuration_builder.add_maps(maps_offsets);
645 }
646 if (!nodes_offset.IsNull()) {
647 configuration_builder.add_nodes(nodes_offset);
648 }
649 if (!applications_offset.IsNull()) {
650 configuration_builder.add_applications(applications_offset);
651 }
652
653 if (base_config->has_channel_storage_duration()) {
654 configuration_builder.add_channel_storage_duration(
655 base_config->channel_storage_duration());
656 }
657
658 CHECK_EQ(Configuration::MiniReflectTypeTable()->num_elems, 6u)
659 << ": Merging logic needs to be updated when the number of configuration "
660 "fields changes.";
661
662 fbb.Finish(configuration_builder.Finish());
663
664 // Clean it up and return it! By using MergeConfiguration here, we'll
665 // actually get a deduplicated config for free too.
666 FlatbufferDetachedBuffer<Configuration> new_merged_config =
667 configuration::MergeConfiguration(
668 FlatbufferDetachedBuffer<Configuration>(fbb.Release()));
669
670 remapped_configuration_buffer_ =
671 std::make_unique<FlatbufferDetachedBuffer<Configuration>>(
672 configuration::MergeConfiguration(new_merged_config, schemas));
673
674 remapped_configuration_ = &remapped_configuration_buffer_->message();
675
676 // TODO(austin): Lazily re-build to save CPU?
677}
678
679} // namespace aos