Add utility function for appending channel to a config

This is helpful for certain log-replay tasks.

Change-Id: I46da77a0481ab1f7d61f7e9eb3ce8fe553e6c7ae
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/configuration.cc b/aos/configuration.cc
index ad6fb54..8b2e1b8 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -1618,5 +1618,31 @@
   return RecursiveCopyFlatBuffer(found_schema);
 }
 
+aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
+    const Configuration *config, std::string_view name,
+    aos::FlatbufferVector<reflection::Schema> schema, const aos::Node *node,
+    ChannelT overrides) {
+  overrides.name = name;
+  overrides.type = schema.message().root_table()->name()->string_view();
+  if (node != nullptr) {
+    CHECK(node->has_name());
+    overrides.source_node = node->name()->string_view();
+  }
+  flatbuffers::FlatBufferBuilder fbb;
+  // Don't populate fields from overrides that the user doesn't explicitly
+  // override.
+  fbb.ForceDefaults(false);
+  const flatbuffers::Offset<Channel> channel_offset =
+      Channel::Pack(fbb, &overrides);
+  const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Channel>>>
+      channels_offset = fbb.CreateVector({channel_offset});
+  Configuration::Builder config_builder(fbb);
+  config_builder.add_channels(channels_offset);
+  fbb.Finish(config_builder.Finish());
+  FlatbufferDetachedBuffer<Configuration> new_channel_config = fbb.Release();
+  new_channel_config = MergeConfiguration(new_channel_config, {schema});
+  return MergeWithConfig(config, new_channel_config);
+}
+
 }  // namespace configuration
 }  // namespace aos
diff --git a/aos/configuration.h b/aos/configuration.h
index 4d84b23..0e5ef74 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -227,6 +227,15 @@
 GetSchemaDetachedBuffer(const Configuration *config,
                         std::string_view schema_type);
 
+// Adds the specified channel to the config and returns the new, merged, config.
+// The channel name is derived from the specified name, the type and schema from
+// the provided schema, the source node from the specified node, and all other
+// fields (e.g., frequency) will be derived from the overrides parameter.
+aos::FlatbufferDetachedBuffer<Configuration> AddChannelToConfiguration(
+    const Configuration *config, std::string_view name,
+    aos::FlatbufferVector<reflection::Schema> schema,
+    const aos::Node *source_node = nullptr, ChannelT overrides = {});
+
 }  // namespace configuration
 
 // Compare and equality operators for Channel.  Note: these only check the name
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index fc45d88..823f43c 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -1049,6 +1049,60 @@
             std::nullopt);
 }
 
+// Tests that we can use a utility to add individual channels to a single-node
+// config.
+TEST_F(ConfigurationTest, AddChannelToConfigSingleNode) {
+  FlatbufferDetachedBuffer<Configuration> base_config =
+      ReadConfig(ArtifactPath("aos/testdata/config1.json"));
+
+  FlatbufferVector<reflection::Schema> schema =
+      FileToFlatbuffer<reflection::Schema>(
+          ArtifactPath("aos/events/ping.bfbs"));
+
+  FlatbufferDetachedBuffer<Configuration> new_config =
+      AddChannelToConfiguration(&base_config.message(), "/new", schema);
+
+  ASSERT_EQ(new_config.message().channels()->size(),
+            base_config.message().channels()->size() + 1);
+
+  const Channel *channel =
+      GetChannel(new_config, "/new", "aos.examples.Ping", "", nullptr);
+  ASSERT_TRUE(channel != nullptr);
+  ASSERT_TRUE(channel->has_schema());
+  // Check that we don't populate channel settings that we don't override the
+  // defaults of.
+  ASSERT_FALSE(channel->has_frequency());
+}
+
+// Tests that we can use a utility to add individual channels to a multi-node
+// config.
+TEST_F(ConfigurationTest, AddChannelToConfigMultiNode) {
+  FlatbufferDetachedBuffer<Configuration> base_config =
+      ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
+
+  FlatbufferVector<reflection::Schema> schema =
+      FileToFlatbuffer<reflection::Schema>(
+          ArtifactPath("aos/events/ping.bfbs"));
+
+  aos::ChannelT channel_overrides;
+  channel_overrides.frequency = 971;
+  FlatbufferDetachedBuffer<Configuration> new_config =
+      AddChannelToConfiguration(&base_config.message(), "/new", schema,
+                                GetNode(&base_config.message(), "pi1"),
+                                channel_overrides);
+
+  ASSERT_EQ(new_config.message().channels()->size(),
+            base_config.message().channels()->size() + 1);
+
+  const Channel *channel =
+      GetChannel(new_config, "/new", "aos.examples.Ping", "", nullptr);
+  ASSERT_TRUE(channel != nullptr);
+  ASSERT_TRUE(channel->has_schema());
+  ASSERT_TRUE(channel->has_source_node());
+  ASSERT_EQ("pi1", channel->source_node()->string_view());
+  ASSERT_EQ(971, channel->frequency());
+}
+
 }  // namespace testing
 }  // namespace configuration
 }  // namespace aos
diff --git a/aos/events/logging/log_replayer.cc b/aos/events/logging/log_replayer.cc
index 4de4052..0f7444a 100644
--- a/aos/events/logging/log_replayer.cc
+++ b/aos/events/logging/log_replayer.cc
@@ -70,25 +70,14 @@
     const aos::Configuration *raw_config = FLAGS_config.empty()
                                                ? config_reader.configuration()
                                                : &config.message();
-    const std::string channel_node =
-        aos::configuration::MultiNode(raw_config)
-            ? absl::StrFormat("\"source_node\": \"%s%s",
-                              aos::configuration::GetMyNode(raw_config)
-                                  ->name()
-                                  ->string_view(),
-                              "\",")
-            : "";
-    config = aos::configuration::MergeWithConfig(
-        raw_config,
-        aos::configuration::AddSchema(
-            absl::StrFormat(
-                "{ \"channels\": [{ \"name\": \"/timing\", \"type\": "
-                "\"aos.timing.ReplayTiming\", \"max_size\": 10000, "
-                "\"frequency\": 10000, %s %s",
-                channel_node, "\"num_senders\": 2 }]}"),
-            {aos::FlatbufferVector<reflection::Schema>(
-                aos::FlatbufferSpan<reflection::Schema>(
-                    aos::timing::ReplayTimingSchema()))}));
+    aos::ChannelT channel_overrides;
+    channel_overrides.max_size = 10000;
+    channel_overrides.frequency = 10000;
+    config = aos::configuration::AddChannelToConfiguration(
+        raw_config, "/timing",
+        aos::FlatbufferSpan<reflection::Schema>(
+            aos::timing::ReplayTimingSchema()),
+        aos::configuration::GetMyNode(raw_config), channel_overrides);
   }
 
   if (!FLAGS_merge_with_config.empty()) {