Add reverse name lookup for channels

This makes it so that if you know the fully-resolved channel name you
can figure out all the possible ways that it can be referred to. This is
potentially useful for a variety of things, including:
* Config validation (e.g. identifying all the channels that exist
  on the /aos/remote_timestamp/* namespace, to determine if there are
  any extraneous channels).
* Improving auto-complete for command-line utilities.
* Adding more options for how channel names are presented in
  log-analysis tools (e.g., currently foxglove doesn't support the
  concept of channel maps/aliases/whatever, so making node-agnostic
  plots is irritating).

Change-Id: I334881199ad6b300a97f96d4a427ae25e55fa15f
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/aos/configuration.cc b/aos/configuration.cc
index ed99e2a..d05cba8 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -458,6 +458,58 @@
   }
 }
 
+void HandleReverseMaps(
+    const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
+    std::string_view type, const Node *node, std::set<std::string> *names) {
+  for (const Map *map : *maps) {
+    CHECK_NOTNULL(map);
+    const Channel *const match = CHECK_NOTNULL(map->match());
+    const Channel *const rename = CHECK_NOTNULL(map->rename());
+
+    // Handle type specific maps.
+    const flatbuffers::String *const match_type_string = match->type();
+    if (match_type_string != nullptr &&
+        match_type_string->string_view() != type) {
+      continue;
+    }
+
+    // Now handle node specific maps.
+    const flatbuffers::String *const match_source_node_string =
+        match->source_node();
+    if (node != nullptr && match_source_node_string != nullptr &&
+        match_source_node_string->string_view() !=
+            node->name()->string_view()) {
+      continue;
+    }
+
+    const flatbuffers::String *const match_name_string = match->name();
+    const flatbuffers::String *const rename_name_string = rename->name();
+    if (match_name_string == nullptr || rename_name_string == nullptr) {
+      continue;
+    }
+
+    const std::string rename_name = rename_name_string->str();
+    const std::string_view match_name = match_name_string->string_view();
+
+    std::set<std::string> possible_renames;
+
+    // Check if the current name(s) could have been reached using the provided
+    // rename.
+    if (match_name.back() == '*') {
+      for (const std::string &option : *names) {
+        if (option.substr(0, rename_name.size()) == rename_name) {
+          possible_renames.insert(
+              absl::StrCat(match_name.substr(0, match_name.size() - 1),
+                           option.substr(rename_name.size())));
+        }
+      }
+      names->insert(possible_renames.begin(), possible_renames.end());
+    } else if (names->count(rename_name) != 0) {
+      names->insert(std::string(match_name));
+    }
+  }
+}
+
 }  // namespace
 
 // Maps name for the provided maps.  Modifies name.
@@ -466,6 +518,9 @@
 // pointers. These combine to make it a performance hotspot during many tests
 // under msan, so there is some optimizing around caching intermediates instead
 // of dereferencing the pointer multiple times.
+//
+// Deliberately not in an anonymous namespace so that the log-reading code can
+// reference it.
 void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
                 std::string *name, std::string_view type, const Node *node) {
   // For the same reason we merge configs in reverse order, we want to process
@@ -527,6 +582,25 @@
   }
 }
 
+std::set<std::string> GetChannelAliases(const Configuration *config,
+                                        std::string_view name,
+                                        std::string_view type,
+                                        const std::string_view application_name,
+                                        const Node *node) {
+  std::set<std::string> names{std::string(name)};
+  if (config->has_maps()) {
+    HandleReverseMaps(config->maps(), type, node, &names);
+  }
+  {
+    const Application *application =
+        GetApplication(config, node, application_name);
+    if (application != nullptr && application->has_maps()) {
+      HandleReverseMaps(application->maps(), type, node, &names);
+    }
+  }
+  return names;
+}
+
 FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
     const Flatbuffer<Configuration> &config) {
   // auto_merge_config will contain all the fields of the Configuration that are
diff --git a/aos/configuration.h b/aos/configuration.h
index cb52d09..31603fe 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -91,6 +91,23 @@
                     channel->type()->string_view(), application_name, node);
 }
 
+// Returns a list of all the channel names that can be used to refer to the
+// specified channel on the given node/application. this allows a reverse-lookup
+// of any renames that happen.
+// Does not perform forwards-lookup first.
+std::set<std::string> GetChannelAliases(const Configuration *config,
+                                        std::string_view name,
+                                        std::string_view type,
+                                        const std::string_view application_name,
+                                        const Node *node);
+inline std::set<std::string> GetChannelAliases(
+    const Configuration *config, const Channel *channel,
+    const std::string_view application_name, const Node *node) {
+  return GetChannelAliases(config, channel->name()->string_view(),
+                           channel->type()->string_view(), application_name,
+                           node);
+}
+
 // Returns the channel index (or dies) of channel in the provided config.
 size_t ChannelIndex(const Configuration *config, const Channel *channel);
 
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index b493058..242ee17 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -190,6 +190,31 @@
               aos::testing::FlatbufferEq(ExpectedLocation()));
 }
 
+// Tests that we can do reverse-lookups of channel names.
+TEST_F(ConfigurationTest, GetChannelAliases) {
+  FlatbufferDetachedBuffer<Configuration> config =
+      ReadConfig(ArtifactPath("aos/testdata/config1.json"));
+
+  // Test a basic lookup first.
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app1", nullptr),
+      ::testing::UnorderedElementsAre("/foo", "/batman", "/bar"));
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/bar", ".aos.bar", "app1", nullptr),
+      ::testing::UnorderedElementsAre("/batman", "/bar"));
+  EXPECT_THAT(GetChannelAliases(&config.message(), "/batman", ".aos.bar",
+                                "app1", nullptr),
+              ::testing::UnorderedElementsAre("/batman"));
+  // /bar (deliberately) does not get included because of the ordering in the
+  // map.
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo", ".aos.bar", "", nullptr),
+      ::testing::UnorderedElementsAre("/foo", "/batman"));
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app2", nullptr),
+      ::testing::UnorderedElementsAre("/foo", "/batman", "/baz"));
+}
+
 // Tests that we can lookup a location with node specific maps.
 TEST_F(ConfigurationTest, GetChannelMultinode) {
   FlatbufferDetachedBuffer<Configuration> config =
@@ -221,6 +246,34 @@
   EXPECT_EQ(GetChannel(config, "/batman3", ".aos.bar", "app1", pi2), nullptr);
 }
 
+// Tests that reverse channel lookup on a multi-node config (including with
+// wildcards) works.
+TEST_F(ConfigurationTest, GetChannelAliasesMultinode) {
+  FlatbufferDetachedBuffer<Configuration> config =
+      ReadConfig(ArtifactPath("aos/testdata/good_multinode.json"));
+
+  const Node *pi1 = GetNode(&config.message(), "pi1");
+  const Node *pi2 = GetNode(&config.message(), "pi2");
+
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo", ".aos.bar", "app1", pi1),
+      ::testing::UnorderedElementsAre("/foo", "/batman", "/batman2", "/batman3",
+                                      "/magic/string"));
+
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo", ".aos.baz", "app1", pi1),
+      ::testing::UnorderedElementsAre("/foo", "/batman3", "/magic/string"));
+
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo/testing", ".aos.bar", "", pi1),
+      ::testing::UnorderedElementsAre("/foo/testing", "/magic/string/testing"));
+
+  EXPECT_THAT(
+      GetChannelAliases(&config.message(), "/foo/testing", ".aos.bar", "app1",
+                        pi2),
+      ::testing::UnorderedElementsAre("/foo/testing", "/magic/string/testing"));
+}
+
 // Tests that we can lookup a location with type specific maps.
 TEST_F(ConfigurationTest, GetChannelTypedMultinode) {
   FlatbufferDetachedBuffer<Configuration> config =