Create AOS MCAP logger for testing Foxglove

Doesn't add indexing yet, so still limited in size.

https://github.com/foxglove/studio/issues/2909 tracks support for
flatbuffers in foxglove studio

References: PRO-13587
Change-Id: I7c8c15c765395ade979eb8a011cfdae65451b526
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/util/BUILD b/aos/util/BUILD
index 8df16e2..3b96cfd 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -34,6 +34,42 @@
     ],
 )
 
+cc_binary(
+    name = "log_to_mcap",
+    srcs = ["log_to_mcap.cc"],
+    deps = [
+        ":mcap_logger",
+        "//aos:init",
+        "//aos/events/logging:log_reader",
+        "//frc971/control_loops:control_loops_fbs",
+    ],
+)
+
+cc_library(
+    name = "mcap_logger",
+    srcs = ["mcap_logger.cc"],
+    hdrs = ["mcap_logger.h"],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        "//aos:configuration_fbs",
+        "//aos:fast_string_builder",
+        "//aos:flatbuffer_utils",
+        "//aos/events:event_loop",
+        "@com_github_nlohmann_json//:json",
+    ],
+)
+
+cc_test(
+    name = "mcap_logger_test",
+    srcs = ["mcap_logger_test.cc"],
+    target_compatible_with = ["@platforms//os:linux"],
+    deps = [
+        ":mcap_logger",
+        "//aos/testing:googletest",
+        "@com_github_nlohmann_json//:json",
+    ],
+)
+
 cc_library(
     name = "math",
     hdrs = ["math.h"],
diff --git a/aos/util/log_to_mcap.cc b/aos/util/log_to_mcap.cc
new file mode 100644
index 0000000..17555a4
--- /dev/null
+++ b/aos/util/log_to_mcap.cc
@@ -0,0 +1,34 @@
+#include "aos/events/event_loop_generated.h"
+#include "aos/events/logging/log_reader.h"
+#include "aos/init.h"
+#include "aos/util/mcap_logger.h"
+
+DEFINE_string(node, "", "Node to replay from the perspective of.");
+DEFINE_string(output_path, "/tmp/log.mcap", "Log to output.");
+
+// Converts an AOS log to an MCAP log that can be fed into Foxglove. To try this
+// out, run:
+// bazel run -c opt //aos/util:log_to_mcap -- --node NODE_NAME /path/to/logfile
+//
+// Then navigate to http://studio.foxglove.dev (or spin up your own instance
+// locally), and use it to open the file (this doesn't upload the file to
+// foxglove's servers or anything).
+int main(int argc, char *argv[]) {
+  aos::InitGoogle(&argc, &argv);
+
+  const std::vector<aos::logger::LogFile> logfiles =
+      aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
+
+  aos::logger::LogReader reader(logfiles);
+
+  reader.Register();
+
+  const aos::Node *node =
+      aos::configuration::GetNode(reader.configuration(), FLAGS_node);
+  CHECK_NOTNULL(node);
+  std::unique_ptr<aos::EventLoop> mcap_event_loop =
+      reader.event_loop_factory()->MakeEventLoop("mcap", node);
+  CHECK(!FLAGS_output_path.empty());
+  aos::McapLogger relogger(mcap_event_loop.get(), FLAGS_output_path);
+  reader.event_loop_factory()->Run();
+}
diff --git a/aos/util/mcap_logger.cc b/aos/util/mcap_logger.cc
new file mode 100644
index 0000000..9ab4012
--- /dev/null
+++ b/aos/util/mcap_logger.cc
@@ -0,0 +1,232 @@
+#include "aos/util/mcap_logger.h"
+
+#include "absl/strings/str_replace.h"
+#include "single_include/nlohmann/json.hpp"
+
+namespace aos {
+nlohmann::json JsonSchemaForFlatbuffer(const FlatbufferType &type,
+                                       JsonSchemaRecursion recursion_level) {
+  nlohmann::json schema;
+  if (recursion_level == JsonSchemaRecursion::kTopLevel) {
+    schema["$schema"] = "https://json-schema.org/draft/2020-12/schema";
+  }
+  schema["type"] = "object";
+  nlohmann::json properties;
+  for (int index = 0; index < type.NumberFields(); ++index) {
+    nlohmann::json field;
+    const bool is_array = type.FieldIsRepeating(index);
+    if (type.FieldIsSequence(index)) {
+      // For sub-tables/structs, just recurse.
+      nlohmann::json subtype = JsonSchemaForFlatbuffer(
+          type.FieldType(index), JsonSchemaRecursion::kNested);
+      if (is_array) {
+        field["type"] = "array";
+        field["items"] = subtype;
+      } else {
+        field = subtype;
+      }
+    } else {
+      std::string elementary_type;
+      switch (type.FieldElementaryType(index)) {
+        case flatbuffers::ET_UTYPE:
+        case flatbuffers::ET_CHAR:
+        case flatbuffers::ET_UCHAR:
+        case flatbuffers::ET_SHORT:
+        case flatbuffers::ET_USHORT:
+        case flatbuffers::ET_INT:
+        case flatbuffers::ET_UINT:
+        case flatbuffers::ET_LONG:
+        case flatbuffers::ET_ULONG:
+        case flatbuffers::ET_FLOAT:
+        case flatbuffers::ET_DOUBLE:
+          elementary_type = "number";
+          break;
+        case flatbuffers::ET_BOOL:
+          elementary_type = "boolean";
+          break;
+        case flatbuffers::ET_STRING:
+          elementary_type = "string";
+          break;
+        case flatbuffers::ET_SEQUENCE:
+          if (type.FieldIsEnum(index)) {
+            elementary_type = "string";
+          } else {
+            LOG(FATAL) << "Should not encounter any sequence fields here.";
+          }
+          break;
+      }
+      if (is_array) {
+        field["type"] = "array";
+        field["items"]["type"] = elementary_type;
+      } else {
+        field["type"] = elementary_type;
+      }
+    }
+    // the nlohmann::json [] operator needs an actual string, not just a
+    // string_view :(.
+    properties[std::string(type.FieldName(index))] = field;
+  }
+  schema["properties"] = properties;
+  return schema;
+}
+
+McapLogger::McapLogger(EventLoop *event_loop, const std::string &output_path)
+    : output_(output_path) {
+  event_loop->SkipTimingReport();
+  event_loop->SkipAosLog();
+  CHECK(output_);
+  WriteMagic();
+  WriteHeader();
+  uint16_t id = 1;
+  for (const Channel *channel : *event_loop->configuration()->channels()) {
+    if (!configuration::ChannelIsReadableOnNode(channel, event_loop->node())) {
+      continue;
+    }
+
+    WriteSchema(id, channel);
+
+    // Write out the channel entry that uses the schema (we just re-use the
+    // chema ID for the channel ID, since we aren't deduplicating schemas for
+    // channels that are of the same type).
+    WriteChannel(id, id, channel);
+
+    event_loop->MakeRawWatcher(
+        channel, [this, id, channel](const Context &context, const void *) {
+          WriteMessage(id, channel, context);
+        });
+    ++id;
+  }
+}
+
+McapLogger::~McapLogger() {
+  WriteDataEnd();
+  WriteFooter();
+  WriteMagic();
+}
+
+void McapLogger::WriteMagic() { output_ << "\x89MCAP0\r\n"; }
+
+void McapLogger::WriteHeader() {
+  string_builder_.Reset();
+  // "profile"
+  AppendString(&string_builder_, "x-aos");
+  // "library"
+  AppendString(&string_builder_, "AOS MCAP converter");
+  WriteRecord(OpCode::kHeader, string_builder_.Result());
+}
+
+void McapLogger::WriteFooter() {
+  string_builder_.Reset();
+  // Offsets and CRC32 for summary section, which we don't populate.
+  AppendInt64(&string_builder_, 0);
+  AppendInt64(&string_builder_, 0);
+  AppendInt32(&string_builder_, 0);
+  WriteRecord(OpCode::kFooter, string_builder_.Result());
+}
+
+void McapLogger::WriteDataEnd() {
+  string_builder_.Reset();
+  // CRC32 for the data, which we are too lazy to calculate.
+  AppendInt32(&string_builder_, 0);
+  WriteRecord(OpCode::kDataEnd, string_builder_.Result());
+}
+
+void McapLogger::WriteSchema(const uint16_t id, const aos::Channel *channel) {
+  CHECK(channel->has_schema());
+  std::string schema = JsonSchemaForFlatbuffer({channel->schema()}).dump();
+
+  // Write out the schema (we don't bother deduplicating schema types):
+  string_builder_.Reset();
+  // Schema ID
+  AppendInt16(&string_builder_, id);
+  // Type name
+  AppendString(&string_builder_, channel->type()->string_view());
+  // Encoding
+  AppendString(&string_builder_, "jsonschema");
+  // Actual schema itself
+  AppendString(&string_builder_, schema);
+  WriteRecord(OpCode::kSchema, string_builder_.Result());
+}
+
+void McapLogger::WriteChannel(const uint16_t id, const uint16_t schema_id,
+                              const aos::Channel *channel) {
+  string_builder_.Reset();
+  // Channel ID
+  AppendInt16(&string_builder_, id);
+  // Schema ID
+  AppendInt16(&string_builder_, schema_id);
+  // Topic name
+  AppendString(&string_builder_,
+               absl::StrCat(channel->name()->string_view(), " ",
+                            channel->type()->string_view()));
+  // Encoding
+  AppendString(&string_builder_, "json");
+  // Metadata (technically supposed to be a Map<string, string>)
+  AppendString(&string_builder_, "");
+  WriteRecord(OpCode::kChannel, string_builder_.Result());
+}
+
+void McapLogger::WriteMessage(uint16_t channel_id, const Channel *channel,
+                              const Context &context) {
+  CHECK_NOTNULL(context.data);
+
+  string_builder_.Reset();
+  // Channel ID
+  AppendInt16(&string_builder_, channel_id);
+  // Queue Index
+  AppendInt32(&string_builder_, context.queue_index);
+  // Log time, and publish time. Since we don't log a logged time, just use
+  // published time.
+  // TODO(james): If we use this for multi-node logfiles, use distributed clock.
+  AppendInt64(&string_builder_,
+              context.monotonic_event_time.time_since_epoch().count());
+  AppendInt64(&string_builder_,
+              context.monotonic_event_time.time_since_epoch().count());
+
+  CHECK(flatbuffers::Verify(*channel->schema(),
+                            *channel->schema()->root_table(),
+                            static_cast<const uint8_t *>(context.data),
+                            static_cast<size_t>(context.size)))
+      << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
+      << channel->type()->c_str();
+
+  aos::FlatbufferToJson(&string_builder_, channel->schema(),
+                        static_cast<const uint8_t *>(context.data));
+
+  WriteRecord(OpCode::kMessage, string_builder_.Result());
+}
+
+void McapLogger::WriteRecord(OpCode op, std::string_view record) {
+  output_.put(static_cast<char>(op));
+  uint64_t record_length = record.size();
+  output_.write(reinterpret_cast<const char *>(&record_length),
+                sizeof(record_length));
+  output_ << record;
+}
+
+void McapLogger::AppendString(FastStringBuilder *builder,
+                              std::string_view string) {
+  AppendInt32(builder, string.size());
+  builder->Append(string);
+}
+
+namespace {
+template <typename T>
+static void AppendInt(FastStringBuilder *builder, T val) {
+  builder->Append(
+      std::string_view(reinterpret_cast<const char *>(&val), sizeof(T)));
+}
+}  // namespace
+
+void McapLogger::AppendInt16(FastStringBuilder *builder, uint16_t val) {
+  AppendInt(builder, val);
+}
+
+void McapLogger::AppendInt32(FastStringBuilder *builder, uint32_t val) {
+  AppendInt(builder, val);
+}
+
+void McapLogger::AppendInt64(FastStringBuilder *builder, uint64_t val) {
+  AppendInt(builder, val);
+}
+}  // namespace aos
diff --git a/aos/util/mcap_logger.h b/aos/util/mcap_logger.h
new file mode 100644
index 0000000..15d11db
--- /dev/null
+++ b/aos/util/mcap_logger.h
@@ -0,0 +1,67 @@
+#ifndef AOS_UTIL_MCAP_LOGGER_H_
+#define AOS_UTIL_MCAP_LOGGER_H_
+
+#include "aos/configuration_generated.h"
+#include "aos/events/event_loop.h"
+#include "aos/fast_string_builder.h"
+#include "aos/flatbuffer_utils.h"
+#include "single_include/nlohmann/json.hpp"
+
+namespace aos {
+
+// Produces a JSON Schema (https://json-schema.org/) for a given flatbuffer
+// type. If recursion_level is set, will include a $schema attribute indicating
+// the schema definition being used (this is used to allow for recursion).
+//
+// Note that this is pretty bare-bones, so, e.g., we don't distinguish between
+// structs and tables when generating the JSON schema, so we don't bother to
+// mark struct fields as required.
+enum class JsonSchemaRecursion {
+  kTopLevel,
+  kNested,
+};
+nlohmann::json JsonSchemaForFlatbuffer(
+    const FlatbufferType &type,
+    JsonSchemaRecursion recursion_level = JsonSchemaRecursion::kTopLevel);
+
+// Generates an MCAP file, per the specification at
+// https://github.com/foxglove/mcap/tree/main/docs/specification
+class McapLogger {
+ public:
+  McapLogger(EventLoop *event_loop, const std::string &output_path);
+  ~McapLogger();
+
+ private:
+  enum class OpCode {
+    kHeader = 0x01,
+    kFooter = 0x02,
+    kSchema = 0x03,
+    kChannel = 0x04,
+    kMessage = 0x05,
+    kDataEnd = 0x0F,
+  };
+  // Helpers to write each type of relevant record.
+  void WriteMagic();
+  void WriteHeader();
+  void WriteFooter();
+  void WriteDataEnd();
+  void WriteSchema(const uint16_t id, const aos::Channel *channel);
+  void WriteChannel(const uint16_t id, const uint16_t schema_id,
+                    const aos::Channel *channel);
+  void WriteMessage(uint16_t channel_id, const Channel *channel,
+                    const Context &context);
+  void WriteConfig();
+
+  // Writes an MCAP record to the output file.
+  void WriteRecord(OpCode op, std::string_view record);
+  // Adds an MCAP-spec string/fixed-size integer to a buffer.
+  static void AppendString(FastStringBuilder *builder, std::string_view string);
+  static void AppendInt16(FastStringBuilder *builder, uint16_t val);
+  static void AppendInt32(FastStringBuilder *builder, uint32_t val);
+  static void AppendInt64(FastStringBuilder *builder, uint64_t val);
+
+  std::ofstream output_;
+  FastStringBuilder string_builder_;
+};
+}  // namespace aos
+#endif  // AOS_UTIL_MCAP_LOGGER_H_
diff --git a/aos/util/mcap_logger_test.cc b/aos/util/mcap_logger_test.cc
new file mode 100644
index 0000000..6e82a53
--- /dev/null
+++ b/aos/util/mcap_logger_test.cc
@@ -0,0 +1,829 @@
+#include "aos/util/mcap_logger.h"
+
+#include <iostream>
+#include "flatbuffers/reflection_generated.h"
+#include "gtest/gtest.h"
+
+namespace aos::testing {
+// TODO(james): Write a proper test for the McapLogger itself. However, that
+// will require writing an MCAP reader (or importing an existing one).
+
+// Confirm that the schema for the reflection.Schema table itself hasn't
+// changed. reflection.Schema should be a very stable type, so this should need
+// updating except when we change the JSON schema generation itself.
+TEST(JsonSchemaTest, ReflectionSchema) {
+  std::string schema_json =
+      JsonSchemaForFlatbuffer({reflection::Schema::MiniReflectTypeTable()})
+          .dump(4);
+  EXPECT_EQ(R"json({
+    "$schema": "https://json-schema.org/draft/2020-12/schema",
+    "properties": {
+        "advanced_features": {
+            "type": "number"
+        },
+        "enums": {
+            "items": {
+                "properties": {
+                    "attributes": {
+                        "items": {
+                            "properties": {
+                                "key": {
+                                    "type": "string"
+                                },
+                                "value": {
+                                    "type": "string"
+                                }
+                            },
+                            "type": "object"
+                        },
+                        "type": "array"
+                    },
+                    "declaration_file": {
+                        "type": "string"
+                    },
+                    "documentation": {
+                        "items": {
+                            "type": "string"
+                        },
+                        "type": "array"
+                    },
+                    "is_union": {
+                        "type": "boolean"
+                    },
+                    "name": {
+                        "type": "string"
+                    },
+                    "underlying_type": {
+                        "properties": {
+                            "base_size": {
+                                "type": "number"
+                            },
+                            "base_type": {
+                                "type": "number"
+                            },
+                            "element": {
+                                "type": "number"
+                            },
+                            "element_size": {
+                                "type": "number"
+                            },
+                            "fixed_length": {
+                                "type": "number"
+                            },
+                            "index": {
+                                "type": "number"
+                            }
+                        },
+                        "type": "object"
+                    },
+                    "values": {
+                        "items": {
+                            "properties": {
+                                "documentation": {
+                                    "items": {
+                                        "type": "string"
+                                    },
+                                    "type": "array"
+                                },
+                                "name": {
+                                    "type": "string"
+                                },
+                                "object": {
+                                    "properties": {
+                                        "attributes": {
+                                            "items": {
+                                                "properties": {
+                                                    "key": {
+                                                        "type": "string"
+                                                    },
+                                                    "value": {
+                                                        "type": "string"
+                                                    }
+                                                },
+                                                "type": "object"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "bytesize": {
+                                            "type": "number"
+                                        },
+                                        "declaration_file": {
+                                            "type": "string"
+                                        },
+                                        "documentation": {
+                                            "items": {
+                                                "type": "string"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "fields": {
+                                            "items": {
+                                                "properties": {
+                                                    "attributes": {
+                                                        "items": {
+                                                            "properties": {
+                                                                "key": {
+                                                                    "type": "string"
+                                                                },
+                                                                "value": {
+                                                                    "type": "string"
+                                                                }
+                                                            },
+                                                            "type": "object"
+                                                        },
+                                                        "type": "array"
+                                                    },
+                                                    "default_integer": {
+                                                        "type": "number"
+                                                    },
+                                                    "default_real": {
+                                                        "type": "number"
+                                                    },
+                                                    "deprecated": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "documentation": {
+                                                        "items": {
+                                                            "type": "string"
+                                                        },
+                                                        "type": "array"
+                                                    },
+                                                    "id": {
+                                                        "type": "number"
+                                                    },
+                                                    "key": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "name": {
+                                                        "type": "string"
+                                                    },
+                                                    "offset": {
+                                                        "type": "number"
+                                                    },
+                                                    "optional": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "padding": {
+                                                        "type": "number"
+                                                    },
+                                                    "required": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "type": {
+                                                        "properties": {
+                                                            "base_size": {
+                                                                "type": "number"
+                                                            },
+                                                            "base_type": {
+                                                                "type": "number"
+                                                            },
+                                                            "element": {
+                                                                "type": "number"
+                                                            },
+                                                            "element_size": {
+                                                                "type": "number"
+                                                            },
+                                                            "fixed_length": {
+                                                                "type": "number"
+                                                            },
+                                                            "index": {
+                                                                "type": "number"
+                                                            }
+                                                        },
+                                                        "type": "object"
+                                                    }
+                                                },
+                                                "type": "object"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "is_struct": {
+                                            "type": "boolean"
+                                        },
+                                        "minalign": {
+                                            "type": "number"
+                                        },
+                                        "name": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "type": "object"
+                                },
+                                "union_type": {
+                                    "properties": {
+                                        "base_size": {
+                                            "type": "number"
+                                        },
+                                        "base_type": {
+                                            "type": "number"
+                                        },
+                                        "element": {
+                                            "type": "number"
+                                        },
+                                        "element_size": {
+                                            "type": "number"
+                                        },
+                                        "fixed_length": {
+                                            "type": "number"
+                                        },
+                                        "index": {
+                                            "type": "number"
+                                        }
+                                    },
+                                    "type": "object"
+                                },
+                                "value": {
+                                    "type": "number"
+                                }
+                            },
+                            "type": "object"
+                        },
+                        "type": "array"
+                    }
+                },
+                "type": "object"
+            },
+            "type": "array"
+        },
+        "fbs_files": {
+            "items": {
+                "properties": {
+                    "filename": {
+                        "type": "string"
+                    },
+                    "included_filenames": {
+                        "items": {
+                            "type": "string"
+                        },
+                        "type": "array"
+                    }
+                },
+                "type": "object"
+            },
+            "type": "array"
+        },
+        "file_ext": {
+            "type": "string"
+        },
+        "file_ident": {
+            "type": "string"
+        },
+        "objects": {
+            "items": {
+                "properties": {
+                    "attributes": {
+                        "items": {
+                            "properties": {
+                                "key": {
+                                    "type": "string"
+                                },
+                                "value": {
+                                    "type": "string"
+                                }
+                            },
+                            "type": "object"
+                        },
+                        "type": "array"
+                    },
+                    "bytesize": {
+                        "type": "number"
+                    },
+                    "declaration_file": {
+                        "type": "string"
+                    },
+                    "documentation": {
+                        "items": {
+                            "type": "string"
+                        },
+                        "type": "array"
+                    },
+                    "fields": {
+                        "items": {
+                            "properties": {
+                                "attributes": {
+                                    "items": {
+                                        "properties": {
+                                            "key": {
+                                                "type": "string"
+                                            },
+                                            "value": {
+                                                "type": "string"
+                                            }
+                                        },
+                                        "type": "object"
+                                    },
+                                    "type": "array"
+                                },
+                                "default_integer": {
+                                    "type": "number"
+                                },
+                                "default_real": {
+                                    "type": "number"
+                                },
+                                "deprecated": {
+                                    "type": "boolean"
+                                },
+                                "documentation": {
+                                    "items": {
+                                        "type": "string"
+                                    },
+                                    "type": "array"
+                                },
+                                "id": {
+                                    "type": "number"
+                                },
+                                "key": {
+                                    "type": "boolean"
+                                },
+                                "name": {
+                                    "type": "string"
+                                },
+                                "offset": {
+                                    "type": "number"
+                                },
+                                "optional": {
+                                    "type": "boolean"
+                                },
+                                "padding": {
+                                    "type": "number"
+                                },
+                                "required": {
+                                    "type": "boolean"
+                                },
+                                "type": {
+                                    "properties": {
+                                        "base_size": {
+                                            "type": "number"
+                                        },
+                                        "base_type": {
+                                            "type": "number"
+                                        },
+                                        "element": {
+                                            "type": "number"
+                                        },
+                                        "element_size": {
+                                            "type": "number"
+                                        },
+                                        "fixed_length": {
+                                            "type": "number"
+                                        },
+                                        "index": {
+                                            "type": "number"
+                                        }
+                                    },
+                                    "type": "object"
+                                }
+                            },
+                            "type": "object"
+                        },
+                        "type": "array"
+                    },
+                    "is_struct": {
+                        "type": "boolean"
+                    },
+                    "minalign": {
+                        "type": "number"
+                    },
+                    "name": {
+                        "type": "string"
+                    }
+                },
+                "type": "object"
+            },
+            "type": "array"
+        },
+        "root_table": {
+            "properties": {
+                "attributes": {
+                    "items": {
+                        "properties": {
+                            "key": {
+                                "type": "string"
+                            },
+                            "value": {
+                                "type": "string"
+                            }
+                        },
+                        "type": "object"
+                    },
+                    "type": "array"
+                },
+                "bytesize": {
+                    "type": "number"
+                },
+                "declaration_file": {
+                    "type": "string"
+                },
+                "documentation": {
+                    "items": {
+                        "type": "string"
+                    },
+                    "type": "array"
+                },
+                "fields": {
+                    "items": {
+                        "properties": {
+                            "attributes": {
+                                "items": {
+                                    "properties": {
+                                        "key": {
+                                            "type": "string"
+                                        },
+                                        "value": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "type": "object"
+                                },
+                                "type": "array"
+                            },
+                            "default_integer": {
+                                "type": "number"
+                            },
+                            "default_real": {
+                                "type": "number"
+                            },
+                            "deprecated": {
+                                "type": "boolean"
+                            },
+                            "documentation": {
+                                "items": {
+                                    "type": "string"
+                                },
+                                "type": "array"
+                            },
+                            "id": {
+                                "type": "number"
+                            },
+                            "key": {
+                                "type": "boolean"
+                            },
+                            "name": {
+                                "type": "string"
+                            },
+                            "offset": {
+                                "type": "number"
+                            },
+                            "optional": {
+                                "type": "boolean"
+                            },
+                            "padding": {
+                                "type": "number"
+                            },
+                            "required": {
+                                "type": "boolean"
+                            },
+                            "type": {
+                                "properties": {
+                                    "base_size": {
+                                        "type": "number"
+                                    },
+                                    "base_type": {
+                                        "type": "number"
+                                    },
+                                    "element": {
+                                        "type": "number"
+                                    },
+                                    "element_size": {
+                                        "type": "number"
+                                    },
+                                    "fixed_length": {
+                                        "type": "number"
+                                    },
+                                    "index": {
+                                        "type": "number"
+                                    }
+                                },
+                                "type": "object"
+                            }
+                        },
+                        "type": "object"
+                    },
+                    "type": "array"
+                },
+                "is_struct": {
+                    "type": "boolean"
+                },
+                "minalign": {
+                    "type": "number"
+                },
+                "name": {
+                    "type": "string"
+                }
+            },
+            "type": "object"
+        },
+        "services": {
+            "items": {
+                "properties": {
+                    "attributes": {
+                        "items": {
+                            "properties": {
+                                "key": {
+                                    "type": "string"
+                                },
+                                "value": {
+                                    "type": "string"
+                                }
+                            },
+                            "type": "object"
+                        },
+                        "type": "array"
+                    },
+                    "calls": {
+                        "items": {
+                            "properties": {
+                                "attributes": {
+                                    "items": {
+                                        "properties": {
+                                            "key": {
+                                                "type": "string"
+                                            },
+                                            "value": {
+                                                "type": "string"
+                                            }
+                                        },
+                                        "type": "object"
+                                    },
+                                    "type": "array"
+                                },
+                                "documentation": {
+                                    "items": {
+                                        "type": "string"
+                                    },
+                                    "type": "array"
+                                },
+                                "name": {
+                                    "type": "string"
+                                },
+                                "request": {
+                                    "properties": {
+                                        "attributes": {
+                                            "items": {
+                                                "properties": {
+                                                    "key": {
+                                                        "type": "string"
+                                                    },
+                                                    "value": {
+                                                        "type": "string"
+                                                    }
+                                                },
+                                                "type": "object"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "bytesize": {
+                                            "type": "number"
+                                        },
+                                        "declaration_file": {
+                                            "type": "string"
+                                        },
+                                        "documentation": {
+                                            "items": {
+                                                "type": "string"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "fields": {
+                                            "items": {
+                                                "properties": {
+                                                    "attributes": {
+                                                        "items": {
+                                                            "properties": {
+                                                                "key": {
+                                                                    "type": "string"
+                                                                },
+                                                                "value": {
+                                                                    "type": "string"
+                                                                }
+                                                            },
+                                                            "type": "object"
+                                                        },
+                                                        "type": "array"
+                                                    },
+                                                    "default_integer": {
+                                                        "type": "number"
+                                                    },
+                                                    "default_real": {
+                                                        "type": "number"
+                                                    },
+                                                    "deprecated": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "documentation": {
+                                                        "items": {
+                                                            "type": "string"
+                                                        },
+                                                        "type": "array"
+                                                    },
+                                                    "id": {
+                                                        "type": "number"
+                                                    },
+                                                    "key": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "name": {
+                                                        "type": "string"
+                                                    },
+                                                    "offset": {
+                                                        "type": "number"
+                                                    },
+                                                    "optional": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "padding": {
+                                                        "type": "number"
+                                                    },
+                                                    "required": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "type": {
+                                                        "properties": {
+                                                            "base_size": {
+                                                                "type": "number"
+                                                            },
+                                                            "base_type": {
+                                                                "type": "number"
+                                                            },
+                                                            "element": {
+                                                                "type": "number"
+                                                            },
+                                                            "element_size": {
+                                                                "type": "number"
+                                                            },
+                                                            "fixed_length": {
+                                                                "type": "number"
+                                                            },
+                                                            "index": {
+                                                                "type": "number"
+                                                            }
+                                                        },
+                                                        "type": "object"
+                                                    }
+                                                },
+                                                "type": "object"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "is_struct": {
+                                            "type": "boolean"
+                                        },
+                                        "minalign": {
+                                            "type": "number"
+                                        },
+                                        "name": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "type": "object"
+                                },
+                                "response": {
+                                    "properties": {
+                                        "attributes": {
+                                            "items": {
+                                                "properties": {
+                                                    "key": {
+                                                        "type": "string"
+                                                    },
+                                                    "value": {
+                                                        "type": "string"
+                                                    }
+                                                },
+                                                "type": "object"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "bytesize": {
+                                            "type": "number"
+                                        },
+                                        "declaration_file": {
+                                            "type": "string"
+                                        },
+                                        "documentation": {
+                                            "items": {
+                                                "type": "string"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "fields": {
+                                            "items": {
+                                                "properties": {
+                                                    "attributes": {
+                                                        "items": {
+                                                            "properties": {
+                                                                "key": {
+                                                                    "type": "string"
+                                                                },
+                                                                "value": {
+                                                                    "type": "string"
+                                                                }
+                                                            },
+                                                            "type": "object"
+                                                        },
+                                                        "type": "array"
+                                                    },
+                                                    "default_integer": {
+                                                        "type": "number"
+                                                    },
+                                                    "default_real": {
+                                                        "type": "number"
+                                                    },
+                                                    "deprecated": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "documentation": {
+                                                        "items": {
+                                                            "type": "string"
+                                                        },
+                                                        "type": "array"
+                                                    },
+                                                    "id": {
+                                                        "type": "number"
+                                                    },
+                                                    "key": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "name": {
+                                                        "type": "string"
+                                                    },
+                                                    "offset": {
+                                                        "type": "number"
+                                                    },
+                                                    "optional": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "padding": {
+                                                        "type": "number"
+                                                    },
+                                                    "required": {
+                                                        "type": "boolean"
+                                                    },
+                                                    "type": {
+                                                        "properties": {
+                                                            "base_size": {
+                                                                "type": "number"
+                                                            },
+                                                            "base_type": {
+                                                                "type": "number"
+                                                            },
+                                                            "element": {
+                                                                "type": "number"
+                                                            },
+                                                            "element_size": {
+                                                                "type": "number"
+                                                            },
+                                                            "fixed_length": {
+                                                                "type": "number"
+                                                            },
+                                                            "index": {
+                                                                "type": "number"
+                                                            }
+                                                        },
+                                                        "type": "object"
+                                                    }
+                                                },
+                                                "type": "object"
+                                            },
+                                            "type": "array"
+                                        },
+                                        "is_struct": {
+                                            "type": "boolean"
+                                        },
+                                        "minalign": {
+                                            "type": "number"
+                                        },
+                                        "name": {
+                                            "type": "string"
+                                        }
+                                    },
+                                    "type": "object"
+                                }
+                            },
+                            "type": "object"
+                        },
+                        "type": "array"
+                    },
+                    "declaration_file": {
+                        "type": "string"
+                    },
+                    "documentation": {
+                        "items": {
+                            "type": "string"
+                        },
+                        "type": "array"
+                    },
+                    "name": {
+                        "type": "string"
+                    }
+                },
+                "type": "object"
+            },
+            "type": "array"
+        }
+    },
+    "type": "object"
+})json",
+            schema_json);
+}
+
+}  // namespace aos::testing