Merge "Mark our SCTP traffic to have higher IP precedence"
diff --git a/aos/BUILD b/aos/BUILD
index 4d041ca..7c0ad3d 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -239,6 +239,7 @@
     srcs = ["configuration.fbs"],
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
+    deps = ["//aos/flatbuffers/reflection:reflection_fbs"],
 )
 
 cc_static_flatbuffer(
diff --git a/aos/events/logging/log_backend_test.cc b/aos/events/logging/log_backend_test.cc
index d3c83cc..1e95c10 100644
--- a/aos/events/logging/log_backend_test.cc
+++ b/aos/events/logging/log_backend_test.cc
@@ -128,7 +128,17 @@
 
 TEST(QueueAlignmentTest, Cases) {
   QueueAligner aligner;
-  uint8_t *start = nullptr;
+
+  // Get a 512-byte-aligned pointer to a buffer. That buffer needs to be at
+  // least 3 sectors big for the purposes of this test.
+  uint8_t buffer[FileHandler::kSector * 4];
+  void *aligned_start = buffer;
+  size_t size = sizeof(buffer);
+  ASSERT_TRUE(std::align(FileHandler::kSector, FileHandler::kSector * 3,
+                         aligned_start, size) != nullptr);
+  ASSERT_GE(size, FileHandler::kSector * 3);
+
+  uint8_t *start = static_cast<uint8_t *>(aligned_start);
   {
     // Only prefix
     std::vector<absl::Span<const uint8_t>> queue;
diff --git a/aos/flatbuffers/BUILD b/aos/flatbuffers/BUILD
index 08d548f..32f1d39 100644
--- a/aos/flatbuffers/BUILD
+++ b/aos/flatbuffers/BUILD
@@ -102,6 +102,7 @@
         ":test_schema",
         "//aos:flatbuffers",
         "//aos:json_to_flatbuffer",
+        "//aos/flatbuffers/test_dir:include_reflection_fbs",
         "//aos/flatbuffers/test_dir:type_coverage_fbs",
         "//aos/testing:googletest",
         "//aos/testing:path",
diff --git a/aos/flatbuffers/builder.h b/aos/flatbuffers/builder.h
index db89d10..36225c0 100644
--- a/aos/flatbuffers/builder.h
+++ b/aos/flatbuffers/builder.h
@@ -77,6 +77,9 @@
   FlatbufferSpan<typename T::Flatbuffer> AsFlatbufferSpan() {
     return {buffer()};
   }
+  FlatbufferSpan<const typename T::Flatbuffer> AsFlatbufferSpan() const {
+    return {buffer()};
+  }
 
   // Returns true if the flatbuffer is validly constructed. Should always return
   // true (barring some sort of memory corruption). Exposed for convenience.
diff --git a/aos/flatbuffers/reflection/BUILD.bazel b/aos/flatbuffers/reflection/BUILD.bazel
new file mode 100644
index 0000000..475f2a2
--- /dev/null
+++ b/aos/flatbuffers/reflection/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
+load("//aos/flatbuffers:generate.bzl", "static_flatbuffer")
+
+copy_file(
+    name = "reflection_fbs_copy",
+    src = "@com_github_google_flatbuffers//reflection:reflection_fbs_schema",
+    out = "reflection.fbs",
+)
+
+# This autogenerates both a reflection_static.h and a reflection_generated.h.
+# However, in order to avoid having two conflicting headers floating around,
+# we forcibly override the #include to use flatbuffers/reflection_generated.h
+# in static_flatbuffers.cc
+static_flatbuffer(
+    name = "reflection_fbs",
+    srcs = ["reflection.fbs"],
+    visibility = ["//visibility:public"],
+)
diff --git a/aos/flatbuffers/static_flatbuffers.cc b/aos/flatbuffers/static_flatbuffers.cc
index 8d8942c..c1b617f 100644
--- a/aos/flatbuffers/static_flatbuffers.cc
+++ b/aos/flatbuffers/static_flatbuffers.cc
@@ -19,6 +19,8 @@
   bool is_inline = true;
   // Whether this is a struct or not.
   bool is_struct = false;
+  // Whether this is a repeated type (vector or string).
+  bool is_repeated = false;
   // Full C++ type of this field.
   std::string full_type = "";
   // Full flatbuffer type for this field.
@@ -118,6 +120,22 @@
 
 const std::string IncludePathForFbs(
     std::string_view fbs_file, std::string_view include_suffix = "static") {
+  // Special case for the reflection_generated.h, which is checked into the
+  // repo.
+  // Note that we *do* autogenerated the reflection_static.h but that because
+  // it uses a special import path, we end up overriding the include anyways
+  // (note that we could muck around with the paths on the bazel side to instead
+  // get a cc_library with the correct include paths specified, although it is
+  // not clear that that would be any simpler than the extra else-if).
+  if (fbs_file == "reflection/reflection.fbs") {
+    if (include_suffix == "generated") {
+      return "flatbuffers/reflection_generated.h";
+    } else if (include_suffix == "static") {
+      return "aos/flatbuffers/reflection/reflection_static.h";
+    } else {
+      LOG(FATAL) << "This should be unreachable.";
+    }
+  }
   fbs_file.remove_suffix(4);
   return absl::StrCat(fbs_file, "_", include_suffix, ".h");
 }
@@ -151,12 +169,14 @@
       // straightforwards.
       field->is_inline = true;
       field->is_struct = false;
+      field->is_repeated = false;
       field->full_type =
           ScalarOrEnumType(schema, type->base_type(), type->index());
       return;
     case reflection::BaseType::String: {
       field->is_inline = false;
       field->is_struct = false;
+      field->is_repeated = true;
       field->full_type =
           absl::StrFormat("::aos::fbs::String<%d>",
                           GetLengthAttributeOrZero(field_fbs, "static_length"));
@@ -166,6 +186,7 @@
       // We need to extract the name of the elements of the vector.
       std::string element_type;
       bool elements_are_inline = true;
+      field->is_repeated = true;
       if (type->base_type() == reflection::BaseType::Vector) {
         switch (type->element()) {
           case reflection::BaseType::Obj: {
@@ -207,6 +228,7 @@
       const reflection::Object *object = GetObject(schema, type->index());
       field->is_inline = object->is_struct();
       field->is_struct = object->is_struct();
+      field->is_repeated = false;
       const std::string flatbuffer_name =
           FlatbufferNameToCppName(object->name()->string_view());
       if (field->is_inline) {
@@ -437,27 +459,97 @@
                          absl::StrJoin(clearers, "\n"));
 }
 
+// Creates the FromFlatbuffer() method that copies from a flatbuffer object API
+// object (i.e., the FlatbufferT types).
+std::string MakeObjectCopier(const std::vector<FieldData> &fields) {
+  std::vector<std::string> copiers;
+  for (const FieldData &field : fields) {
+    if (field.is_struct) {
+      // Structs are stored as unique_ptr<FooStruct>
+      copiers.emplace_back(absl::StrFormat(R"code(
+      if (other.%s) {
+        set_%s(*other.%s);
+      }
+      )code",
+                                           field.name, field.name, field.name));
+    } else if (field.is_inline) {
+      // Inline non-struct elements are stored as FooType.
+      copiers.emplace_back(absl::StrFormat(R"code(
+      set_%s(other.%s);
+      )code",
+                                           field.name, field.name));
+    } else if (field.is_repeated) {
+      // strings are stored as std::string's.
+      // vectors are stored as std::vector's.
+      copiers.emplace_back(absl::StrFormat(R"code(
+      // Unconditionally copy strings/vectors, even if it will just end up
+      // being 0-length (this maintains consistency with the flatbuffer Pack()
+      // behavior).
+      if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(other.%s)) {
+        // Fail if we were unable to copy (e.g., if we tried to copy in a long
+        // vector and do not have the space for it).
+        return false;
+      }
+      )code",
+                                           field.name, field.name));
+    } else {
+      // Tables are stored as unique_ptr<FooTable>
+      copiers.emplace_back(absl::StrFormat(R"code(
+      if (other.%s) {
+        if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(*other.%s)) {
+          // Fail if we were unable to copy (e.g., if we tried to copy in a long
+          // vector and do not have the space for it).
+          return false;
+        }
+      }
+      )code",
+                                           field.name, field.name, field.name));
+    }
+  }
+  return absl::StrFormat(
+      R"code(
+  // Copies the contents of the provided flatbuffer into this flatbuffer,
+  // returning true on success.
+  // Because the Flatbuffer Object API does not provide any concept of an
+  // optionally populated scalar field, all scalar fields will be populated
+  // after a call to FromFlatbufferObject().
+  // This is a deep copy, and will call FromFlatbufferObject on
+  // any constituent objects.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer::NativeTableType &other) {
+    Clear();
+    %s
+    return true;
+  }
+  [[nodiscard]] bool FromFlatbuffer(const flatbuffers::unique_ptr<Flatbuffer::NativeTableType>& other) {
+    return FromFlatbuffer(*other);
+  }
+)code",
+      absl::StrJoin(copiers, "\n"));
+}
+
+// Creates the FromFlatbuffer() method that copies from an actual flatbuffer
+// object.
 std::string MakeCopier(const std::vector<FieldData> &fields) {
   std::vector<std::string> copiers;
   for (const FieldData &field : fields) {
     if (field.is_struct) {
       copiers.emplace_back(absl::StrFormat(R"code(
-      if (other->has_%s()) {
-        set_%s(*other->%s());
+      if (other.has_%s()) {
+        set_%s(*other.%s());
       }
       )code",
                                            field.name, field.name, field.name));
     } else if (field.is_inline) {
       copiers.emplace_back(absl::StrFormat(R"code(
-      if (other->has_%s()) {
-        set_%s(other->%s());
+      if (other.has_%s()) {
+        set_%s(other.%s());
       }
       )code",
                                            field.name, field.name, field.name));
     } else {
       copiers.emplace_back(absl::StrFormat(R"code(
-      if (other->has_%s()) {
-        if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(other->%s())) {
+      if (other.has_%s()) {
+        if (!CHECK_NOTNULL(add_%s())->FromFlatbuffer(other.%s())) {
           // Fail if we were unable to copy (e.g., if we tried to copy in a long
           // vector and do not have the space for it).
           return false;
@@ -473,11 +565,16 @@
   // returning true on success.
   // This is a deep copy, and will call FromFlatbuffer on any constituent
   // objects.
-  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer &other) {
     Clear();
     %s
     return true;
   }
+  // Equivalent to FromFlatbuffer(const Flatbuffer&); this overload is provided
+  // to ease implementation of the aos::fbs::Vector internals.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+    return FromFlatbuffer(*CHECK_NOTNULL(other));
+  }
 )code",
       absl::StrJoin(copiers, "\n"));
 }
@@ -689,6 +786,7 @@
   public:
   // The underlying "raw" flatbuffer type for this type.
   typedef %s Flatbuffer;
+  typedef flatbuffers::unique_ptr<Flatbuffer::NativeTableType> FlatbufferObjectType;
   // Returns this object as a flatbuffer type. This reference may not be valid
   // following mutations to the underlying flatbuffer, due to how memory may get
   // may get moved around.
@@ -699,6 +797,7 @@
 %s
 %s
 %s
+%s
   private:
 %s
 %s
@@ -708,7 +807,7 @@
   )code",
       type_namespace, type_name, FlatbufferNameToCppName(fbs_type_name),
       constants, MakeConstructor(type_name), type_name, accessors,
-      MakeFullClearer(fields), MakeCopier(fields),
+      MakeFullClearer(fields), MakeCopier(fields), MakeObjectCopier(fields),
       MakeMoveConstructor(type_name), members, MakeSubObjectList(fields));
 
   GeneratedObject result;
diff --git a/aos/flatbuffers/static_flatbuffers_test.cc b/aos/flatbuffers/static_flatbuffers_test.cc
index fd95db3..52fa01e 100644
--- a/aos/flatbuffers/static_flatbuffers_test.cc
+++ b/aos/flatbuffers/static_flatbuffers_test.cc
@@ -10,6 +10,7 @@
 #include "aos/flatbuffers.h"
 #include "aos/flatbuffers/builder.h"
 #include "aos/flatbuffers/interesting_schemas.h"
+#include "aos/flatbuffers/test_dir/include_reflection_static.h"
 #include "aos/flatbuffers/test_dir/type_coverage_static.h"
 #include "aos/flatbuffers/test_schema.h"
 #include "aos/flatbuffers/test_static.h"
@@ -1042,4 +1043,55 @@
   TestMemory(builder.buffer());
 }
 
