Add a "configuration" channel to MCAP files for foxglove

This is helpful for doing certain types of analysis where you might want
to, e.g., correlate channel indices to a channel name/type (e.g., for
analyzing sent-too-fast errors or for using the ReplayTiming
message implemented by I471fefd96a4d043766b54dd4488726e24926a95f).

Change-Id: Ic68026be58205607a2099ccfd7547187989ec26c
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/util/mcap_logger.cc b/aos/util/mcap_logger.cc
index 561a02b..dc27504 100644
--- a/aos/util/mcap_logger.cc
+++ b/aos/util/mcap_logger.cc
@@ -1,6 +1,7 @@
 #include "aos/util/mcap_logger.h"
 
 #include "absl/strings/str_replace.h"
+#include "aos/configuration_schema.h"
 #include "aos/flatbuffer_merge.h"
 #include "single_include/nlohmann/json.hpp"
 
@@ -85,7 +86,29 @@
                        Serialization serialization)
     : event_loop_(event_loop),
       output_(output_path),
-      serialization_(serialization) {
+      serialization_(serialization),
+      configuration_channel_([]() {
+        // Setup a fake Channel for providing the configuration in the MCAP
+        // file. This is included for convenience so that consumers of the MCAP
+        // file can actually dereference things like the channel indices in AOS
+        // timing reports.
+        flatbuffers::FlatBufferBuilder fbb;
+        flatbuffers::Offset<flatbuffers::String> name_offset =
+            fbb.CreateString("");
+        flatbuffers::Offset<flatbuffers::String> type_offset =
+            fbb.CreateString("aos.Configuration");
+        flatbuffers::Offset<reflection::Schema> schema_offset =
+            aos::CopyFlatBuffer(
+                aos::FlatbufferSpan<reflection::Schema>(ConfigurationSchema()),
+                &fbb);
+        Channel::Builder channel(fbb);
+        channel.add_name(name_offset);
+        channel.add_type(type_offset);
+        channel.add_schema(schema_offset);
+        fbb.Finish(channel.Finish());
+        return fbb.Release();
+      }()),
+      configuration_(CopyFlatBuffer(event_loop_->configuration())) {
   event_loop->SkipTimingReport();
   event_loop->SkipAosLog();
   CHECK(output_);
@@ -181,6 +204,20 @@
     }
   }
 
+  // Manually add in a special /configuration channel.
+  if (register_handlers == RegisterHandlers::kYes) {
+    configuration_id_ = ++id;
+    event_loop_->OnRun([this]() {
+      Context config_context;
+      config_context.monotonic_event_time = event_loop_->monotonic_now();
+      config_context.queue_index = 0;
+      config_context.size = configuration_.span().size();
+      config_context.data = configuration_.span().data();
+      WriteMessage(configuration_id_, &configuration_channel_.message(),
+                   config_context, &current_chunk_);
+    });
+  }
+
   std::vector<SummaryOffset> offsets;
 
   const uint64_t schema_offset = output_.tellp();
@@ -189,6 +226,8 @@
     WriteSchema(pair.first, pair.second);
   }
 
+  WriteSchema(configuration_id_, &configuration_channel_.message());
+
   const uint64_t channel_offset = output_.tellp();
 
   offsets.push_back(
@@ -201,6 +240,13 @@
     WriteChannel(pair.first, pair.first, pair.second);
   }
 
+  // Provide the configuration message on a special channel that is just named
+  // "configuration", which is guaranteed not to conflict with existing under
+  // our current naming scheme (since our current scheme will, at a minimum, put
+  // a space between the name/type of a channel).
+  WriteChannel(configuration_id_, configuration_id_,
+               &configuration_channel_.message(), "configuration");
+
   offsets.push_back({OpCode::kChannel, channel_offset,
                      static_cast<uint64_t>(output_.tellp()) - channel_offset});
   return offsets;
@@ -267,7 +313,8 @@
 }
 
 void McapLogger::WriteChannel(const uint16_t id, const uint16_t schema_id,
-                              const aos::Channel *channel) {
+                              const aos::Channel *channel,
+                              std::string_view override_name) {
   string_builder_.Reset();
   // Channel ID
   AppendInt16(&string_builder_, id);
@@ -275,8 +322,10 @@
   AppendInt16(&string_builder_, schema_id);
   // Topic name
   AppendString(&string_builder_,
-               absl::StrCat(channel->name()->string_view(), " ",
-                            channel->type()->string_view()));
+               override_name.empty()
+                   ? absl::StrCat(channel->name()->string_view(), " ",
+                                  channel->type()->string_view())
+                   : override_name);
   // Encoding
   switch (serialization_) {
     case Serialization::kJson: