Add more helpers.

GetNode, GetNodeOrDie, GetChannelIndex, GetNodeIndex.  All useful when
reading multi-node log files.

Change-Id: I8a2cb88a820ac8baf6cf637ea654dc39e9a03855
diff --git a/aos/configuration.cc b/aos/configuration.cc
index 095add5..c4f139c 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -470,6 +470,18 @@
   }
 }
 
+size_t ChannelIndex(const Configuration *configuration,
+                    const Channel *channel) {
+  CHECK(configuration->channels() != nullptr) << ": No channels";
+
+  auto c = std::find(configuration->channels()->begin(),
+                     configuration->channels()->end(), channel);
+  CHECK(c != configuration->channels()->end())
+      << ": Channel pointer not found in configuration()->channels()";
+
+  return std::distance(configuration->channels()->begin(), c);
+}
+
 std::string CleanedChannelToString(const Channel *channel) {
   FlatbufferDetachedBuffer<Channel> cleaned_channel = CopyFlatBuffer(channel);
   cleaned_channel.mutable_message()->clear_schema();
@@ -605,6 +617,17 @@
   return nullptr;
 }
 
+const Node *GetNode(const Configuration *config, const Node *node) {
+  if (!MultiNode(config)) {
+    CHECK(node == nullptr) << ": Provided a node in a single node world.";
+    return nullptr;
+  } else {
+    CHECK(node != nullptr);
+    CHECK(node->has_name());
+    return GetNode(config, node->name()->string_view());
+  }
+}
+
 const Node *GetNode(const Configuration *config, std::string_view name) {
   CHECK(config->has_nodes())
       << ": Asking for a node from a single node configuration.";
@@ -617,6 +640,44 @@
   return nullptr;
 }
 
+const Node *GetNodeOrDie(const Configuration *config, const Node *node) {
+  if (!MultiNode(config)) {
+    CHECK(node == nullptr) << ": Provided a node in a single node world.";
+    return nullptr;
+  } else {
+    const Node *config_node = GetNode(config, node);
+    if (config_node == nullptr) {
+      LOG(FATAL) << "Couldn't find node matching " << FlatbufferToJson(node);
+    }
+    return config_node;
+  }
+}
+
+int GetNodeIndex(const Configuration *config, const Node *node) {
+  CHECK(config->has_nodes())
+      << ": Asking for a node from a single node configuration.";
+  int node_index = 0;
+  for (const Node *iterated_node : *config->nodes()) {
+    if (iterated_node == node) {
+      return node_index;
+    }
+    ++node_index;
+  }
+  LOG(FATAL) << "Node not found in the configuration.";
+}
+
+std::vector<const Node *> GetNodes(const Configuration *config) {
+  std::vector<const Node *> nodes;
+  if (configuration::MultiNode(config)) {
+    for (const Node *node : *config->nodes()) {
+      nodes.emplace_back(node);
+    }
+  } else {
+    nodes.emplace_back(nullptr);
+  }
+  return nodes;
+}
+
 bool MultiNode(const Configuration *config) { return config->has_nodes(); }
 
 bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
diff --git a/aos/configuration.h b/aos/configuration.h
index 88687ad..a9fbfe3 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -60,15 +60,29 @@
                     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);
+
 // Returns the Node out of the config with the matching name, or nullptr if it
 // can't be found.
 const Node *GetNode(const Configuration *config, std::string_view name);
+const Node *GetNode(const Configuration *config, const Node *node);
+// Returns a matching node, or nullptr if the provided node is nullptr and we
+// are in a single node world.
+const Node *GetNodeOrDie(const Configuration *config, const Node *node);
 // Returns the Node out of the configuration which matches our hostname.
 // CHECKs if it can't be found.
 const Node *GetMyNode(const Configuration *config);
 const Node *GetNodeFromHostname(const Configuration *config,
                                 std::string_view name);
 
+// Returns a vector of the nodes in the config.  (nullptr is considered the node
+// in a single node world.)
+std::vector<const Node *> GetNodes(const Configuration *config);
+
+// Returns the node index for a node.  Note: node needs to exist inside config.
+int GetNodeIndex(const Configuration *config, const Node *node);
+
 // Returns true if we are running in a multinode configuration.
 bool MultiNode(const Configuration *config);
 
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index b8fc5b6..70515fd 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -39,6 +39,16 @@
       FlatbufferToJson(config, true));
 }
 
+// Tests that we can get back a ChannelIndex.
+TEST_F(ConfigurationTest, ChannelIndex) {
+  FlatbufferDetachedBuffer<Configuration> config =
+      ReadConfig("aos/testdata/config1.json");
+
+  EXPECT_EQ(
+      ChannelIndex(&config.message(), config.message().channels()->Get(1u)),
+      1u);
+}
+
 // Tests that we can read and merge a multinode configuration.
 TEST_F(ConfigurationTest, ConfigMergeMultinode) {
   FlatbufferDetachedBuffer<Configuration> config =
@@ -599,6 +609,63 @@
       ::testing::ElementsAreArray({"pi1"}));
 }
 
+// Tests that we can pull out all the nodes.
+TEST_F(ConfigurationTest, GetNodes) {
+  {
+    FlatbufferDetachedBuffer<Configuration> config =
+        ReadConfig("aos/testdata/good_multinode.json");
+    const Node *pi1 = GetNode(&config.message(), "pi1");
+    const Node *pi2 = GetNode(&config.message(), "pi2");
+
+    EXPECT_THAT(GetNodes(&config.message()), ::testing::ElementsAre(pi1, pi2));
+  }
+
+  {
+    FlatbufferDetachedBuffer<Configuration> config =
+        ReadConfig("aos/testdata/config1.json");
+    EXPECT_THAT(GetNodes(&config.message()), ::testing::ElementsAre(nullptr));
+  }
+}
+
+// Tests that we can extract a node index from a config.
+TEST_F(ConfigurationTest, GetNodeIndex) {
+  FlatbufferDetachedBuffer<Configuration> config =
+      ReadConfig("aos/testdata/good_multinode.json");
+  const Node *pi1 = GetNode(&config.message(), "pi1");
+  const Node *pi2 = GetNode(&config.message(), "pi2");
+
+  EXPECT_EQ(GetNodeIndex(&config.message(), pi1), 0);
+  EXPECT_EQ(GetNodeIndex(&config.message(), pi2), 1);
+}
+
+// Tests that GetNodeOrDie handles both single and multi-node worlds and returns
+// valid nodes.
+TEST_F(ConfigurationDeathTest, GetNodeOrDie) {
+  FlatbufferDetachedBuffer<Configuration> config =
+      ReadConfig("aos/testdata/good_multinode.json");
+  FlatbufferDetachedBuffer<Configuration> config2 =
+      ReadConfig("aos/testdata/good_multinode.json");
+  {
+    // Simple case, nullptr -> nullptr
+    FlatbufferDetachedBuffer<Configuration> single_node_config =
+        ReadConfig("aos/testdata/config1.json");
+    EXPECT_EQ(nullptr, GetNodeOrDie(&single_node_config.message(), nullptr));
+
+    // Confirm that we die when a node is passed in.
+    EXPECT_DEATH(
+        {
+          GetNodeOrDie(&single_node_config.message(),
+                       config.message().nodes()->Get(0));
+        },
+        "Provided a node in a single node world.");
+  }
+
+  const Node *pi1 = GetNode(&config.message(), "pi1");
+  // Now try a lookup using a node from a different instance of the config.
+  EXPECT_EQ(pi1,
+            GetNodeOrDie(&config.message(), config2.message().nodes()->Get(0)));
+}
+
 }  // namespace testing
 }  // namespace configuration
 }  // namespace aos
diff --git a/aos/events/event_loop.cc b/aos/events/event_loop.cc
index e368df2..c6d3755 100644
--- a/aos/events/event_loop.cc
+++ b/aos/events/event_loop.cc
@@ -71,14 +71,7 @@
 }
 
 int EventLoop::ChannelIndex(const Channel *channel) {
-  CHECK(configuration_->channels() != nullptr) << ": No channels";
-
-  auto c = std::find(configuration_->channels()->begin(),
-                     configuration_->channels()->end(), channel);
-  CHECK(c != configuration_->channels()->end())
-      << ": Channel pointer not found in configuration()->channels()";
-
-  return std::distance(configuration()->channels()->begin(), c);
+  return configuration::ChannelIndex(configuration_, channel);
 }
 
 void EventLoop::NewSender(RawSender *sender) {