+// Uses a small example to manually verify that we can copy from the flatbuffer
+// object API.
+TEST_F(StaticFlatbuffersTest, ObjectApiCopy) {
+  aos::fbs::testing::TestTableT object_t;
+  object_t.scalar = 971;
+  object_t.vector_of_strings.push_back("971");
+  object_t.vector_of_structs.push_back({1, 2});
+  object_t.subtable = std::make_unique<SubTableT>();
+  aos::fbs::VectorAllocator allocator;
+  Builder<TestTableStatic> builder(&allocator);
+  ASSERT_TRUE(builder->FromFlatbuffer(object_t));
+  ASSERT_TRUE(builder.AsFlatbufferSpan().Verify());
+  // Note that vectors and strings get set to zero-length, but present, values.
+  EXPECT_EQ(
+      "{ \"scalar\": 971, \"vector_of_scalars\": [  ], \"string\": \"\", "
+      "\"vector_of_strings\": [ \"971\" ], \"subtable\": { \"foo\": 0, "
+      "\"baz\": 0.0 }, \"vector_aligned\": [  ], \"vector_of_structs\": [ { "
+      "\"x\": 1.0, \"y\": 2.0 } ], \"vector_of_tables\": [  ], "
+      "\"unspecified_length_vector\": [  ], \"unspecified_length_string\": "
+      "\"\", \"unspecified_length_vector_of_strings\": [  ] }",
+      aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
+}
+
+// More completely covers our object API copying by comparing the flatbuffer
+// Pack() methods to our FromFlatbuffer() methods.
+TEST_F(StaticFlatbuffersTest, FlatbufferObjectTypeCoverage) {
+  VerifyJson<aos::testing::ConfigurationStatic>("{\n\n}");
+  std::string populated_config =
+      aos::util::ReadFileToStringOrDie(aos::testing::ArtifactPath(
+          "aos/flatbuffers/test_dir/type_coverage.json"));
+  Builder<aos::testing::ConfigurationStatic> json_builder =
+      aos::JsonToStaticFlatbuffer<aos::testing::ConfigurationStatic>(
+          populated_config);
+  aos::testing::ConfigurationT object_t;
+  json_builder->AsFlatbuffer().UnPackTo(&object_t);
+
+  Builder<aos::testing::ConfigurationStatic> from_object_static;
+  ASSERT_TRUE(from_object_static->FromFlatbuffer(object_t));
+  flatbuffers::FlatBufferBuilder fbb;
+  fbb.Finish(aos::testing::Configuration::Pack(fbb, &object_t));
+  aos::FlatbufferDetachedBuffer<aos::testing::Configuration> from_object_raw =
+      fbb.Release();
+  EXPECT_EQ(aos::FlatbufferToJson(from_object_raw, {.multi_line = true}),
+            aos::FlatbufferToJson(from_object_static, {.multi_line = true}));
+}
+
+// Tests that we can build code that uses the reflection types.
+TEST_F(StaticFlatbuffersTest, IncludeReflectionTypes) {
+  VerifyJson<::aos::testing::UseSchemaStatic>("{\n\n}");
+}
+
 }  // namespace aos::fbs::testing
diff --git a/aos/flatbuffers/static_vector.h b/aos/flatbuffers/static_vector.h
index 7349a8d..6133075 100644
--- a/aos/flatbuffers/static_vector.h
+++ b/aos/flatbuffers/static_vector.h
@@ -203,6 +203,11 @@
       typename internal::InlineWrapper<T, kInline>::FlatbufferType;
   using ConstFlatbufferType =
       typename internal::InlineWrapper<T, kInline>::ConstFlatbufferType;
+  // FlatbufferObjectType corresponds to the type used by the flatbuffer
+  // "object" API (i.e. the FlatbufferT types).
+  // This type will be something unintelligble for inline types.
+  using FlatbufferObjectType =
+      typename internal::InlineWrapper<T, kInline>::FlatbufferObjectType;
   // flatbuffers::Vector type that corresponds to this Vector.
   typedef flatbuffers::Vector<FlatbufferType> Flatbuffer;
   typedef const flatbuffers::Vector<ConstFlatbufferType> ConstFlatbuffer;
@@ -329,7 +334,70 @@
   // we can allocate through reserve()).
   // This is a deep copy, and will call FromFlatbuffer on any constituent
   // objects.
-  [[nodiscard]] bool FromFlatbuffer(ConstFlatbuffer *vector);
+  [[nodiscard]] bool FromFlatbuffer(ConstFlatbuffer *vector) {
+    return FromFlatbuffer(*CHECK_NOTNULL(vector));
+  }
+  [[nodiscard]] bool FromFlatbuffer(ConstFlatbuffer &vector);
+  // The remaining FromFlatbuffer() overloads are for when using the flatbuffer
+  // "object" API, which uses std::vector's for representing vectors.
+  [[nodiscard]] bool FromFlatbuffer(const std::vector<InlineType> &vector) {
+    static_assert(kInline);
+    return FromData(vector.data(), vector.size());
+  }
+  // Overload for vectors of bools, since the standard library may not use a
+  // full byte per vector element.
+  [[nodiscard]] bool FromFlatbuffer(const std::vector<bool> &vector) {
+    static_assert(kInline);
+    // We won't be able to do a clean memcpy because std::vector<bool> may be
+    // implemented using bit-packing.
+    return FromIterator(vector.cbegin(), vector.cend());
+  }
+  // Overload for non-inline types. Note that to avoid having this overload get
+  // resolved with inline types, we make FlatbufferObjectType != InlineType.
+  [[nodiscard]] bool FromFlatbuffer(
+      const std::vector<FlatbufferObjectType> &vector) {
+    static_assert(!kInline);
+    return FromNotInlineIterable(vector);
+  }
+
+  // Copies values from the provided data pointer into the vector, resizing the
+  // vector as needed to match. Returns false on failure (e.g., if the
+  // underlying allocator has insufficient space to perform the copy). Only
+  // works for inline data types.
+  [[nodiscard]] bool FromData(const InlineType *input_data, size_t input_size) {
+    static_assert(kInline);
+    if (!reserve(input_size)) {
+      return false;
+    }
+
+    // We will be overwriting the whole vector very shortly; there is no need to
+    // clear the buffer to zero.
+    resize_inline(input_size, SetZero::kNo);
+
+    memcpy(inline_data(), input_data, size() * sizeof(InlineType));
+    return true;
+  }
+
+  // Copies values from the provided iterators into the vector, resizing the
+  // vector as needed to match. Returns false on failure (e.g., if the
+  // underlying allocator has insufficient space to perform the copy). Only
+  // works for inline data types.
+  // Does not attempt any optimizations if the iterators meet the
+  // std::contiguous_iterator concept; instead, it simply copies each element
+  // out one-by-one.
+  template <typename Iterator>
+  [[nodiscard]] bool FromIterator(Iterator begin, Iterator end) {
+    static_assert(kInline);
+    resize(0);
+    for (Iterator it = begin; it != end; ++it) {
+      if (!reserve(size() + 1)) {
+        return false;
+      }
+      // Should never fail, due to the reserve() above.
+      CHECK(emplace_back(*it));
+    }
+    return true;
+  }
 
   // Returns the element at the provided index. index must be less than size().
   const T &at(size_t index) const {
@@ -569,29 +637,22 @@
   }
   // Implementation that handles copying from a flatbuffers::Vector of an inline
   // data type.
-  [[nodiscard]] bool FromInlineFlatbuffer(ConstFlatbuffer *vector) {
-    if (!reserve(CHECK_NOTNULL(vector)->size())) {
-      return false;
-    }
-
-    // We will be overwriting the whole vector very shortly; there is no need to
-    // clear the buffer to zero.
-    resize_inline(vector->size(), SetZero::kNo);
-
-    memcpy(inline_data(), vector->Data(), size() * sizeof(InlineType));
-    return true;
+  [[nodiscard]] bool FromInlineFlatbuffer(ConstFlatbuffer &vector) {
+    return FromData(reinterpret_cast<const InlineType *>(vector.Data()),
+                    vector.size());
   }
 
   // Implementation that handles copying from a flatbuffers::Vector of a
   // not-inline data type.
-  [[nodiscard]] bool FromNotInlineFlatbuffer(const Flatbuffer *vector) {
-    if (!reserve(vector->size())) {
+  template <typename Iterable>
+  [[nodiscard]] bool FromNotInlineIterable(const Iterable &vector) {
+    if (!reserve(vector.size())) {
       return false;
     }
     // "Clear" the vector.
     resize_not_inline(0);
 
-    for (const typename T::Flatbuffer *entry : *vector) {
+    for (const auto &entry : vector) {
       if (!CHECK_NOTNULL(emplace_back())->FromFlatbuffer(entry)) {
         return false;
       }
@@ -599,6 +660,10 @@
     return true;
   }
 
+  [[nodiscard]] bool FromNotInlineFlatbuffer(const Flatbuffer &vector) {
+    return FromNotInlineIterable(vector);
+  }
+
   // In order to allow for easy partial template specialization, we use a
   // non-member class to call FromInline/FromNotInlineFlatbuffer and
   // resize_inline/resize_not_inline. There are not actually any great ways to
@@ -659,6 +724,7 @@
  public:
   typedef Vector<char, kStaticLength, true, 0, true> VectorType;
   typedef flatbuffers::String Flatbuffer;
+  typedef std::string FlatbufferObjectType;
   String(std::span<uint8_t> buffer, ResizeableObject *parent)
       : VectorType(buffer, parent) {}
   virtual ~String() {}
@@ -667,6 +733,10 @@
     VectorType::resize_inline(string.size(), SetZero::kNo);
     memcpy(VectorType::data(), string.data(), string.size());
   }
+  using VectorType::FromFlatbuffer;
+  [[nodiscard]] bool FromFlatbuffer(const std::string &string) {
+    return VectorType::FromData(string.data(), string.size());
+  }
   std::string_view string_view() const {
     return std::string_view(VectorType::data(), VectorType::size());
   }
@@ -690,12 +760,13 @@
   typedef T ObjectType;
   typedef flatbuffers::Offset<typename T::Flatbuffer> FlatbufferType;
   typedef flatbuffers::Offset<typename T::Flatbuffer> ConstFlatbufferType;
+  typedef T::FlatbufferObjectType FlatbufferObjectType;
   static_assert((T::kSize % T::kAlign) == 0);
   static constexpr size_t kDataAlign = T::kAlign;
   static constexpr size_t kDataSize = T::kSize;
   template <typename StaticVector>
   static bool FromFlatbuffer(
-      StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
+      StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
     return to->FromNotInlineFlatbuffer(from);
   }
   template <typename StaticVector>
@@ -712,11 +783,12 @@
   typedef T ObjectType;
   typedef T FlatbufferType;
   typedef T ConstFlatbufferType;
+  typedef T *FlatbufferObjectType;
   static constexpr size_t kDataAlign = alignof(T);
   static constexpr size_t kDataSize = sizeof(T);
   template <typename StaticVector>
   static bool FromFlatbuffer(
-      StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
+      StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
     return to->FromInlineFlatbuffer(from);
   }
   template <typename StaticVector>
@@ -731,11 +803,12 @@
   typedef uint8_t ObjectType;
   typedef uint8_t FlatbufferType;
   typedef uint8_t ConstFlatbufferType;
+  typedef uint8_t *FlatbufferObjectType;
   static constexpr size_t kDataAlign = 1u;
   static constexpr size_t kDataSize = 1u;
   template <typename StaticVector>
   static bool FromFlatbuffer(
-      StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
+      StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
     return to->FromInlineFlatbuffer(from);
   }
   template <typename StaticVector>
@@ -753,11 +826,12 @@
   typedef T ObjectType;
   typedef T *FlatbufferType;
   typedef const T *ConstFlatbufferType;
+  typedef T *FlatbufferObjectType;
   static constexpr size_t kDataAlign = alignof(T);
   static constexpr size_t kDataSize = sizeof(T);
   template <typename StaticVector>
   static bool FromFlatbuffer(
-      StaticVector *to, const typename StaticVector::ConstFlatbuffer *from) {
+      StaticVector *to, const typename StaticVector::ConstFlatbuffer &from) {
     return to->FromInlineFlatbuffer(from);
   }
   template <typename StaticVector>
@@ -770,7 +844,7 @@
 template <typename T, size_t kStaticLength, bool kInline, size_t kForceAlign,
           bool kNullTerminate>
 bool Vector<T, kStaticLength, kInline, kForceAlign,
-            kNullTerminate>::FromFlatbuffer(ConstFlatbuffer *vector) {
+            kNullTerminate>::FromFlatbuffer(ConstFlatbuffer &vector) {
   return internal::InlineWrapper<T, kInline>::FromFlatbuffer(this, vector);
 }
 
diff --git a/aos/flatbuffers/test_dir/BUILD b/aos/flatbuffers/test_dir/BUILD
index 76f5fb4..a6275a5 100644
--- a/aos/flatbuffers/test_dir/BUILD
+++ b/aos/flatbuffers/test_dir/BUILD
@@ -1,6 +1,13 @@
 load("//aos/flatbuffers:generate.bzl", "static_flatbuffer")
 
 static_flatbuffer(
+    name = "include_reflection_fbs",
+    srcs = ["include_reflection.fbs"],
+    visibility = ["//visibility:public"],
+    deps = ["//aos/flatbuffers/reflection:reflection_fbs"],
+)
+
+static_flatbuffer(
     name = "include_fbs",
     srcs = ["include.fbs"],
     visibility = ["//visibility:public"],
diff --git a/aos/flatbuffers/test_dir/include_reflection.fbs b/aos/flatbuffers/test_dir/include_reflection.fbs
new file mode 100644
index 0000000..7eda4f5
--- /dev/null
+++ b/aos/flatbuffers/test_dir/include_reflection.fbs
@@ -0,0 +1,9 @@
+include "reflection/reflection.fbs";
+
+namespace aos.testing;
+
+table UseSchema {
+  schema:reflection.Schema (id: 0);
+}
+
+root_type UseSchema;
diff --git a/aos/flatbuffers/test_dir/sample_test_static.h b/aos/flatbuffers/test_dir/sample_test_static.h
index a6362d7..57ac57a 100644
--- a/aos/flatbuffers/test_dir/sample_test_static.h
+++ b/aos/flatbuffers/test_dir/sample_test_static.h
@@ -14,6 +14,8 @@
  public:
   // The underlying "raw" flatbuffer type for this type.
   typedef aos::fbs::testing::MinimallyAlignedTable Flatbuffer;
+  typedef flatbuffers::unique_ptr<Flatbuffer::NativeTableType>
+      FlatbufferObjectType;
   // Returns this object as a flatbuffer type. This reference may not be valid
   // following mutations to the underlying flatbuffer, due to how memory may get
   // may get moved around.
@@ -135,15 +137,39 @@
   // returning true on success.
   // This is a deep copy, and will call FromFlatbuffer on any constituent
   // objects.
-  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer &other) {
     Clear();
 
-    if (other->has_field()) {
-      set_field(other->field());
+    if (other.has_field()) {
+      set_field(other.field());
     }
 
     return true;
   }
+  // Equivalent to FromFlatbuffer(const Flatbuffer&); this overload is provided
+  // to ease implementation of the aos::fbs::Vector internals.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+    return FromFlatbuffer(*CHECK_NOTNULL(other));
+  }
+
+  // Copies the contents of the provided flatbuffer into this flatbuffer,
+  // returning true on success.
+  // Because the Flatbuffer Object API does not provide any concept of an
+  // optionally populated scalar field, all scalar fields will be populated
+  // after a call to FromFlatbufferObject().
+  // This is a deep copy, and will call FromFlatbufferObject on
+  // any constituent objects.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer::NativeTableType &other) {
+    Clear();
+
+    set_field(other.field);
+
+    return true;
+  }
+  [[nodiscard]] bool FromFlatbuffer(
+      const flatbuffers::unique_ptr<Flatbuffer::NativeTableType> &other) {
+    return FromFlatbuffer(*other);
+  }
 
  private:
   // We need to provide a MoveConstructor to allow this table to be
@@ -168,6 +194,8 @@
  public:
   // The underlying "raw" flatbuffer type for this type.
   typedef aos::fbs::testing::SubTable Flatbuffer;
+  typedef flatbuffers::unique_ptr<Flatbuffer::NativeTableType>
+      FlatbufferObjectType;
   // Returns this object as a flatbuffer type. This reference may not be valid
   // following mutations to the underlying flatbuffer, due to how memory may get
   // may get moved around.
@@ -314,19 +342,45 @@
   // returning true on success.
   // This is a deep copy, and will call FromFlatbuffer on any constituent
   // objects.
-  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer &other) {
     Clear();
 
-    if (other->has_baz()) {
-      set_baz(other->baz());
+    if (other.has_baz()) {
+      set_baz(other.baz());
     }
 
-    if (other->has_foo()) {
-      set_foo(other->foo());
+    if (other.has_foo()) {
+      set_foo(other.foo());
     }
 
     return true;
   }
+  // Equivalent to FromFlatbuffer(const Flatbuffer&); this overload is provided
+  // to ease implementation of the aos::fbs::Vector internals.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+    return FromFlatbuffer(*CHECK_NOTNULL(other));
+  }
+
+  // Copies the contents of the provided flatbuffer into this flatbuffer,
+  // returning true on success.
+  // Because the Flatbuffer Object API does not provide any concept of an
+  // optionally populated scalar field, all scalar fields will be populated
+  // after a call to FromFlatbufferObject().
+  // This is a deep copy, and will call FromFlatbufferObject on
+  // any constituent objects.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer::NativeTableType &other) {
+    Clear();
+
+    set_baz(other.baz);
+
+    set_foo(other.foo);
+
+    return true;
+  }
+  [[nodiscard]] bool FromFlatbuffer(
+      const flatbuffers::unique_ptr<Flatbuffer::NativeTableType> &other) {
+    return FromFlatbuffer(*other);
+  }
 
  private:
   // We need to provide a MoveConstructor to allow this table to be
@@ -354,6 +408,8 @@
  public:
   // The underlying "raw" flatbuffer type for this type.
   typedef aos::fbs::testing::TestTable Flatbuffer;
+  typedef flatbuffers::unique_ptr<Flatbuffer::NativeTableType>
+      FlatbufferObjectType;
   // Returns this object as a flatbuffer type. This reference may not be valid
   // following mutations to the underlying flatbuffer, due to how memory may get
   // may get moved around.
@@ -1060,109 +1116,108 @@
   // returning true on success.
   // This is a deep copy, and will call FromFlatbuffer on any constituent
   // objects.
-  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer &other) {
     Clear();
 
-    if (other->has_included_table()) {
+    if (other.has_included_table()) {
       if (!CHECK_NOTNULL(add_included_table())
-               ->FromFlatbuffer(other->included_table())) {
+               ->FromFlatbuffer(other.included_table())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_scalar()) {
-      set_scalar(other->scalar());
+    if (other.has_scalar()) {
+      set_scalar(other.scalar());
     }
 
-    if (other->has_string()) {
-      if (!CHECK_NOTNULL(add_string())->FromFlatbuffer(other->string())) {
+    if (other.has_string()) {
+      if (!CHECK_NOTNULL(add_string())->FromFlatbuffer(other.string())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_substruct()) {
-      set_substruct(*other->substruct());
+    if (other.has_substruct()) {
+      set_substruct(*other.substruct());
     }
 
-    if (other->has_subtable()) {
-      if (!CHECK_NOTNULL(add_subtable())->FromFlatbuffer(other->subtable())) {
+    if (other.has_subtable()) {
+      if (!CHECK_NOTNULL(add_subtable())->FromFlatbuffer(other.subtable())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_unspecified_length_string()) {
+    if (other.has_unspecified_length_string()) {
       if (!CHECK_NOTNULL(add_unspecified_length_string())
-               ->FromFlatbuffer(other->unspecified_length_string())) {
+               ->FromFlatbuffer(other.unspecified_length_string())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_unspecified_length_vector()) {
+    if (other.has_unspecified_length_vector()) {
       if (!CHECK_NOTNULL(add_unspecified_length_vector())
-               ->FromFlatbuffer(other->unspecified_length_vector())) {
+               ->FromFlatbuffer(other.unspecified_length_vector())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_unspecified_length_vector_of_strings()) {
+    if (other.has_unspecified_length_vector_of_strings()) {
       if (!CHECK_NOTNULL(add_unspecified_length_vector_of_strings())
-               ->FromFlatbuffer(
-                   other->unspecified_length_vector_of_strings())) {
+               ->FromFlatbuffer(other.unspecified_length_vector_of_strings())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_vector_aligned()) {
+    if (other.has_vector_aligned()) {
       if (!CHECK_NOTNULL(add_vector_aligned())
-               ->FromFlatbuffer(other->vector_aligned())) {
+               ->FromFlatbuffer(other.vector_aligned())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_vector_of_scalars()) {
+    if (other.has_vector_of_scalars()) {
       if (!CHECK_NOTNULL(add_vector_of_scalars())
-               ->FromFlatbuffer(other->vector_of_scalars())) {
+               ->FromFlatbuffer(other.vector_of_scalars())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_vector_of_strings()) {
+    if (other.has_vector_of_strings()) {
       if (!CHECK_NOTNULL(add_vector_of_strings())
-               ->FromFlatbuffer(other->vector_of_strings())) {
+               ->FromFlatbuffer(other.vector_of_strings())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_vector_of_structs()) {
+    if (other.has_vector_of_structs()) {
       if (!CHECK_NOTNULL(add_vector_of_structs())
-               ->FromFlatbuffer(other->vector_of_structs())) {
+               ->FromFlatbuffer(other.vector_of_structs())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
       }
     }
 
-    if (other->has_vector_of_tables()) {
+    if (other.has_vector_of_tables()) {
       if (!CHECK_NOTNULL(add_vector_of_tables())
-               ->FromFlatbuffer(other->vector_of_tables())) {
+               ->FromFlatbuffer(other.vector_of_tables())) {
         // Fail if we were unable to copy (e.g., if we tried to copy in a long
         // vector and do not have the space for it).
         return false;
@@ -1171,6 +1226,140 @@
 
     return true;
   }
+  // Equivalent to FromFlatbuffer(const Flatbuffer&); this overload is provided
+  // to ease implementation of the aos::fbs::Vector internals.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer *other) {
+    return FromFlatbuffer(*CHECK_NOTNULL(other));
+  }
+
+  // Copies the contents of the provided flatbuffer into this flatbuffer,
+  // returning true on success.
+  // Because the Flatbuffer Object API does not provide any concept of an
+  // optionally populated scalar field, all scalar fields will be populated
+  // after a call to FromFlatbufferObject().
+  // This is a deep copy, and will call FromFlatbufferObject on
+  // any constituent objects.
+  [[nodiscard]] bool FromFlatbuffer(const Flatbuffer::NativeTableType &other) {
+    Clear();
+
+    if (other.included_table) {
+      if (!CHECK_NOTNULL(add_included_table())
+               ->FromFlatbuffer(*other.included_table)) {
+        // Fail if we were unable to copy (e.g., if we tried to copy in a long
+        // vector and do not have the space for it).
+        return false;
+      }
+    }
+
+    set_scalar(other.scalar);
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_string())->FromFlatbuffer(other.string)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    if (other.substruct) {
+      set_substruct(*other.substruct);
+    }
+
+    if (other.subtable) {
+      if (!CHECK_NOTNULL(add_subtable())->FromFlatbuffer(*other.subtable)) {
+        // Fail if we were unable to copy (e.g., if we tried to copy in a long
+        // vector and do not have the space for it).
+        return false;
+      }
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_unspecified_length_string())
+             ->FromFlatbuffer(other.unspecified_length_string)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_unspecified_length_vector())
+             ->FromFlatbuffer(other.unspecified_length_vector)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_unspecified_length_vector_of_strings())
+             ->FromFlatbuffer(other.unspecified_length_vector_of_strings)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_vector_aligned())
+             ->FromFlatbuffer(other.vector_aligned)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_vector_of_scalars())
+             ->FromFlatbuffer(other.vector_of_scalars)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_vector_of_strings())
+             ->FromFlatbuffer(other.vector_of_strings)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_vector_of_structs())
+             ->FromFlatbuffer(other.vector_of_structs)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    // Unconditionally copy strings/vectors, even if it will just end up
+    // being 0-length (this maintains consistency with the flatbuffer Pack()
+    // behavior).
+    if (!CHECK_NOTNULL(add_vector_of_tables())
+             ->FromFlatbuffer(other.vector_of_tables)) {
+      // Fail if we were unable to copy (e.g., if we tried to copy in a long
+      // vector and do not have the space for it).
+      return false;
+    }
+
+    return true;
+  }
+  [[nodiscard]] bool FromFlatbuffer(
+      const flatbuffers::unique_ptr<Flatbuffer::NativeTableType> &other) {
+    return FromFlatbuffer(*other);
+  }
 
  private:
   // We need to provide a MoveConstructor to allow this table to be
diff --git a/aos/json_to_flatbuffer.h b/aos/json_to_flatbuffer.h
index 6ef3544..deafaa7 100644
--- a/aos/json_to_flatbuffer.h
+++ b/aos/json_to_flatbuffer.h
@@ -75,6 +75,12 @@
       Flatbuffer<T>::MiniReflectTypeTable(), json_options);
 }
 
+template <typename T, typename Enable = T::Flatbuffer>
+inline ::std::string FlatbufferToJson(const fbs::Builder<T> &flatbuffer,
+                                      JsonOptions json_options = {}) {
+  return FlatbufferToJson(flatbuffer.AsFlatbufferSpan(), json_options);
+}
+
 // Converts a flatbuffer::Table to JSON.
 template <typename T>
 typename std::enable_if<
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index 95210c0..38d519d 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -19,6 +19,7 @@
 DEFINE_uint32(queue_initialization_threads, 0,
               "Number of threads to spin up to initialize the queue.  0 means "
               "use the main thread.");
+DECLARE_bool(enable_ftrace);
 
 namespace aos::starter {
 
@@ -214,6 +215,11 @@
   if (info.ssi_signo == SIGCHLD) {
     // SIGCHLD messages can be collapsed if multiple are received, so all
     // applications must check their status.
+    if (FLAGS_enable_ftrace) {
+      ftrace_.FormatMessage("SIGCHLD");
+      ftrace_.TurnOffOrDie();
+    }
+
     for (auto iter = applications_.begin(); iter != applications_.end();) {
       if (iter->second.MaybeHandleSignal()) {
         iter = applications_.erase(iter);
diff --git a/aos/starter/starterd_lib.h b/aos/starter/starterd_lib.h
index 3779c84..1ffc782 100644
--- a/aos/starter/starterd_lib.h
+++ b/aos/starter/starterd_lib.h
@@ -77,6 +77,8 @@
   aos::PhasedLoopHandler *status_timer_;
   aos::TimerHandler *cleanup_timer_;
 
+  aos::Ftrace ftrace_;
+
   int status_count_ = 0;
   const int max_status_count_;
 
diff --git a/aos/time/time.cc b/aos/time/time.cc
index 6a25aa9..e75fe45 100644
--- a/aos/time/time.cc
+++ b/aos/time/time.cc
@@ -6,6 +6,7 @@
 #include <cstring>
 #include <ctime>
 #include <iomanip>
+#include <sstream>
 
 #ifdef __linux__
 
@@ -79,6 +80,18 @@
   return stream;
 }
 
+std::string ToString(const aos::monotonic_clock::time_point &now) {
+  std::ostringstream stream;
+  stream << now;
+  return stream.str();
+}
+
+std::string ToString(const aos::realtime_clock::time_point &now) {
+  std::ostringstream stream;
+  stream << now;
+  return stream.str();
+}
+
 #ifdef __linux__
 std::optional<monotonic_clock::time_point> monotonic_clock::FromString(
     const std::string_view now) {
@@ -170,6 +183,16 @@
           : std::chrono::duration_cast<std::chrono::seconds>(
                 now.time_since_epoch());
 
+  // We can run into some corner cases where the seconds value is large enough
+  // to cause the conversion to nanoseconds to overflow. That is undefined
+  // behaviour so we prevent it with this check here.
+  if (int64_t result;
+      __builtin_mul_overflow(seconds.count(), 1'000'000'000, &result)) {
+    stream << "(unrepresentable realtime " << now.time_since_epoch().count()
+           << ")";
+    return stream;
+  }
+
   std::time_t seconds_t = seconds.count();
   stream << std::put_time(localtime_r(&seconds_t, &tm), "%Y-%m-%d_%H-%M-%S.")
          << std::setfill('0') << std::setw(9)
diff --git a/aos/time/time.h b/aos/time/time.h
index 1a5cbd1..8462625 100644
--- a/aos/time/time.h
+++ b/aos/time/time.h
@@ -81,6 +81,9 @@
 std::ostream &operator<<(std::ostream &stream,
                          const aos::realtime_clock::time_point &now);
 
+std::string ToString(const aos::monotonic_clock::time_point &now);
+std::string ToString(const aos::realtime_clock::time_point &now);
+
 namespace time {
 #ifdef __linux__
 
diff --git a/aos/time/time_test.cc b/aos/time/time_test.cc
index 63a145b..97116b0 100644
--- a/aos/time/time_test.cc
+++ b/aos/time/time_test.cc
@@ -208,8 +208,9 @@
     std::stringstream s;
     s << t;
 
-    EXPECT_EQ(s.str(), "1677-09-21_00-12-43.145224192");
-    EXPECT_EQ(realtime_clock::FromString(s.str()).value(), t);
+    // min_time happens to be unrepresentable because of rounding and signed
+    // integer overflow.
+    EXPECT_EQ(s.str(), "(unrepresentable realtime -9223372036854775808)");
   }
 
   {
@@ -224,4 +225,12 @@
   }
 }
 
+// Test that ToString works for monotonic and realtime time points.
+TEST(TimeTest, ToStringTimePoints) {
+  EXPECT_EQ(ToString(realtime_clock::epoch() + std::chrono::hours(5 * 24) +
+                     std::chrono::seconds(11) + std::chrono::milliseconds(5)),
+            "1970-01-06_00-00-11.005000000");
+  EXPECT_EQ(ToString(monotonic_clock::min_time), "-9223372036.854775808sec");
+}
+
 }  // namespace aos::time::testing
diff --git a/aos/util/mcap_logger_test.cc b/aos/util/mcap_logger_test.cc
index c6febf9..3684e93 100644
--- a/aos/util/mcap_logger_test.cc
+++ b/aos/util/mcap_logger_test.cc
@@ -81,6 +81,20 @@
                     "values": {
                         "items": {
                             "properties": {
+                                "attributes": {
+                                    "items": {
+                                        "properties": {
+                                            "key": {
+                                                "type": "string"
+                                            },
+                                            "value": {
+                                                "type": "string"
+                                            }
+                                        },
+                                        "type": "object"
+                                    },
+                                    "type": "array"
+                                },
                                 "documentation": {
                                     "items": {
                                         "type": "string"
diff --git a/aos/uuid.h b/aos/uuid.h
index 8dd93d4..a2cf8ae 100644
--- a/aos/uuid.h
+++ b/aos/uuid.h
@@ -8,6 +8,7 @@
 
 #include "absl/types/span.h"
 #include "flatbuffers/flatbuffers.h"
+#include "glog/logging.h"
 
 namespace aos {
 
@@ -66,6 +67,11 @@
   flatbuffers::Offset<flatbuffers::Vector<uint8_t>> PackVector(
       flatbuffers::FlatBufferBuilder *fbb) const;
 
+  template <typename T>
+  void PackStaticVector(T *static_vector) const {
+    CHECK(static_vector->FromData(data_.data(), data_.size()));
+  }
+
   // Returns a human-readable string representing this UUID.
   //
   // This is done without any memory allocation, which means it's returned in a
diff --git a/documentation/aos/docs/flatbuffers.md b/documentation/aos/docs/flatbuffers.md
index 132348d..a57e465 100644
--- a/documentation/aos/docs/flatbuffers.md
+++ b/documentation/aos/docs/flatbuffers.md
@@ -157,6 +157,24 @@
 type using the regular generated flatbuffer API and a `FromFlatbuffer()` method
 which attempts to copy the specified flatbuffer into the current object.
 
+The `FromFlatbuffer()` method works on both the "raw" flatbuffer type, as well
+as on the [Flatbuffer Object
+API](https://flatbuffers.dev/flatbuffers_guide_use_cpp.html) (i.e. the
+`FlatbufferT` types). When copying
+flatbuffers from the object-based API, we apply the same semantics that that the
+`Pack()` method does in the raw flatbuffer type. Namely, all non-table fields
+will be set:
+
+ * Scalar fields are always populated, even if their value is equal to the
+   default.
+ * Vectors are set to zero-length vectors if there is no data in the vector.
+ * Strings are set to the empty string if there is no data in the string.
+
+These limitations are a consequence of how flatbuffers are represented in the
+object API, and is not an issue when copying from regular flatbuffer types.
+For copying from raw flatbuffer objects (which is what most existing code
+uses), these caveats do not apply, and there is no loss of information.
+
 ### Sample Usage
 
 The below example constructs a table of the above example `TestTable`:
@@ -436,10 +454,24 @@
   space allocated for the vector; returns false on failure (e.g., if you are in
   a fixed-size allocator that does not support increasing the size past a
   certain point).
-* `bool FromFlatbuffer(const flatbuffers::Vector<>*)`: Attempts to copy an
+* `bool FromFlatbuffer(const flatbuffers::Vector<>&)`: Attempts to copy an
   existing vector into this `Vector`. This may attempt to call `reserve()`
   if the new vector is longer than `capacity()`. If the copy fails for
   any reason, returns `false`.
+* `bool FromFlatbuffer(const std::vector<>&)`: Attempts to copy an
+  existing vector into this `Vector`. This may attempt to call `reserve()`
+  if the new vector is longer than `capacity()`. If the copy fails for
+  any reason, returns `false`. This is called "`FromFlatbuffer`" because
+  the [Flatbuffer Object
+  API](https://flatbuffers.dev/flatbuffers_guide_use_cpp.html) uses
+  `std::vector<>` to represent vectors.
+* `bool FromData(const T*, size_t)`: Attempts to copy a contiguous set of data
+  from the provided pointer. Only applies to inline types. This may attempt to
+  call `reserve()`, and if the call fails, it returns `false`.
+* `bool FromIterator(It begin, It end)`: Attempts to copy data from [begin, end)
+  into the vector. Does not assume that the data is stored contiguously in
+  memory. Only applies to inline types. This may attempt to
+  call `reserve()`, and if the call fails, it returns `false`.
 
 #### Managing Resizing of Vectors
 
diff --git a/third_party/flatbuffers/include/flatbuffers/flatbuffer_builder.h b/third_party/flatbuffers/include/flatbuffers/flatbuffer_builder.h
index efa4d89..6f9d7c8 100644
--- a/third_party/flatbuffers/include/flatbuffers/flatbuffer_builder.h
+++ b/third_party/flatbuffers/include/flatbuffers/flatbuffer_builder.h
@@ -787,7 +787,7 @@
   /// where the vector is stored.
   template<typename T>
   Offset<Vector<const T *>> CreateVectorOfStructs(const T *v, size_t len) {
-    StartVector(len * sizeof(T) / AlignOf<T>(), sizeof(T), AlignOf<T>());
+    StartVector(len, sizeof(T), AlignOf<T>());
     if (len > 0) {
       PushBytes(reinterpret_cast<const uint8_t *>(v), sizeof(T) * len);
     }
@@ -1211,7 +1211,7 @@
   // Allocates space for a vector of structures.
   // Must be completed with EndVectorOfStructs().
   template<typename T> T *StartVectorOfStructs(size_t vector_size) {
-    StartVector(vector_size * sizeof(T) / AlignOf<T>(), sizeof(T), AlignOf<T>());
+    StartVector(vector_size, sizeof(T), AlignOf<T>());
     return reinterpret_cast<T *>(buf_.make_space(vector_size * sizeof(T)));
   }
 
diff --git a/third_party/flatbuffers/include/flatbuffers/reflection_generated.h b/third_party/flatbuffers/include/flatbuffers/reflection_generated.h
index 6a99e66..b340086 100644
--- a/third_party/flatbuffers/include/flatbuffers/reflection_generated.h
+++ b/third_party/flatbuffers/include/flatbuffers/reflection_generated.h
@@ -523,6 +523,7 @@
   int64_t value = 0;
   flatbuffers::unique_ptr<reflection::TypeT> union_type{};
   std::vector<std::string> documentation{};
+  std::vector<flatbuffers::unique_ptr<reflection::KeyValueT>> attributes{};
   EnumValT() = default;
   EnumValT(const EnumValT &o);
   EnumValT(EnumValT&&) FLATBUFFERS_NOEXCEPT = default;
@@ -603,6 +604,9 @@
   const flatbuffers::Vector<flatbuffers::Offset<reflection::KeyValue>> *attributes() const {
     return GetPointer<const flatbuffers::Vector<flatbuffers::Offset<reflection::KeyValue>> *>(VT_ATTRIBUTES);
   }
+  flatbuffers::Vector<flatbuffers::Offset<reflection::KeyValue>> *mutable_attributes() {
+    return GetPointer<flatbuffers::Vector<flatbuffers::Offset<reflection::KeyValue>> *>(VT_ATTRIBUTES);
+  }
   void clear_attributes() {
     ClearField(VT_ATTRIBUTES);
   }
@@ -2462,7 +2466,8 @@
       (lhs.name == rhs.name) &&
       (lhs.value == rhs.value) &&
       ((lhs.union_type == rhs.union_type) || (lhs.union_type && rhs.union_type && *lhs.union_type == *rhs.union_type)) &&
-      (lhs.documentation == rhs.documentation);
+      (lhs.documentation == rhs.documentation) &&
+      (lhs.attributes.size() == rhs.attributes.size() && std::equal(lhs.attributes.cbegin(), lhs.attributes.cend(), rhs.attributes.cbegin(), [](flatbuffers::unique_ptr<reflection::KeyValueT> const &a, flatbuffers::unique_ptr<reflection::KeyValueT> const &b) { return (a == b) || (a && b && *a == *b); }));
 }
 
 inline bool operator!=(const EnumValT &lhs, const EnumValT &rhs) {
@@ -2475,6 +2480,8 @@
         value(o.value),
         union_type((o.union_type) ? new reflection::TypeT(*o.union_type) : nullptr),
         documentation(o.documentation) {
+  attributes.reserve(o.attributes.size());
+  for (const auto &attributes_ : o.attributes) { attributes.emplace_back((attributes_) ? new reflection::KeyValueT(*attributes_) : nullptr); }
 }
 
 inline EnumValT &EnumValT::operator=(EnumValT o) FLATBUFFERS_NOEXCEPT {
@@ -2482,6 +2489,7 @@
   std::swap(value, o.value);
   std::swap(union_type, o.union_type);
   std::swap(documentation, o.documentation);
+  std::swap(attributes, o.attributes);
   return *this;
 }
 
@@ -2498,6 +2506,7 @@
   { auto _e = value(); _o->value = _e; }
   { auto _e = union_type(); if (_e) { if(_o->union_type) { _e->UnPackTo(_o->union_type.get(), _resolver); } else { _o->union_type = flatbuffers::unique_ptr<reflection::TypeT>(_e->UnPack(_resolver)); } } else if (_o->union_type) { _o->union_type.reset(); } }
   { auto _e = documentation(); if (_e) { _o->documentation.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { _o->documentation[_i] = _e->Get(_i)->str(); } } else { _o->documentation.resize(0); } }
+  { auto _e = attributes(); if (_e) { _o->attributes.resize(_e->size()); for (flatbuffers::uoffset_t _i = 0; _i < _e->size(); _i++) { if(_o->attributes[_i]) { _e->Get(_i)->UnPackTo(_o->attributes[_i].get(), _resolver); } else { _o->attributes[_i] = flatbuffers::unique_ptr<reflection::KeyValueT>(_e->Get(_i)->UnPack(_resolver)); }; } } else { _o->attributes.resize(0); } }
 }
 
 inline flatbuffers::Offset<EnumVal> EnumVal::Pack(flatbuffers::FlatBufferBuilder &_fbb, const EnumValT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
@@ -2512,12 +2521,14 @@
   auto _value = _o->value;
   auto _union_type = _o->union_type ? CreateType(_fbb, _o->union_type.get(), _rehasher) : 0;
   auto _documentation = _fbb.CreateVectorOfStrings(_o->documentation);
+  auto _attributes = _fbb.CreateVector<flatbuffers::Offset<reflection::KeyValue>> (_o->attributes.size(), [](size_t i, _VectorArgs *__va) { return CreateKeyValue(*__va->__fbb, __va->__o->attributes[i].get(), __va->__rehasher); }, &_va );
   return reflection::CreateEnumVal(
       _fbb,
       _name,
       _value,
       _union_type,
-      _documentation);
+      _documentation,
+      _attributes);
 }
 
 
@@ -3211,21 +3222,24 @@
     { flatbuffers::ET_LONG, 0, -1 },
     { flatbuffers::ET_SEQUENCE, 0, 0 },
     { flatbuffers::ET_SEQUENCE, 0, 1 },
-    { flatbuffers::ET_STRING, 1, -1 }
+    { flatbuffers::ET_STRING, 1, -1 },
+    { flatbuffers::ET_SEQUENCE, 1, 2 }
   };
   static const flatbuffers::TypeFunction type_refs[] = {
     reflection::ObjectTypeTable,
-    reflection::TypeTypeTable
+    reflection::TypeTypeTable,
+    reflection::KeyValueTypeTable
   };
   static const char * const names[] = {
     "name",
     "value",
     "object",
     "union_type",
-    "documentation"
+    "documentation",
+    "attributes"
   };
   static const flatbuffers::TypeTable tt = {
-    flatbuffers::ST_TABLE, 5, type_codes, type_refs, nullptr, nullptr, names
+    flatbuffers::ST_TABLE, 6, type_codes, type_refs, nullptr, nullptr, names
   };
   return &tt;
 }