Merge "Put blob detection results in a struct"
diff --git a/.bazelrc b/.bazelrc
index c41b14b..1c6dbde 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -24,6 +24,7 @@
 build:k8 --platforms=//tools/platforms:linux_x86
 build:roborio --platforms=//tools/platforms:linux_roborio
 build:armv7 --platforms=//tools/platforms:linux_armv7
+build:arm64 --platforms=//tools/platforms:linux_arm64
 build:cortex-m4f --platforms=//tools/platforms:cortex_m4f
 build:rp2040 --platforms=//tools/platforms:rp2040
 
diff --git a/.gitignore b/.gitignore
index 8c6aa64..8eeac66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,10 @@
 # default. We don't want folks to check it in.
 /ldap.json
 
+# The scraping library uses looks for this config file by default,
+# you don't want to get that checked in
+/scouting_config.json
+
 # Hide vagrant's files that unfortunately make it into the source tree when you
 # run "vagrant up".
 /vm/.vagrant/
diff --git a/WORKSPACE b/WORKSPACE
index 199822b..1ea9cce 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -51,6 +51,10 @@
     python_gtk_debs = "files",
 )
 load(
+    "//debian:opencv_arm64.bzl",
+    opencv_arm64_debs = "files",
+)
+load(
     "//debian:opencv_armhf.bzl",
     opencv_armhf_debs = "files",
 )
@@ -104,6 +108,8 @@
 
 generate_repositories_for_debs(python_gtk_debs)
 
+generate_repositories_for_debs(opencv_arm64_debs)
+
 generate_repositories_for_debs(opencv_armhf_debs)
 
 generate_repositories_for_debs(opencv_amd64_debs)
@@ -139,6 +145,12 @@
     llvm_version = llvm_version,
 )
 
+llvm(
+    name = "llvm_aarch64",
+    distribution = "clang+llvm-%s-aarch64-linux-gnu.tar.xz" % llvm_version,
+    llvm_version = llvm_version,
+)
+
 llvm_conlyopts = [
     "-std=gnu99",
 ]
@@ -189,40 +201,49 @@
     conlyopts = {
         "linux-x86_64": llvm_conlyopts,
         "linux-armv7": llvm_conlyopts,
+        "linux-aarch64": llvm_conlyopts,
     },
     copts = {
         "linux-x86_64": llvm_copts,
         "linux-armv7": llvm_copts,
+        "linux-aarch64": llvm_copts,
     },
     cxxopts = {
         "linux-x86_64": llvm_cxxopts,
         "linux-armv7": llvm_cxxopts,
+        "linux-aarch64": llvm_cxxopts,
     },
     dbg_copts = {
         "linux-x86_64": llvm_dbg_copts,
         "linux-armv7": llvm_dbg_copts,
+        "linux-aarch64": llvm_dbg_copts,
     },
     fastbuild_copts = {
         "linux-x86_64": llvm_fastbuild_copts,
         "linux-armv7": llvm_fastbuild_copts,
+        "linux-aarch64": llvm_fastbuild_copts,
     },
     llvm_version = llvm_version,
     opt_copts = {
         "linux-x86_64": llvm_opt_copts,
         "linux-armv7": llvm_opt_copts,
+        "linux-aarch64": llvm_opt_copts,
     },
     standard_libraries = {
         "linux-x86_64": "libstdc++-10",
         "linux-armv7": "libstdc++-10",
+        "linux-aarch64": "libstdc++-10",
     },
     static_libstdcxx = False,
     sysroot = {
         "linux-x86_64": "@amd64_debian_sysroot//:sysroot_files",
         "linux-armv7": "@armhf_debian_rootfs//:sysroot_files",
+        "linux-aarch64": "@arm64_debian_rootfs//:sysroot_files",
     },
     target_toolchain_roots = {
         "linux-x86_64": "@llvm_k8//",
         "linux-armv7": "@llvm_armv7//",
+        "linux-aarch64": "@llvm_aarch64//",
     },
     toolchain_roots = {
         "linux-x86_64": "@llvm_k8//",
@@ -384,6 +405,17 @@
     url = "https://www.frc971.org/Build-Dependencies/2021-10-30-raspios-bullseye-armhf-lite_rootfs.tar.bz2",
 )
 
+# The main partition from https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-11-08/2021-10-30-raspios-bullseye-armhf-lite.zip.sig
+# The following files and folders are removed to make bazel happy with it:
+#   usr/share/ca-certificates
+#   lib/systemd/system/system-systemd\\x2dcryptsetup.slice
+http_archive(
+    name = "arm64_debian_rootfs",
+    build_file = "@//:compilers/debian_rootfs.BUILD",
+    sha256 = "7e6ad432fec0a36f8b66c3fc2ab8795ea446e61f7dce7a206b55602677cf0904",
+    url = "https://www.frc971.org/Build-Dependencies/2021-10-30-raspios-bullseye-arm64-lite_rootfs.tar.bz2",
+)
+
 # Created with:
 #   `debootstrap buster buster_sysroot`
 # and then chrooting in and running:
@@ -813,6 +845,7 @@
     extra_target_triples = [
         "arm-unknown-linux-gnueabi",
         "armv7-unknown-linux-gnueabihf",
+        "aarch64-unknown-linux-gnu",
     ],
     version = "1.56.1",
 )
@@ -856,6 +889,14 @@
     url = "https://www.frc971.org/Build-Dependencies/2021-10-03_superstructure_shoot_balls.tar.gz",
 )
 
+# OpenCV arm64 (for raspberry pi)
+http_archive(
+    name = "opencv_arm64",
+    build_file = "@//debian:opencv.BUILD",
+    sha256 = "d284fae46ca710cf24c81ff7ace34929773466bff38f365a80371bea3b36a2ed",
+    url = "https://www.frc971.org/Build-Dependencies/opencv_arm64.tar.gz",
+)
+
 # OpenCV armhf (for raspberry pi)
 http_archive(
     name = "opencv_armhf",
@@ -883,6 +924,19 @@
 )
 
 # Downloaded from:
+# https://github.com/halide/Halide/releases/download/v8.0.0/halide-arm64-linux-64-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz
+# which is "Halide 8.0.0" at https://github.com/halide/Halide/releases.
+# The "2019/08/27" release was renamed as per the release notes:
+# https://github.com/halide/Halide/releases/tag/v8.0.0
+http_archive(
+    name = "halide_arm64",
+    build_file = "@//debian:halide.BUILD",
+    sha256 = "97b3e54565cd9df52abdd6452f3720ffd38861524154d74ae3d20dc949ed2a63",
+    strip_prefix = "halide/",
+    url = "https://www.frc971.org/Build-Dependencies/halide-arm64-linux-64-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz",
+)
+
+# Downloaded from:
 # https://github.com/halide/Halide/releases/download/release_2019_08_27/halide-arm32-linux-32-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz
 # which is "Halide 2019/08/27" at https://github.com/halide/Halide/releases.
 http_archive(
diff --git a/aos/actions/actor.h b/aos/actions/actor.h
index 33e70f2..b906059 100644
--- a/aos/actions/actor.h
+++ b/aos/actions/actor.h
@@ -90,7 +90,7 @@
 
   ::aos::EventLoop *event_loop() { return event_loop_; }
 
-  ::aos::monotonic_clock::time_point monotonic_now() {
+  ::aos::monotonic_clock::time_point monotonic_now() const {
     return event_loop_->monotonic_now();
   }
 
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index c81fa1e..11368ae 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -542,8 +542,8 @@
   virtual ~EventLoop();
 
   // Current time.
-  virtual monotonic_clock::time_point monotonic_now() = 0;
-  virtual realtime_clock::time_point realtime_now() = 0;
+  virtual monotonic_clock::time_point monotonic_now() const = 0;
+  virtual realtime_clock::time_point realtime_now() const = 0;
 
   template <typename T>
   const Channel *GetChannel(const std::string_view channel_name) {
diff --git a/aos/events/logging/BUILD b/aos/events/logging/BUILD
index 6d28343..d190120 100644
--- a/aos/events/logging/BUILD
+++ b/aos/events/logging/BUILD
@@ -54,7 +54,7 @@
         "@boringssl//:crypto",
     ] + select({
         "//tools:cpu_k8": [":lzma_encoder"],
-        "//tools:cpu_aarch64": [":lzma_encoder"],
+        "//tools:cpu_arm64": [":lzma_encoder"],
         "//conditions:default": [],
     }),
 )
@@ -421,7 +421,7 @@
     srcs = ["logger_test.cc"],
     copts = select({
         "//tools:cpu_k8": ["-DLZMA=1"],
-        "//tools:cpu_aarch64": ["-DLZMA=1"],
+        "//tools:cpu_arm64": ["-DLZMA=1"],
         "//conditions:default": [],
     }),
     data = [
diff --git a/aos/events/logging/log_namer.cc b/aos/events/logging/log_namer.cc
index b41212c..f269f43 100644
--- a/aos/events/logging/log_namer.cc
+++ b/aos/events/logging/log_namer.cc
@@ -18,10 +18,13 @@
 namespace logger {
 
 NewDataWriter::NewDataWriter(LogNamer *log_namer, const Node *node,
+                             const Node *logger_node,
                              std::function<void(NewDataWriter *)> reopen,
                              std::function<void(NewDataWriter *)> close)
     : node_(node),
       node_index_(configuration::GetNodeIndex(log_namer->configuration_, node)),
+      logger_node_index_(
+          configuration::GetNodeIndex(log_namer->configuration_, logger_node)),
       log_namer_(log_namer),
       reopen_(std::move(reopen)),
       close_(std::move(close)) {
@@ -59,6 +62,13 @@
         monotonic_clock::max_time;
     state.oldest_local_unreliable_monotonic_timestamp =
         monotonic_clock::max_time;
+    state.oldest_remote_reliable_monotonic_timestamp =
+        monotonic_clock::max_time;
+    state.oldest_local_reliable_monotonic_timestamp = monotonic_clock::max_time;
+    state.oldest_logger_remote_unreliable_monotonic_timestamp =
+        monotonic_clock::max_time;
+    state.oldest_logger_local_unreliable_monotonic_timestamp =
+        monotonic_clock::max_time;
   }
 
   state_[node_index_].boot_uuid = source_node_boot_uuid;
@@ -78,8 +88,8 @@
 void NewDataWriter::UpdateRemote(
     const size_t remote_node_index, const UUID &remote_node_boot_uuid,
     const monotonic_clock::time_point monotonic_remote_time,
-    const monotonic_clock::time_point monotonic_event_time,
-    const bool reliable) {
+    const monotonic_clock::time_point monotonic_event_time, const bool reliable,
+    monotonic_clock::time_point monotonic_timestamp_time) {
   // Trigger rotation if anything in the header changes.
   bool rotate = false;
   CHECK_LT(remote_node_index, state_.size());
@@ -96,6 +106,13 @@
         monotonic_clock::max_time;
     state.oldest_local_unreliable_monotonic_timestamp =
         monotonic_clock::max_time;
+    state.oldest_remote_reliable_monotonic_timestamp =
+        monotonic_clock::max_time;
+    state.oldest_local_reliable_monotonic_timestamp = monotonic_clock::max_time;
+    state.oldest_logger_remote_unreliable_monotonic_timestamp =
+        monotonic_clock::max_time;
+    state.oldest_logger_local_unreliable_monotonic_timestamp =
+        monotonic_clock::max_time;
     rotate = true;
   }
 
@@ -112,6 +129,38 @@
       state.oldest_local_unreliable_monotonic_timestamp = monotonic_event_time;
       rotate = true;
     }
+  } else {
+    if (state.oldest_remote_reliable_monotonic_timestamp >
+        monotonic_remote_time) {
+      VLOG(1) << filename() << " Remote " << remote_node_index
+              << " oldest_remote_reliable_monotonic_timestamp updated from "
+              << state.oldest_remote_reliable_monotonic_timestamp << " to "
+              << monotonic_remote_time;
+      state.oldest_remote_reliable_monotonic_timestamp = monotonic_remote_time;
+      state.oldest_local_reliable_monotonic_timestamp = monotonic_event_time;
+      rotate = true;
+    }
+  }
+
+  // Track the logger timestamps too.
+  if (monotonic_timestamp_time != monotonic_clock::min_time) {
+    State &logger_state = state_[node_index_];
+    CHECK_EQ(remote_node_index, logger_node_index_);
+    if (monotonic_event_time <
+        logger_state.oldest_logger_remote_unreliable_monotonic_timestamp) {
+      VLOG(1)
+          << filename() << " Remote " << node_index_
+          << " oldest_logger_remote_unreliable_monotonic_timestamp updated "
+             "from "
+          << logger_state.oldest_logger_remote_unreliable_monotonic_timestamp
+          << " to " << monotonic_event_time;
+      logger_state.oldest_logger_remote_unreliable_monotonic_timestamp =
+          monotonic_event_time;
+      logger_state.oldest_logger_local_unreliable_monotonic_timestamp =
+          monotonic_timestamp_time;
+
+      rotate = true;
+    }
   }
 
   // Did any of the timestamps change?
@@ -219,7 +268,7 @@
   const UUID &source_node_boot_uuid = state[node_index].boot_uuid;
   const Node *const source_node =
       configuration::GetNode(configuration_, node_index);
-  CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 28u);
+  CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 32u);
   flatbuffers::FlatBufferBuilder fbb;
   fbb.ForceDefaults(true);
 
@@ -279,22 +328,36 @@
 
   int64_t *unused;
   flatbuffers::Offset<flatbuffers::Vector<int64_t>>
-      oldest_remote_monotonic_timestamps_offset = fbb.CreateUninitializedVector(
-          state.size(), &unused);
+      oldest_remote_monotonic_timestamps_offset =
+          fbb.CreateUninitializedVector(state.size(), &unused);
 
   flatbuffers::Offset<flatbuffers::Vector<int64_t>>
-      oldest_local_monotonic_timestamps_offset = fbb.CreateUninitializedVector(
-          state.size(), &unused);
+      oldest_local_monotonic_timestamps_offset =
+          fbb.CreateUninitializedVector(state.size(), &unused);
 
   flatbuffers::Offset<flatbuffers::Vector<int64_t>>
       oldest_remote_unreliable_monotonic_timestamps_offset =
-          fbb.CreateUninitializedVector(
-              state.size(), &unused);
+          fbb.CreateUninitializedVector(state.size(), &unused);
 
   flatbuffers::Offset<flatbuffers::Vector<int64_t>>
       oldest_local_unreliable_monotonic_timestamps_offset =
-          fbb.CreateUninitializedVector(
-              state.size(), &unused);
+          fbb.CreateUninitializedVector(state.size(), &unused);
+
+  flatbuffers::Offset<flatbuffers::Vector<int64_t>>
+      oldest_remote_reliable_monotonic_timestamps_offset =
+          fbb.CreateUninitializedVector(state.size(), &unused);
+
+  flatbuffers::Offset<flatbuffers::Vector<int64_t>>
+      oldest_local_reliable_monotonic_timestamps_offset =
+          fbb.CreateUninitializedVector(state.size(), &unused);
+
+  flatbuffers::Offset<flatbuffers::Vector<int64_t>>
+      oldest_logger_remote_unreliable_monotonic_timestamps_offset =
+          fbb.CreateUninitializedVector(state.size(), &unused);
+
+  flatbuffers::Offset<flatbuffers::Vector<int64_t>>
+      oldest_logger_local_unreliable_monotonic_timestamps_offset =
+          fbb.CreateUninitializedVector(state.size(), &unused);
 
   for (size_t i = 0; i < state.size(); ++i) {
     if (state[i].boot_uuid != UUID::Zero()) {
@@ -311,6 +374,14 @@
                monotonic_clock::max_time);
       CHECK_EQ(state[i].oldest_local_unreliable_monotonic_timestamp,
                monotonic_clock::max_time);
+      CHECK_EQ(state[i].oldest_remote_reliable_monotonic_timestamp,
+               monotonic_clock::max_time);
+      CHECK_EQ(state[i].oldest_local_reliable_monotonic_timestamp,
+               monotonic_clock::max_time);
+      CHECK_EQ(state[i].oldest_logger_remote_unreliable_monotonic_timestamp,
+               monotonic_clock::max_time);
+      CHECK_EQ(state[i].oldest_logger_local_unreliable_monotonic_timestamp,
+               monotonic_clock::max_time);
     }
 
     flatbuffers::GetMutableTemporaryPointer(
@@ -326,12 +397,40 @@
     flatbuffers::GetMutableTemporaryPointer(
         fbb, oldest_remote_unreliable_monotonic_timestamps_offset)
         ->Mutate(i, state[i]
-                        .oldest_remote_unreliable_monotonic_timestamp.time_since_epoch()
+                        .oldest_remote_unreliable_monotonic_timestamp
+                        .time_since_epoch()
                         .count());
     flatbuffers::GetMutableTemporaryPointer(
         fbb, oldest_local_unreliable_monotonic_timestamps_offset)
         ->Mutate(i, state[i]
-                        .oldest_local_unreliable_monotonic_timestamp.time_since_epoch()
+                        .oldest_local_unreliable_monotonic_timestamp
+                        .time_since_epoch()
+                        .count());
+
+    flatbuffers::GetMutableTemporaryPointer(
+        fbb, oldest_remote_reliable_monotonic_timestamps_offset)
+        ->Mutate(i, state[i]
+                        .oldest_remote_reliable_monotonic_timestamp
+                        .time_since_epoch()
+                        .count());
+    flatbuffers::GetMutableTemporaryPointer(
+        fbb, oldest_local_reliable_monotonic_timestamps_offset)
+        ->Mutate(
+            i, state[i]
+                   .oldest_local_reliable_monotonic_timestamp.time_since_epoch()
+                   .count());
+
+    flatbuffers::GetMutableTemporaryPointer(
+        fbb, oldest_logger_remote_unreliable_monotonic_timestamps_offset)
+        ->Mutate(i, state[i]
+                        .oldest_logger_remote_unreliable_monotonic_timestamp
+                        .time_since_epoch()
+                        .count());
+    flatbuffers::GetMutableTemporaryPointer(
+        fbb, oldest_logger_local_unreliable_monotonic_timestamps_offset)
+        ->Mutate(i, state[i]
+                        .oldest_logger_local_unreliable_monotonic_timestamp
+                        .time_since_epoch()
                         .count());
   }
 
@@ -415,6 +514,16 @@
       oldest_remote_unreliable_monotonic_timestamps_offset);
   log_file_header_builder.add_oldest_local_unreliable_monotonic_timestamps(
       oldest_local_unreliable_monotonic_timestamps_offset);
+  log_file_header_builder.add_oldest_remote_reliable_monotonic_timestamps(
+      oldest_remote_reliable_monotonic_timestamps_offset);
+  log_file_header_builder.add_oldest_local_reliable_monotonic_timestamps(
+      oldest_local_reliable_monotonic_timestamps_offset);
+  log_file_header_builder
+      .add_oldest_logger_remote_unreliable_monotonic_timestamps(
+          oldest_logger_remote_unreliable_monotonic_timestamps_offset);
+  log_file_header_builder
+      .add_oldest_logger_local_unreliable_monotonic_timestamps(
+          oldest_logger_local_unreliable_monotonic_timestamps_offset);
   fbb.FinishSizePrefixed(log_file_header_builder.Finish());
   aos::SizePrefixedFlatbufferDetachedBuffer<LogFileHeader> result(
       fbb.Release());
@@ -556,13 +665,14 @@
     nodes_.emplace_back(source_node);
   }
 
-  NewDataWriter data_writer(this, source_node,
-                            [this, channel](NewDataWriter *data_writer) {
-                              OpenWriter(channel, data_writer);
-                            },
-                            [this](NewDataWriter *data_writer) {
-                              CloseWriter(&data_writer->writer);
-                            });
+  NewDataWriter data_writer(
+      this, source_node, node_,
+      [this, channel](NewDataWriter *data_writer) {
+        OpenWriter(channel, data_writer);
+      },
+      [this](NewDataWriter *data_writer) {
+        CloseWriter(&data_writer->writer);
+      });
   return &(
       data_writers_.emplace(channel, std::move(data_writer)).first->second);
 }
@@ -580,14 +690,14 @@
     nodes_.emplace_back(node);
   }
 
-  NewDataWriter data_writer(this, configuration::GetNode(configuration_, node),
-                            [this, channel](NewDataWriter *data_writer) {
-                              OpenForwardedTimestampWriter(channel,
-                                                           data_writer);
-                            },
-                            [this](NewDataWriter *data_writer) {
-                              CloseWriter(&data_writer->writer);
-                            });
+  NewDataWriter data_writer(
+      this, configuration::GetNode(configuration_, node), node_,
+      [this, channel](NewDataWriter *data_writer) {
+        OpenForwardedTimestampWriter(channel, data_writer);
+      },
+      [this](NewDataWriter *data_writer) {
+        CloseWriter(&data_writer->writer);
+      });
   return &(
       data_writers_.emplace(channel, std::move(data_writer)).first->second);
 }
@@ -652,7 +762,7 @@
 void MultiNodeLogNamer::OpenDataWriter() {
   if (!data_writer_) {
     data_writer_ = std::make_unique<NewDataWriter>(
-        this, node_,
+        this, node_, node_,
         [this](NewDataWriter *writer) {
           std::string name;
           if (node() != nullptr) {
diff --git a/aos/events/logging/log_namer.h b/aos/events/logging/log_namer.h
index c3fc5d4..4a1e74f 100644
--- a/aos/events/logging/log_namer.h
+++ b/aos/events/logging/log_namer.h
@@ -36,7 +36,7 @@
   // node is the node whom's prespective we are logging from.
   // reopen is called whenever a file needs to be reopened.
   // close is called to close that file and extract any statistics.
-  NewDataWriter(LogNamer *log_namer, const Node *node,
+  NewDataWriter(LogNamer *log_namer, const Node *node, const Node *logger_node,
                 std::function<void(NewDataWriter *)> reopen,
                 std::function<void(NewDataWriter *)> close);
 
@@ -50,10 +50,14 @@
   // Rotates the log file, delaying writing the new header until data arrives.
   void Rotate();
 
+  // Updates all the metadata in the log file about the remote node which this
+  // message is from.
   void UpdateRemote(size_t remote_node_index, const UUID &remote_node_boot_uuid,
                     monotonic_clock::time_point monotonic_remote_time,
                     monotonic_clock::time_point monotonic_event_time,
-                    bool reliable);
+                    bool reliable,
+                    monotonic_clock::time_point monotonic_timestamp_time =
+                        monotonic_clock::min_time);
   // Queues up a message with the provided boot UUID.
   void QueueMessage(flatbuffers::FlatBufferBuilder *fbb,
                     const UUID &node_boot_uuid,
@@ -98,6 +102,27 @@
     // oldest_local_unreliable_monotonic_timestamp.
     monotonic_clock::time_point oldest_local_unreliable_monotonic_timestamp =
         monotonic_clock::max_time;
+
+    // Timestamp on the remote monotonic clock of the oldest message sent to
+    // node_index_, only including messages forwarded with time_to_live() == 0.
+    monotonic_clock::time_point oldest_remote_reliable_monotonic_timestamp =
+        monotonic_clock::max_time;
+    // Timestamp on the local monotonic clock of the message in
+    // oldest_local_reliable_monotonic_timestamp.
+    monotonic_clock::time_point oldest_local_reliable_monotonic_timestamp =
+        monotonic_clock::max_time;
+
+    // Timestamp on the remote monotonic clock of the oldest message timestamp
+    // sent back to logger_node_index_.  The remote here will be the node this
+    // part is from the perspective of, ie node_index_.
+    monotonic_clock::time_point
+        oldest_logger_remote_unreliable_monotonic_timestamp =
+            monotonic_clock::max_time;
+    // The time on the monotonic clock of the logger when this timestamp made it
+    // back to the logger (logger_node_index_).
+    monotonic_clock::time_point
+        oldest_logger_local_unreliable_monotonic_timestamp =
+            monotonic_clock::max_time;
   };
 
  private:
@@ -113,6 +138,7 @@
 
   const Node *node_ = nullptr;
   size_t node_index_ = 0;
+  size_t logger_node_index_ = 0;
   LogNamer *log_namer_;
   UUID parts_uuid_ = UUID::Random();
   size_t parts_index_ = 0;
@@ -271,14 +297,15 @@
                 const aos::Node *node)
       : LogNamer(event_loop->configuration(), event_loop, node),
         base_name_(base_name),
-        data_writer_(this, node,
-                     [this](NewDataWriter *writer) {
-                       writer->writer = std::make_unique<DetachedBufferWriter>(
-                           absl::StrCat(base_name_, ".part",
-                                        writer->parts_index(), ".bfbs"),
-                           std::make_unique<aos::logger::DummyEncoder>());
-                     },
-                     [](NewDataWriter * /*writer*/) {}) {}
+        data_writer_(
+            this, node, event_loop->node(),
+            [this](NewDataWriter *writer) {
+              writer->writer = std::make_unique<DetachedBufferWriter>(
+                  absl::StrCat(base_name_, ".part", writer->parts_index(),
+                               ".bfbs"),
+                  std::make_unique<aos::logger::DummyEncoder>());
+            },
+            [](NewDataWriter * /*writer*/) {}) {}
 
   LocalLogNamer(const LocalLogNamer &) = delete;
   LocalLogNamer(LocalLogNamer &&) = delete;
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 63eba05..e58ccd2 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -292,8 +292,7 @@
       states_[configuration::GetNodeIndex(configuration(), node)].get();
   CHECK(state != nullptr) << ": Unknown node " << FlatbufferToJson(node);
 
-  // TODO(austin): Un-hard-code the 0 boot count.
-  return state->monotonic_start_time(0);
+  return state->monotonic_start_time(state->boot_count());
 }
 
 realtime_clock::time_point LogReader::realtime_start_time(
@@ -302,8 +301,7 @@
       states_[configuration::GetNodeIndex(configuration(), node)].get();
   CHECK(state != nullptr) << ": Unknown node " << FlatbufferToJson(node);
 
-  // TODO(austin): Un-hard-code the 0 boot count.
-  return state->realtime_start_time(0);
+  return state->realtime_start_time(state->boot_count());
 }
 
 void LogReader::OnStart(std::function<void()> fn) {
diff --git a/aos/events/logging/log_reader.h b/aos/events/logging/log_reader.h
index 5cccdeb..3eebba7 100644
--- a/aos/events/logging/log_reader.h
+++ b/aos/events/logging/log_reader.h
@@ -400,7 +400,7 @@
           ->node_event_loop_factory_->node();
     }
 
-    monotonic_clock::time_point monotonic_now() {
+    monotonic_clock::time_point monotonic_now() const {
       return node_event_loop_factory_->monotonic_now();
     }
 
diff --git a/aos/events/logging/log_writer.cc b/aos/events/logging/log_writer.cc
index 8eaeb73..0e1dece 100644
--- a/aos/events/logging/log_writer.cc
+++ b/aos/events/logging/log_writer.cc
@@ -36,7 +36,33 @@
   timer_handler_->set_name("channel_poll");
   VLOG(1) << "Creating logger for " << FlatbufferToJson(node_);
 
-  std::map<const Channel *, const Node *> timestamp_logger_channels;
+  // When we are logging remote timestamps, we need to be able to translate from
+  // the channel index that the event loop uses to the channel index in the
+  // config in the log file.
+  event_loop_to_logged_channel_index_.resize(
+      event_loop->configuration()->channels()->size(), -1);
+  for (size_t event_loop_channel_index = 0;
+       event_loop_channel_index <
+       event_loop->configuration()->channels()->size();
+       ++event_loop_channel_index) {
+    const Channel *event_loop_channel =
+        event_loop->configuration()->channels()->Get(event_loop_channel_index);
+
+    const Channel *logged_channel = aos::configuration::GetChannel(
+        configuration_, event_loop_channel->name()->string_view(),
+        event_loop_channel->type()->string_view(), "", node_);
+
+    if (logged_channel != nullptr) {
+      event_loop_to_logged_channel_index_[event_loop_channel_index] =
+          configuration::ChannelIndex(configuration_, logged_channel);
+    }
+  }
+
+  // Map to match source channels with the timestamp logger, if the contents
+  // should be reliable, and a list of all channels logged on it to be treated
+  // as reliable.
+  std::map<const Channel *, std::tuple<const Node *, bool, std::vector<bool>>>
+      timestamp_logger_channels;
 
   message_bridge::ChannelTimestampFinder finder(event_loop_);
   for (const Channel *channel : *event_loop_->configuration()->channels()) {
@@ -46,6 +72,9 @@
     if (!channel->has_destination_nodes()) {
       continue;
     }
+    const size_t channel_index =
+        configuration::ChannelIndex(event_loop_->configuration(), channel);
+
     for (const Connection *connection : *channel->destination_nodes()) {
       if (configuration::ConnectionDeliveryTimeIsLoggedOnNode(
               connection, event_loop_->node())) {
@@ -54,8 +83,37 @@
 
         VLOG(1) << "Timestamps are logged from "
                 << FlatbufferToJson(other_node);
-        timestamp_logger_channels.insert(
-            std::make_pair(finder.ForChannel(channel, connection), other_node));
+        // True if each channel's remote timestamps are split into a separate
+        // RemoteMessage channel.
+        const bool is_split =
+            finder.SplitChannelForChannel(channel, connection) != nullptr;
+
+        const Channel *const timestamp_logger_channel =
+            finder.ForChannel(channel, connection);
+
+        auto it = timestamp_logger_channels.find(timestamp_logger_channel);
+        if (it != timestamp_logger_channels.end()) {
+          CHECK(!is_split);
+          CHECK_LT(channel_index, std::get<2>(it->second).size());
+          std::get<2>(it->second)[channel_index] = (connection->time_to_live() == 0);
+        } else {
+          if (is_split) {
+            timestamp_logger_channels.insert(std::make_pair(
+                timestamp_logger_channel,
+                std::make_tuple(other_node, (connection->time_to_live() == 0),
+                                std::vector<bool>())));
+          } else {
+            std::vector<bool> channel_reliable_contents(
+                event_loop->configuration()->channels()->size(), false);
+            channel_reliable_contents[channel_index] =
+                (connection->time_to_live() == 0);
+
+            timestamp_logger_channels.insert(std::make_pair(
+                timestamp_logger_channel,
+                std::make_tuple(other_node, false,
+                                std::move(channel_reliable_contents))));
+          }
+        }
       }
     }
   }
@@ -86,8 +144,8 @@
 
     const bool is_readable =
         configuration::ChannelIsReadableOnNode(config_channel, node_);
-    const bool is_logged = configuration::ChannelMessageIsLoggedOnNode(
-        config_channel, node_);
+    const bool is_logged =
+        configuration::ChannelMessageIsLoggedOnNode(config_channel, node_);
     const bool log_message = is_logged && is_readable;
 
     bool log_delivery_times = false;
@@ -107,8 +165,8 @@
       }
     }
 
-    // Now, detect a RemoteMessage timestamp logger where we should just log the
-    // contents to a file directly.
+    // Now, detect a RemoteMessage timestamp logger where we should just log
+    // the contents to a file directly.
     const bool log_contents = timestamp_logger_channels.find(channel) !=
                               timestamp_logger_channels.end();
 
@@ -144,7 +202,14 @@
       if (log_contents) {
         VLOG(1) << "Timestamp logger channel "
                 << configuration::CleanedChannelToString(channel);
-        fs.timestamp_node = timestamp_logger_channels.find(channel)->second;
+        auto timestamp_logger_channel_info =
+            timestamp_logger_channels.find(channel);
+        CHECK(timestamp_logger_channel_info != timestamp_logger_channels.end());
+        fs.timestamp_node = std::get<0>(timestamp_logger_channel_info->second);
+        fs.reliable_contents =
+            std::get<1>(timestamp_logger_channel_info->second);
+        fs.channel_reliable_contents =
+            std::get<2>(timestamp_logger_channel_info->second);
         fs.wants_contents_writer = true;
         fs.contents_node_index =
             configuration::GetNodeIndex(configuration_, fs.timestamp_node);
@@ -152,36 +217,14 @@
       fetchers_.emplace_back(std::move(fs));
     }
   }
-
-  // When we are logging remote timestamps, we need to be able to translate from
-  // the channel index that the event loop uses to the channel index in the
-  // config in the log file.
-  event_loop_to_logged_channel_index_.resize(
-      event_loop->configuration()->channels()->size(), -1);
-  for (size_t event_loop_channel_index = 0;
-       event_loop_channel_index <
-       event_loop->configuration()->channels()->size();
-       ++event_loop_channel_index) {
-    const Channel *event_loop_channel =
-        event_loop->configuration()->channels()->Get(event_loop_channel_index);
-
-    const Channel *logged_channel = aos::configuration::GetChannel(
-        configuration_, event_loop_channel->name()->string_view(),
-        event_loop_channel->type()->string_view(), "", node_);
-
-    if (logged_channel != nullptr) {
-      event_loop_to_logged_channel_index_[event_loop_channel_index] =
-          configuration::ChannelIndex(configuration_, logged_channel);
-    }
-  }
 }
 
 Logger::~Logger() {
   if (log_namer_) {
     // If we are replaying a log file, or in simulation, we want to force the
     // last bit of data to be logged.  The easiest way to deal with this is to
-    // poll everything as we go to destroy the class, ie, shut down the logger,
-    // and write it to disk.
+    // poll everything as we go to destroy the class, ie, shut down the
+    // logger, and write it to disk.
     StopLogging(event_loop_->monotonic_now());
   }
 }
@@ -686,10 +729,10 @@
         message_header_builder.add_remote_queue_index(
             msg->remote_queue_index());
 
+        const aos::monotonic_clock::time_point monotonic_timestamp_time =
+            f.fetcher->context().monotonic_event_time;
         message_header_builder.add_monotonic_timestamp_time(
-            f.fetcher->context()
-                .monotonic_event_time.time_since_epoch()
-                .count());
+            monotonic_timestamp_time.time_since_epoch().count());
 
         fbb.FinishSizePrefixed(message_header_builder.Finish());
         const auto end = event_loop_->monotonic_now();
@@ -701,13 +744,18 @@
 
         // Start with recording info about the data flowing from our node to the
         // remote.
+        const bool reliable =
+            f.channel_reliable_contents.size() != 0u
+                ? f.channel_reliable_contents[msg->channel_index()]
+                : f.reliable_contents;
+
         f.contents_writer->UpdateRemote(
             node_index_, event_loop_->boot_uuid(),
             monotonic_clock::time_point(
                 chrono::nanoseconds(msg->monotonic_remote_time())),
             monotonic_clock::time_point(
                 chrono::nanoseconds(msg->monotonic_sent_time())),
-            f.reliable_forwarding);
+            reliable, monotonic_timestamp_time);
 
         f.contents_writer->QueueMessage(
             &fbb, UUID::FromVector(msg->boot_uuid()), end);
diff --git a/aos/events/logging/log_writer.h b/aos/events/logging/log_writer.h
index 5c8b0c7..6b04f5d 100644
--- a/aos/events/logging/log_writer.h
+++ b/aos/events/logging/log_writer.h
@@ -199,6 +199,17 @@
 
     // If true, this message is being sent over a reliable channel.
     bool reliable_forwarding = false;
+
+    // One of the following will be populated.  If channel_reliable_contents is
+    // non zero size, it contains a mapping from the event loop channel (not the
+    // logged channel) to a bool telling us if that particular channel is
+    // reliable.
+    //
+    // If channel_reliable_contents is empty, reliable_contents will contain the
+    // same info for all contents logged here.  This is the predominant case for
+    // split timestamp channels (the prefered approach).
+    bool reliable_contents = false;
+    std::vector<bool> channel_reliable_contents;
   };
 
   // Vector mapping from the channel index from the event loop to the logged
diff --git a/aos/events/logging/logfile_sorting.cc b/aos/events/logging/logfile_sorting.cc
index 5000ff9..1e7c817 100644
--- a/aos/events/logging/logfile_sorting.cc
+++ b/aos/events/logging/logfile_sorting.cc
@@ -93,7 +93,7 @@
 }
 
 bool ConfigOnly(const LogFileHeader *header) {
-  CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 28u);
+  CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 32u);
   if (header->has_monotonic_start_time()) return false;
   if (header->has_realtime_start_time()) return false;
   if (header->has_max_out_of_order_duration()) return false;
@@ -117,6 +117,10 @@
   if (header->has_oldest_local_monotonic_timestamps()) return false;
   if (header->has_oldest_remote_unreliable_monotonic_timestamps()) return false;
   if (header->has_oldest_local_unreliable_monotonic_timestamps()) return false;
+  if (header->has_oldest_remote_reliable_monotonic_timestamps()) return false;
+  if (header->has_oldest_local_reliable_monotonic_timestamps()) return false;
+  if (header->has_oldest_logger_remote_unreliable_monotonic_timestamps()) return false;
+  if (header->has_oldest_logger_local_unreliable_monotonic_timestamps()) return false;
 
   return header->has_configuration();
 }
@@ -259,6 +263,13 @@
 
 // For a pair of nodes, this holds the oldest times that messages were
 // transfered.
+//
+// TODO(austin): We are finding that the timestamps in
+// oldest_remote_monotonic_timestamp are not very useful because there may not
+// be reliable messages on both boots in the log.  Figure out how to use that
+// data better (or really, use the new reliable remote timestamps field) and
+// update this code.  I think we can delay a bit until someone figures out how
+// to get here without other code paths sorting us first.
 struct BootPairTimes {
   // Pair of local and remote timestamps for the oldest message forwarded to
   // this node.
@@ -616,6 +627,21 @@
                log_header->message()
                    .oldest_remote_unreliable_monotonic_timestamps()
                    ->size());
+      CHECK_EQ(log_header->message()
+                   .has_oldest_logger_local_unreliable_monotonic_timestamps(),
+               log_header->message()
+                   .has_oldest_logger_remote_unreliable_monotonic_timestamps());
+      if (log_header->message()
+              .has_oldest_logger_local_unreliable_monotonic_timestamps()) {
+        CHECK_EQ(boot_uuids_size,
+                 log_header->message()
+                     .oldest_logger_local_unreliable_monotonic_timestamps()
+                     ->size());
+        CHECK_EQ(boot_uuids_size,
+                 log_header->message()
+                     .oldest_logger_remote_unreliable_monotonic_timestamps()
+                     ->size());
+      }
       CHECK(!logger_boot_uuid.empty());
       CHECK(!source_boot_uuid.empty());
       for (size_t node_index = 0; node_index < boot_uuids_size; ++node_index) {
@@ -640,7 +666,26 @@
                 log_header->message()
                     .oldest_remote_unreliable_monotonic_timestamps()
                     ->Get(node_index)));
-        if (boot_uuid.empty() || boot_uuid == source_boot_uuid) {
+
+        const monotonic_clock::time_point
+            oldest_logger_local_unreliable_monotonic_timestamp =
+                log_header->message()
+                        .has_oldest_logger_local_unreliable_monotonic_timestamps()
+                    ? monotonic_clock::time_point(chrono::nanoseconds(
+                          log_header->message()
+                              .oldest_logger_local_unreliable_monotonic_timestamps()
+                              ->Get(node_index)))
+                    : monotonic_clock::max_time;
+        const monotonic_clock::time_point
+            oldest_logger_remote_unreliable_monotonic_timestamp =
+                log_header->message()
+                        .has_oldest_logger_remote_unreliable_monotonic_timestamps()
+                    ? monotonic_clock::time_point(chrono::nanoseconds(
+                          log_header->message()
+                              .oldest_logger_remote_unreliable_monotonic_timestamps()
+                              ->Get(node_index)))
+                    : monotonic_clock::max_time;
+        if (boot_uuid.empty()) {
           CHECK_EQ(oldest_local_monotonic_timestamp, monotonic_clock::max_time);
           CHECK_EQ(oldest_remote_monotonic_timestamp,
                    monotonic_clock::max_time);
@@ -648,9 +693,113 @@
                    monotonic_clock::max_time);
           CHECK_EQ(oldest_remote_unreliable_monotonic_timestamp,
                    monotonic_clock::max_time);
+          CHECK_EQ(oldest_logger_local_unreliable_monotonic_timestamp,
+                   monotonic_clock::max_time);
+          CHECK_EQ(oldest_logger_remote_unreliable_monotonic_timestamp,
+                   monotonic_clock::max_time);
           continue;
         }
 
+        if (boot_uuid == source_boot_uuid) {
+          CHECK_EQ(oldest_local_monotonic_timestamp, monotonic_clock::max_time);
+          CHECK_EQ(oldest_remote_monotonic_timestamp,
+                   monotonic_clock::max_time);
+          CHECK_EQ(oldest_local_unreliable_monotonic_timestamp,
+                   monotonic_clock::max_time);
+          CHECK_EQ(oldest_remote_unreliable_monotonic_timestamp,
+                   monotonic_clock::max_time);
+          if (oldest_logger_local_unreliable_monotonic_timestamp !=
+                     monotonic_clock::max_time) {
+            CHECK_NE(oldest_logger_remote_unreliable_monotonic_timestamp,
+                     monotonic_clock::max_time);
+            // Now, we found a timestamp going the other way.  Add it in!
+            auto logger_node_boot_times_it = boot_times.find(logger_node);
+            if (logger_node_boot_times_it == boot_times.end()) {
+              logger_node_boot_times_it =
+                  boot_times
+                      .emplace(
+                          logger_node,
+                          absl::btree_map<
+                              std::string,
+                              absl::btree_map<
+                                  size_t, absl::btree_map<
+                                              std::string,
+                                              std::vector<BootPairTimes>>>>())
+                      .first;
+            }
+
+            auto logger_source_boot_times_it =
+                logger_node_boot_times_it->second.find(
+                    std::string(logger_boot_uuid));
+
+            if (logger_source_boot_times_it ==
+                logger_node_boot_times_it->second.end()) {
+              logger_source_boot_times_it =
+                  logger_node_boot_times_it->second
+                      .emplace(
+                          logger_boot_uuid,
+                          absl::btree_map<
+                              size_t,
+                              absl::btree_map<std::string,
+                                              std::vector<BootPairTimes>>>())
+                      .first;
+            }
+
+            // We need the index of the source node.  Luckily, since we are at
+            // the index in the boot UUID list which matches the source node
+            // boot uuid, we know it's index!
+            auto logger_destination_boot_times_it =
+                logger_source_boot_times_it->second.find(node_index);
+            if (logger_destination_boot_times_it ==
+                logger_source_boot_times_it->second.end()) {
+              logger_destination_boot_times_it =
+                  logger_source_boot_times_it->second
+                      .emplace(node_index,
+                               absl::btree_map<std::string,
+                                               std::vector<BootPairTimes>>())
+                      .first;
+            }
+
+            auto logger_boot_times_it =
+                logger_destination_boot_times_it->second.find(
+                    std::string(source_boot_uuid));
+
+            if (logger_boot_times_it ==
+                logger_destination_boot_times_it->second.end()) {
+              // We have a new boot UUID pairing.  Copy over the data we have.
+              logger_destination_boot_times_it->second.emplace(
+                  source_boot_uuid,
+                  std::vector<BootPairTimes>{BootPairTimes{
+                      .oldest_remote_monotonic_timestamp =
+                          monotonic_clock::max_time,
+                      .oldest_local_monotonic_timestamp =
+                          monotonic_clock::max_time,
+                      .oldest_remote_unreliable_monotonic_timestamp =
+                          oldest_logger_remote_unreliable_monotonic_timestamp,
+                      .oldest_local_unreliable_monotonic_timestamp =
+                          oldest_logger_local_unreliable_monotonic_timestamp}});
+            } else {
+              logger_boot_times_it->second.emplace_back(BootPairTimes{
+                  .oldest_remote_monotonic_timestamp =
+                      monotonic_clock::max_time,
+                  .oldest_local_monotonic_timestamp = monotonic_clock::max_time,
+                  .oldest_remote_unreliable_monotonic_timestamp =
+                      oldest_logger_remote_unreliable_monotonic_timestamp,
+                  .oldest_local_unreliable_monotonic_timestamp =
+                      oldest_logger_local_unreliable_monotonic_timestamp});
+            }
+          }
+          continue;
+        }
+
+        // There is no supported way to get logger timestamps from anything but
+        // the source node.  Since we've already handled that above, we should
+        // always expect max_time here.
+        CHECK_EQ(oldest_logger_local_unreliable_monotonic_timestamp,
+                 monotonic_clock::max_time);
+        CHECK_EQ(oldest_logger_remote_unreliable_monotonic_timestamp,
+                 monotonic_clock::max_time);
+
         // Now, we have a valid pairing.
         auto destination_boot_times_it =
             source_boot_times_it->second.find(node_index);
@@ -836,24 +985,17 @@
               VLOG(1)
                   << "Unreliable remote time "
                   << next_boot_time.oldest_remote_unreliable_monotonic_timestamp
-                  << " remote " << boot_time_list.first << " local "
-                  << source_boot_uuid;
-              VLOG(1)
-                  << "Unreliable local time "
+                  << " remote " << boot_time_list.first << " -> local time "
                   << next_boot_time.oldest_local_unreliable_monotonic_timestamp
-                  << " remote " << boot_time_list.first << " local "
-                  << source_boot_uuid;
+                  << " local " << source_boot_uuid;
             }
             if (next_boot_time.oldest_local_monotonic_timestamp !=
                 aos::monotonic_clock::max_time) {
               VLOG(1) << "Reliable remote time "
                       << next_boot_time.oldest_remote_monotonic_timestamp
-                      << " remote " << boot_time_list.first << " local "
-                      << source_boot_uuid;
-              VLOG(1) << "Reliable local time "
+                      << " remote " << boot_time_list.first << " -> local time "
                       << next_boot_time.oldest_local_monotonic_timestamp
-                      << " remote " << boot_time_list.first << " local "
-                      << source_boot_uuid;
+                      << " local " << source_boot_uuid;
             }
             // If we found an existing entry, update the min to be the min of
             // all records.  This lets us combine info from multiple part files.
@@ -953,24 +1095,9 @@
             source_boot_times.begin(), source_boot_times.end(),
             [](const std::tuple<std::string, BootPairTimes, BootPairTimes> &a,
                const std::tuple<std::string, BootPairTimes, BootPairTimes> &b) {
-              // There are cases where we will only have a reliable timestamp.
-              // In that case, we need to use oldest_local_monotonic_timestamp.
-              // But, that may result in collisions if the same message gets
-              // forwarded to both boots, so it will have the same timestamp.
-              // Solve that by breaking the tie with the unreliable messages.
-              if (std::get<1>(a).oldest_local_monotonic_timestamp ==
-                  std::get<1>(b).oldest_local_monotonic_timestamp) {
-                CHECK_NE(
-                    std::get<1>(a).oldest_local_unreliable_monotonic_timestamp,
-                    std::get<1>(b).oldest_local_unreliable_monotonic_timestamp);
-                return std::get<1>(a)
-                           .oldest_local_unreliable_monotonic_timestamp <
-                       std::get<1>(b)
-                           .oldest_local_unreliable_monotonic_timestamp;
-              } else {
-                return std::get<1>(a).oldest_local_monotonic_timestamp <
-                       std::get<1>(b).oldest_local_monotonic_timestamp;
-              }
+              return std::get<1>(a)
+                         .oldest_local_unreliable_monotonic_timestamp <
+                     std::get<1>(b).oldest_local_unreliable_monotonic_timestamp;
             });
 
         // The last time from the source node on the logger node.
@@ -1094,21 +1221,8 @@
             destination_boot_times.begin(), destination_boot_times.end(),
             [](const std::pair<std::string, BootPairTimes> &a,
                const std::pair<std::string, BootPairTimes> &b) {
-              // There are cases where we will only have a reliable timestamp.
-              // In that case, we need to use oldest_remote_monotonic_timestamp.
-              // But, that may result in collisions if the same message gets
-              // forwarded to both boots, so it will have the same timestamp.
-              // Solve that by breaking the tie with the unreliable messages.
-              if (a.second.oldest_remote_monotonic_timestamp ==
-                  b.second.oldest_remote_monotonic_timestamp) {
-                CHECK_NE(a.second.oldest_remote_unreliable_monotonic_timestamp,
-                         b.second.oldest_remote_unreliable_monotonic_timestamp);
-                return a.second.oldest_remote_unreliable_monotonic_timestamp <
-                       b.second.oldest_remote_unreliable_monotonic_timestamp;
-              } else {
-                return a.second.oldest_remote_monotonic_timestamp <
-                       b.second.oldest_remote_monotonic_timestamp;
-              }
+              return a.second.oldest_remote_unreliable_monotonic_timestamp <
+                     b.second.oldest_remote_unreliable_monotonic_timestamp;
             });
 
         for (size_t boot_id = 0; boot_id < destination_boot_times.size();
diff --git a/aos/events/logging/logger.fbs b/aos/events/logging/logger.fbs
index eb70184..0fe6253 100644
--- a/aos/events/logging/logger.fbs
+++ b/aos/events/logging/logger.fbs
@@ -108,30 +108,60 @@
   // across the network and using those to determine constraints so we can sort
   // a DAG.
   //
-  // There are 2 main problem cases.  Let's say we have 2 channels.  /a which
-  // is reliable, and /b which isn't, both sent from the same remote node.
+  // There are 5 main cases.  Let's say we have 2 channels.  /r which
+  // is reliable, and /u which isn't, both sent from the same remote node.
+  // The examples below are listed as the remote node sending the message, and
+  // then the local node receiving and logging the message.
   //
-  // case 1:  /a -> boot 0 received on boot 0.
-  //          /b -> boot 1 received on boot 0.
+  // case 0:  /r -> boot 0 received on boot 0.
+  //          /u -> boot 0 received on boot 0.
+  //  We log for a bit, then the remote reboots.
+  //          /r -> boot 1 received on boot 0.
+  //          /u -> boot 1 received on boot 0.
+  //
+  // case 1:  /r -> boot 0 received on boot 0.
+  //          /u -> boot 1 received on boot 0.
   //  We start logging after both messages arrive.
   //
-  // case 2:  /a -> boot 0 received on boot 0.
-  //          /b -> boot 0 received on boot 0.
+  // case 2:  /r -> boot 0 received on boot 0.
+  //          /u -> boot 0 received on boot 0.
   //  We log for a bit, then reboot.  More messages show up when we reconnect.
-  //          /a -> boot 0 received on boot 1.
-  //          /b -> boot 0 received on boot 1.
+  //          /r -> boot 0 received on boot 1.
+  //          /u -> boot 0 received on boot 1.
+  //
+  // case 3:  /u -> boot 0 received on boot 0.
+  //          /r -> boot 1 received on boot 0.
+  //          /u -> boot 1 received on boot 0.
+  //  We start logging after all three messages arrive.
+  //
+  // case 4:  /u -> boot 0 received on boot 0.
+  //          /r -> boot 1 received on boot 0.
+  //
+  // In case 0, we have all the messages showing up and a reboot of the remote.
   //
   // In case 1: we only have a reliable timestamp from boot 0, but that
-  // reliable timestamp makes it clear that /a was before /b, so boot 0 was
+  // reliable timestamp makes it clear that /r was before /u, so boot 0 was
   // before boot 1.
   //
   // In case 2: we have the same reliable timestamp, so that tells us nothing.
-  // The unreliable timestamps though tell a different story.  /b will be after
-  // /a, since any messages on /b generated before the reboot won't get
-  // delivered.  So, we get an ordering constraint saying that any sent /b's
-  // on the second boot were after /b on the first boot.
+  // The unreliable timestamps though tell a different story.  /u will be after
+  // /r, since any messages on /u generated before the reboot won't get
+  // delivered.  So, we get an ordering constraint saying that any sent /u's
+  // on the second boot were after /u on the first boot.
+  //
+  // In case 3: we only got the reliable message on the second boot for some
+  // reason.  Reliable messages aren't 100% reliable.  In this case, the
+  // reliable timestamps are actually a distraction and are misleading.  We
+  // want to use the unreliable timestamps here.
+  //
+  // In case 4: we have utter madness...
+  //
+  // We expect the nominal case to be case 0, or the first half of case 0 if
+  // there are no reboots.
   //
   // We believe that any other cases are covered by the same mechanism.
+  // TODO(austin/brian): Shore up this and capture the cases that are 100%
+  // ambiguous and we can't sort.
   //
   // For all channels sent from a specific node, these vectors hold the
   // timestamp of the oldest known message from that node, and the
@@ -150,6 +180,17 @@
   corrupted_oldest_local_unreliable_monotonic_timestamps:[int64] (id: 23, deprecated);
   oldest_remote_unreliable_monotonic_timestamps:[int64] (id: 26);
   oldest_local_unreliable_monotonic_timestamps:[int64] (id: 27);
+
+  // For all channels *excluding* the unreliable channels (ttl != 0), record the
+  // same quantity.
+  oldest_remote_reliable_monotonic_timestamps:[int64] (id: 28);
+  oldest_local_reliable_monotonic_timestamps:[int64] (id: 29);
+
+  // For all the remote timestamps which come back to the logger.  The "local"
+  // time here is the logger node boot, and "remote" is the node which sent the
+  // timestamps.
+  oldest_logger_remote_unreliable_monotonic_timestamps:[int64] (id: 30);
+  oldest_logger_local_unreliable_monotonic_timestamps:[int64] (id: 31);
 }
 
 // Table holding a message.
diff --git a/aos/events/logging/logger_test.cc b/aos/events/logging/logger_test.cc
index 55bf687..1732549 100644
--- a/aos/events/logging/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -398,8 +398,7 @@
               ping_sender.MakeBuilder();
           examples::Ping::Builder ping_builder =
               builder.MakeBuilder<examples::Ping>();
-          CHECK_EQ(builder.Send(ping_builder.Finish()),
-                   RawSender::Error::kOk);
+          CHECK_EQ(builder.Send(ping_builder.Finish()), RawSender::Error::kOk);
         });
 
     // 100 ms / 0.05 ms -> 2000 messages.  Should be enough to crash it.
@@ -510,8 +509,15 @@
 
   ~LoggerState() {
     if (logger) {
-      for (const std::string &file : log_namer->all_filenames()) {
+      std::vector<std::string> filenames;
+      AppendAllFilenames(&filenames);
+      std::sort(filenames.begin(), filenames.end());
+      for (const std::string &file : filenames) {
         LOG(INFO) << "Wrote to " << file;
+        auto x = ReadHeader(file);
+        if (x) {
+          VLOG(1) << aos::FlatbufferToJson(x.value());
+        }
       }
     }
   }
@@ -598,8 +604,8 @@
 
   std::vector<std::string> MakeLogFiles(std::string logfile_base1,
                                         std::string logfile_base2,
-                                        size_t pi1_data_count = 2,
-                                        size_t pi2_data_count = 2) {
+                                        size_t pi1_data_count = 3,
+                                        size_t pi2_data_count = 3) {
     std::vector<std::string> result;
     result.emplace_back(absl::StrCat(
         logfile_base1, "_", std::get<0>(GetParam()).sha256, Extension()));
@@ -638,6 +644,10 @@
                           "_timestamps/pi1/aos/remote_timestamps/pi2/"
                           "aos.message_bridge.RemoteMessage.part1" +
                           Extension());
+      result.emplace_back(logfile_base1 +
+                          "_timestamps/pi1/aos/remote_timestamps/pi2/"
+                          "aos.message_bridge.RemoteMessage.part2" +
+                          Extension());
       result.emplace_back(logfile_base2 +
                           "_timestamps/pi2/aos/remote_timestamps/pi1/"
                           "aos.message_bridge.RemoteMessage.part0" +
@@ -687,6 +697,8 @@
     result.emplace_back(logfile_base1_ + "_pi1_data.part0" + Extension());
     result.emplace_back(logfile_base1_ + "_pi1_data.part1" + Extension());
     result.emplace_back(logfile_base1_ + "_pi1_data.part2" + Extension());
+    result.emplace_back(logfile_base1_ + "_pi1_data.part3" + Extension());
+    result.emplace_back(logfile_base1_ + "_pi1_data.part4" + Extension());
     result.emplace_back(logfile_base1_ +
                         "_pi2_data/test/aos.examples.Pong.part0" + Extension());
     result.emplace_back(logfile_base1_ +
@@ -710,22 +722,13 @@
     result.emplace_back(absl::StrCat(
         logfile_base1_, "_", std::get<0>(GetParam()).sha256, Extension()));
     if (shared()) {
-      result.emplace_back(logfile_base1_ +
-                          "_timestamps/pi1/aos/remote_timestamps/pi2/"
-                          "aos.message_bridge.RemoteMessage.part0" +
-                          Extension());
-      result.emplace_back(logfile_base1_ +
-                          "_timestamps/pi1/aos/remote_timestamps/pi2/"
-                          "aos.message_bridge.RemoteMessage.part1" +
-                          Extension());
-      result.emplace_back(logfile_base1_ +
-                          "_timestamps/pi1/aos/remote_timestamps/pi2/"
-                          "aos.message_bridge.RemoteMessage.part2" +
-                          Extension());
-      result.emplace_back(logfile_base1_ +
-                          "_timestamps/pi1/aos/remote_timestamps/pi2/"
-                          "aos.message_bridge.RemoteMessage.part3" +
-                          Extension());
+      for (size_t i = 0; i < 6; ++i) {
+        result.emplace_back(
+            absl::StrCat(logfile_base1_,
+                         "_timestamps/pi1/aos/remote_timestamps/pi2/"
+                         "aos.message_bridge.RemoteMessage.part",
+                         i, Extension()));
+      }
     } else {
       result.emplace_back(logfile_base1_ +
                           "_timestamps/pi1/aos/remote_timestamps/pi2/pi1/aos/"
@@ -794,17 +797,24 @@
 
   std::vector<std::vector<std::string>> StructureLogFiles() {
     std::vector<std::vector<std::string>> result{
-        std::vector<std::string>{logfiles_[2], logfiles_[3]},
-        std::vector<std::string>{logfiles_[4], logfiles_[5]},
-        std::vector<std::string>{logfiles_[6], logfiles_[7]},
-        std::vector<std::string>{logfiles_[8], logfiles_[9]},
+        std::vector<std::string>{logfiles_[2], logfiles_[3], logfiles_[4]},
+        std::vector<std::string>{logfiles_[5], logfiles_[6]},
+        std::vector<std::string>{logfiles_[7], logfiles_[8], logfiles_[9]},
         std::vector<std::string>{logfiles_[10], logfiles_[11]},
-        std::vector<std::string>{logfiles_[12], logfiles_[13]},
-        std::vector<std::string>{logfiles_[14], logfiles_[15]}};
+        std::vector<std::string>{logfiles_[12], logfiles_[13]}};
 
-    if (!shared()) {
+    if (shared()) {
+      result.emplace_back(std::vector<std::string>{logfiles_[14], logfiles_[15],
+                                                   logfiles_[16]});
+      result.emplace_back(
+          std::vector<std::string>{logfiles_[17], logfiles_[18]});
+    } else {
+      result.emplace_back(
+          std::vector<std::string>{logfiles_[14], logfiles_[15]});
       result.emplace_back(
           std::vector<std::string>{logfiles_[16], logfiles_[17]});
+      result.emplace_back(
+          std::vector<std::string>{logfiles_[18], logfiles_[19]});
     }
 
     return result;
@@ -887,7 +897,7 @@
     // Test that each list of parts is in order.  Don't worry about the ordering
     // between part file lists though.
     // (inner vectors all need to be in order, but outer one doesn't matter).
-    EXPECT_THAT(ToLogReaderVector(sorted_parts),
+    ASSERT_THAT(ToLogReaderVector(sorted_parts),
                 ::testing::UnorderedElementsAreArray(structured_logfiles_));
 
     EXPECT_THAT(logger_nodes, ::testing::UnorderedElementsAre("pi1", "pi2"));
@@ -1010,7 +1020,9 @@
 
 // Tests that we can write and read simple multi-node log files.
 TEST_P(MultinodeLoggerTest, SimpleMultiNode) {
+  std::vector<std::string> actual_filenames;
   time_converter_.StartEqual();
+
   {
     LoggerState pi1_logger = MakeLogger(pi1_);
     LoggerState pi2_logger = MakeLogger(pi2_);
@@ -1021,8 +1033,13 @@
     StartLogger(&pi2_logger);
 
     event_loop_factory_.RunFor(chrono::milliseconds(20000));
+    pi1_logger.AppendAllFilenames(&actual_filenames);
+    pi2_logger.AppendAllFilenames(&actual_filenames);
   }
 
+  ASSERT_THAT(actual_filenames,
+              ::testing::UnorderedElementsAreArray(logfiles_));
+
   {
     std::set<std::string> logfile_uuids;
     std::set<std::string> parts_uuids;
@@ -1048,41 +1065,73 @@
     // And confirm everything is on the correct node.
     EXPECT_EQ(log_header[2].message().node()->name()->string_view(), "pi1");
     EXPECT_EQ(log_header[3].message().node()->name()->string_view(), "pi1");
-    EXPECT_EQ(log_header[4].message().node()->name()->string_view(), "pi2");
+    EXPECT_EQ(log_header[4].message().node()->name()->string_view(), "pi1");
+
     EXPECT_EQ(log_header[5].message().node()->name()->string_view(), "pi2");
     EXPECT_EQ(log_header[6].message().node()->name()->string_view(), "pi2");
+
     EXPECT_EQ(log_header[7].message().node()->name()->string_view(), "pi2");
-    EXPECT_EQ(log_header[8].message().node()->name()->string_view(), "pi1");
-    EXPECT_EQ(log_header[9].message().node()->name()->string_view(), "pi1");
-    EXPECT_EQ(log_header[10].message().node()->name()->string_view(), "pi2");
-    EXPECT_EQ(log_header[11].message().node()->name()->string_view(), "pi2");
+    EXPECT_EQ(log_header[8].message().node()->name()->string_view(), "pi2");
+    EXPECT_EQ(log_header[9].message().node()->name()->string_view(), "pi2");
+
+    EXPECT_EQ(log_header[10].message().node()->name()->string_view(), "pi1");
+    EXPECT_EQ(log_header[11].message().node()->name()->string_view(), "pi1");
+
     EXPECT_EQ(log_header[12].message().node()->name()->string_view(), "pi2");
     EXPECT_EQ(log_header[13].message().node()->name()->string_view(), "pi2");
-    EXPECT_EQ(log_header[14].message().node()->name()->string_view(), "pi1");
-    EXPECT_EQ(log_header[15].message().node()->name()->string_view(), "pi1");
-    if (!shared()) {
+
+    if (shared()) {
+      EXPECT_EQ(log_header[14].message().node()->name()->string_view(), "pi2");
+      EXPECT_EQ(log_header[15].message().node()->name()->string_view(), "pi2");
       EXPECT_EQ(log_header[16].message().node()->name()->string_view(), "pi2");
-      EXPECT_EQ(log_header[17].message().node()->name()->string_view(), "pi2");
+
+      EXPECT_EQ(log_header[17].message().node()->name()->string_view(), "pi1");
+      EXPECT_EQ(log_header[18].message().node()->name()->string_view(), "pi1");
+    } else {
+      EXPECT_EQ(log_header[14].message().node()->name()->string_view(), "pi2");
+      EXPECT_EQ(log_header[15].message().node()->name()->string_view(), "pi2");
+
+      EXPECT_EQ(log_header[16].message().node()->name()->string_view(), "pi1");
+      EXPECT_EQ(log_header[17].message().node()->name()->string_view(), "pi1");
+
+      EXPECT_EQ(log_header[18].message().node()->name()->string_view(), "pi2");
+      EXPECT_EQ(log_header[19].message().node()->name()->string_view(), "pi2");
     }
 
     // And the parts index matches.
     EXPECT_EQ(log_header[2].message().parts_index(), 0);
     EXPECT_EQ(log_header[3].message().parts_index(), 1);
-    EXPECT_EQ(log_header[4].message().parts_index(), 0);
-    EXPECT_EQ(log_header[5].message().parts_index(), 1);
-    EXPECT_EQ(log_header[6].message().parts_index(), 0);
-    EXPECT_EQ(log_header[7].message().parts_index(), 1);
-    EXPECT_EQ(log_header[8].message().parts_index(), 0);
-    EXPECT_EQ(log_header[9].message().parts_index(), 1);
+    EXPECT_EQ(log_header[4].message().parts_index(), 2);
+
+    EXPECT_EQ(log_header[5].message().parts_index(), 0);
+    EXPECT_EQ(log_header[6].message().parts_index(), 1);
+
+    EXPECT_EQ(log_header[7].message().parts_index(), 0);
+    EXPECT_EQ(log_header[8].message().parts_index(), 1);
+    EXPECT_EQ(log_header[9].message().parts_index(), 2);
+
     EXPECT_EQ(log_header[10].message().parts_index(), 0);
     EXPECT_EQ(log_header[11].message().parts_index(), 1);
+
     EXPECT_EQ(log_header[12].message().parts_index(), 0);
     EXPECT_EQ(log_header[13].message().parts_index(), 1);
-    EXPECT_EQ(log_header[14].message().parts_index(), 0);
-    EXPECT_EQ(log_header[15].message().parts_index(), 1);
-    if (!shared()) {
+
+    if (shared()) {
+      EXPECT_EQ(log_header[14].message().parts_index(), 0);
+      EXPECT_EQ(log_header[15].message().parts_index(), 1);
+      EXPECT_EQ(log_header[16].message().parts_index(), 2);
+
+      EXPECT_EQ(log_header[17].message().parts_index(), 0);
+      EXPECT_EQ(log_header[18].message().parts_index(), 1);
+    } else {
+      EXPECT_EQ(log_header[14].message().parts_index(), 0);
+      EXPECT_EQ(log_header[15].message().parts_index(), 1);
+
       EXPECT_EQ(log_header[16].message().parts_index(), 0);
       EXPECT_EQ(log_header[17].message().parts_index(), 1);
+
+      EXPECT_EQ(log_header[18].message().parts_index(), 0);
+      EXPECT_EQ(log_header[19].message().parts_index(), 1);
     }
   }
 
@@ -1102,50 +1151,65 @@
     EXPECT_THAT(
         CountChannelsData(config, logfiles_[3]),
         UnorderedElementsAre(
-            std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 200),
+            std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 1),
+            std::make_tuple("/pi1/aos", "aos.message_bridge.ClientStatistics",
+                            1)))
+        << " : " << logfiles_[3];
+    EXPECT_THAT(
+        CountChannelsData(config, logfiles_[4]),
+        UnorderedElementsAre(
+            std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 199),
             std::make_tuple("/pi1/aos", "aos.message_bridge.ServerStatistics",
                             20),
             std::make_tuple("/pi1/aos", "aos.message_bridge.ClientStatistics",
-                            200),
+                            199),
             std::make_tuple("/pi1/aos", "aos.timing.Report", 40),
             std::make_tuple("/test", "aos.examples.Ping", 2000)))
-        << " : " << logfiles_[3];
+        << " : " << logfiles_[4];
     // Timestamps for pong
     EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[2]),
                 UnorderedElementsAre())
         << " : " << logfiles_[2];
     EXPECT_THAT(
         CountChannelsTimestamp(config, logfiles_[3]),
-        UnorderedElementsAre(
-            std::make_tuple("/test", "aos.examples.Pong", 2001),
-            std::make_tuple("/pi2/aos", "aos.message_bridge.Timestamp", 200)))
+        UnorderedElementsAre(std::make_tuple("/test", "aos.examples.Pong", 1)))
         << " : " << logfiles_[3];
+    EXPECT_THAT(
+        CountChannelsTimestamp(config, logfiles_[4]),
+        UnorderedElementsAre(
+            std::make_tuple("/test", "aos.examples.Pong", 2000),
+            std::make_tuple("/pi2/aos", "aos.message_bridge.Timestamp", 200)))
+        << " : " << logfiles_[4];
 
     // Pong data.
     EXPECT_THAT(
-        CountChannelsData(config, logfiles_[4]),
+        CountChannelsData(config, logfiles_[5]),
         UnorderedElementsAre(std::make_tuple("/test", "aos.examples.Pong", 91)))
-        << " : " << logfiles_[4];
-    EXPECT_THAT(CountChannelsData(config, logfiles_[5]),
+        << " : " << logfiles_[5];
+    EXPECT_THAT(CountChannelsData(config, logfiles_[6]),
                 UnorderedElementsAre(
                     std::make_tuple("/test", "aos.examples.Pong", 1910)))
-        << " : " << logfiles_[5];
+        << " : " << logfiles_[6];
 
     // No timestamps
-    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[4]),
-                UnorderedElementsAre())
-        << " : " << logfiles_[4];
     EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[5]),
                 UnorderedElementsAre())
         << " : " << logfiles_[5];
+    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[6]),
+                UnorderedElementsAre())
+        << " : " << logfiles_[6];
 
     // Timing reports and pongs.
-    EXPECT_THAT(CountChannelsData(config, logfiles_[6]),
+    EXPECT_THAT(CountChannelsData(config, logfiles_[7]),
                 UnorderedElementsAre(std::make_tuple(
                     "/pi2/aos", "aos.message_bridge.ServerStatistics", 1)))
-        << " : " << logfiles_[6];
+        << " : " << logfiles_[7];
     EXPECT_THAT(
-        CountChannelsData(config, logfiles_[7]),
+        CountChannelsData(config, logfiles_[8]),
+        UnorderedElementsAre(std::make_tuple("/test", "aos.examples.Pong", 1)))
+        << " : " << logfiles_[8];
+    EXPECT_THAT(
+        CountChannelsData(config, logfiles_[9]),
         UnorderedElementsAre(
             std::make_tuple("/pi2/aos", "aos.message_bridge.Timestamp", 200),
             std::make_tuple("/pi2/aos", "aos.message_bridge.ServerStatistics",
@@ -1153,120 +1217,142 @@
             std::make_tuple("/pi2/aos", "aos.message_bridge.ClientStatistics",
                             200),
             std::make_tuple("/pi2/aos", "aos.timing.Report", 40),
-            std::make_tuple("/test", "aos.examples.Pong", 2001)))
-        << " : " << logfiles_[7];
+            std::make_tuple("/test", "aos.examples.Pong", 2000)))
+        << " : " << logfiles_[9];
     // And ping timestamps.
-    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[6]),
+    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[7]),
                 UnorderedElementsAre())
-        << " : " << logfiles_[6];
-    EXPECT_THAT(
-        CountChannelsTimestamp(config, logfiles_[7]),
-        UnorderedElementsAre(
-            std::make_tuple("/test", "aos.examples.Ping", 2001),
-            std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 200)))
         << " : " << logfiles_[7];
+    EXPECT_THAT(
+        CountChannelsTimestamp(config, logfiles_[8]),
+        UnorderedElementsAre(std::make_tuple("/test", "aos.examples.Ping", 1)))
+        << " : " << logfiles_[8];
+    EXPECT_THAT(
+        CountChannelsTimestamp(config, logfiles_[9]),
+        UnorderedElementsAre(
+            std::make_tuple("/test", "aos.examples.Ping", 2000),
+            std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 200)))
+        << " : " << logfiles_[9];
 
     // And then test that the remotely logged timestamp data files only have
     // timestamps in them.
-    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[8]),
-                UnorderedElementsAre())
-        << " : " << logfiles_[8];
-    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[9]),
-                UnorderedElementsAre())
-        << " : " << logfiles_[9];
     EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[10]),
                 UnorderedElementsAre())
         << " : " << logfiles_[10];
     EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[11]),
                 UnorderedElementsAre())
         << " : " << logfiles_[11];
-
-    EXPECT_THAT(CountChannelsData(config, logfiles_[8]),
-                UnorderedElementsAre(std::make_tuple(
-                    "/pi1/aos", "aos.message_bridge.Timestamp", 9)))
-        << " : " << logfiles_[8];
-    EXPECT_THAT(CountChannelsData(config, logfiles_[9]),
-                UnorderedElementsAre(std::make_tuple(
-                    "/pi1/aos", "aos.message_bridge.Timestamp", 191)))
-        << " : " << logfiles_[9];
+    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[12]),
+                UnorderedElementsAre())
+        << " : " << logfiles_[12];
+    EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[13]),
+                UnorderedElementsAre())
+        << " : " << logfiles_[13];
 
     EXPECT_THAT(CountChannelsData(config, logfiles_[10]),
                 UnorderedElementsAre(std::make_tuple(
-                    "/pi2/aos", "aos.message_bridge.Timestamp", 9)))
+                    "/pi1/aos", "aos.message_bridge.Timestamp", 9)))
         << " : " << logfiles_[10];
     EXPECT_THAT(CountChannelsData(config, logfiles_[11]),
                 UnorderedElementsAre(std::make_tuple(
-                    "/pi2/aos", "aos.message_bridge.Timestamp", 191)))
+                    "/pi1/aos", "aos.message_bridge.Timestamp", 191)))
         << " : " << logfiles_[11];
 
-    // Timestamps from pi2 on pi1, and the other way.
     EXPECT_THAT(CountChannelsData(config, logfiles_[12]),
-                UnorderedElementsAre())
+                UnorderedElementsAre(std::make_tuple(
+                    "/pi2/aos", "aos.message_bridge.Timestamp", 9)))
         << " : " << logfiles_[12];
     EXPECT_THAT(CountChannelsData(config, logfiles_[13]),
-                UnorderedElementsAre())
+                UnorderedElementsAre(std::make_tuple(
+                    "/pi2/aos", "aos.message_bridge.Timestamp", 191)))
         << " : " << logfiles_[13];
-    EXPECT_THAT(CountChannelsData(config, logfiles_[14]),
-                UnorderedElementsAre())
-        << " : " << logfiles_[14];
-    EXPECT_THAT(CountChannelsData(config, logfiles_[15]),
-                UnorderedElementsAre())
-        << " : " << logfiles_[15];
-    if (!shared()) {
+
+    // Timestamps from pi2 on pi1, and the other way.
+    if (shared()) {
+      EXPECT_THAT(CountChannelsData(config, logfiles_[14]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[14];
+      EXPECT_THAT(CountChannelsData(config, logfiles_[15]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[15];
       EXPECT_THAT(CountChannelsData(config, logfiles_[16]),
                   UnorderedElementsAre())
           << " : " << logfiles_[16];
       EXPECT_THAT(CountChannelsData(config, logfiles_[17]),
                   UnorderedElementsAre())
           << " : " << logfiles_[17];
-    }
+      EXPECT_THAT(CountChannelsData(config, logfiles_[18]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[18];
 
-    if (shared()) {
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[14]),
+                  UnorderedElementsAre(
+                      std::make_tuple("/test", "aos.examples.Ping", 1)))
+          << " : " << logfiles_[14];
       EXPECT_THAT(
-          CountChannelsTimestamp(config, logfiles_[12]),
+          CountChannelsTimestamp(config, logfiles_[15]),
           UnorderedElementsAre(
               std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 9),
-              std::make_tuple("/test", "aos.examples.Ping", 91)))
-          << " : " << logfiles_[12];
+              std::make_tuple("/test", "aos.examples.Ping", 90)))
+          << " : " << logfiles_[15];
       EXPECT_THAT(
-          CountChannelsTimestamp(config, logfiles_[13]),
+          CountChannelsTimestamp(config, logfiles_[16]),
           UnorderedElementsAre(
               std::make_tuple("/pi1/aos", "aos.message_bridge.Timestamp", 191),
               std::make_tuple("/test", "aos.examples.Ping", 1910)))
-          << " : " << logfiles_[13];
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[14]),
-                  UnorderedElementsAre(std::make_tuple(
-                      "/pi2/aos", "aos.message_bridge.Timestamp", 9)))
-          << " : " << logfiles_[14];
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[15]),
-                  UnorderedElementsAre(std::make_tuple(
-                      "/pi2/aos", "aos.message_bridge.Timestamp", 191)))
-          << " : " << logfiles_[15];
-    } else {
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[12]),
-                  UnorderedElementsAre(std::make_tuple(
-                      "/pi1/aos", "aos.message_bridge.Timestamp", 9)))
-          << " : " << logfiles_[12];
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[13]),
-                  UnorderedElementsAre(std::make_tuple(
-                      "/pi1/aos", "aos.message_bridge.Timestamp", 191)))
-          << " : " << logfiles_[13];
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[14]),
-                  UnorderedElementsAre(std::make_tuple(
-                      "/pi2/aos", "aos.message_bridge.Timestamp", 9)))
-          << " : " << logfiles_[14];
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[15]),
-                  UnorderedElementsAre(std::make_tuple(
-                      "/pi2/aos", "aos.message_bridge.Timestamp", 191)))
-          << " : " << logfiles_[15];
-      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[16]),
-                  UnorderedElementsAre(
-                      std::make_tuple("/test", "aos.examples.Ping", 91)))
           << " : " << logfiles_[16];
       EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[17]),
+                  UnorderedElementsAre(std::make_tuple(
+                      "/pi2/aos", "aos.message_bridge.Timestamp", 9)))
+          << " : " << logfiles_[17];
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[18]),
+                  UnorderedElementsAre(std::make_tuple(
+                      "/pi2/aos", "aos.message_bridge.Timestamp", 191)))
+          << " : " << logfiles_[18];
+    } else {
+      EXPECT_THAT(CountChannelsData(config, logfiles_[14]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[14];
+      EXPECT_THAT(CountChannelsData(config, logfiles_[15]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[15];
+      EXPECT_THAT(CountChannelsData(config, logfiles_[16]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[16];
+      EXPECT_THAT(CountChannelsData(config, logfiles_[17]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[17];
+      EXPECT_THAT(CountChannelsData(config, logfiles_[18]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[18];
+      EXPECT_THAT(CountChannelsData(config, logfiles_[19]),
+                  UnorderedElementsAre())
+          << " : " << logfiles_[19];
+
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[14]),
+                  UnorderedElementsAre(std::make_tuple(
+                      "/pi1/aos", "aos.message_bridge.Timestamp", 9)))
+          << " : " << logfiles_[14];
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[15]),
+                  UnorderedElementsAre(std::make_tuple(
+                      "/pi1/aos", "aos.message_bridge.Timestamp", 191)))
+          << " : " << logfiles_[15];
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[16]),
+                  UnorderedElementsAre(std::make_tuple(
+                      "/pi2/aos", "aos.message_bridge.Timestamp", 9)))
+          << " : " << logfiles_[16];
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[17]),
+                  UnorderedElementsAre(std::make_tuple(
+                      "/pi2/aos", "aos.message_bridge.Timestamp", 191)))
+          << " : " << logfiles_[17];
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[18]),
+                  UnorderedElementsAre(
+                      std::make_tuple("/test", "aos.examples.Ping", 91)))
+          << " : " << logfiles_[18];
+      EXPECT_THAT(CountChannelsTimestamp(config, logfiles_[19]),
                   UnorderedElementsAre(
                       std::make_tuple("/test", "aos.examples.Ping", 1910)))
-          << " : " << logfiles_[17];
+          << " : " << logfiles_[19];
     }
   }
 
@@ -1459,6 +1545,8 @@
 // time.
 TEST_P(MultinodeLoggerTest, StaggeredStart) {
   time_converter_.StartEqual();
+  std::vector<std::string> actual_filenames;
+
   {
     LoggerState pi1_logger = MakeLogger(pi1_);
     LoggerState pi2_logger = MakeLogger(pi2_);
@@ -1472,12 +1560,13 @@
     StartLogger(&pi2_logger);
 
     event_loop_factory_.RunFor(chrono::milliseconds(20000));
+    pi1_logger.AppendAllFilenames(&actual_filenames);
+    pi2_logger.AppendAllFilenames(&actual_filenames);
   }
 
   // Since we delay starting pi2, it already knows about all the timestamps so
   // we don't end up with extra parts.
-  LogReader reader(
-      SortParts(MakeLogFiles(logfile_base1_, logfile_base2_, 2, 1)));
+  LogReader reader(SortParts(actual_filenames));
 
   SimulatedEventLoopFactory log_reader_factory(reader.configuration());
   log_reader_factory.set_send_delay(chrono::microseconds(0));
@@ -1788,6 +1877,7 @@
 // Tests that we can sort a bunch of parts with the end missing off a
 // file.  We should use the part we can read.
 TEST_P(MultinodeLoggerTest, SortTruncatedParts) {
+  std::vector<std::string> actual_filenames;
   time_converter_.StartEqual();
   // Make a bunch of parts.
   {
@@ -1800,16 +1890,22 @@
     StartLogger(&pi2_logger);
 
     event_loop_factory_.RunFor(chrono::milliseconds(2000));
+
+    pi1_logger.AppendAllFilenames(&actual_filenames);
+    pi2_logger.AppendAllFilenames(&actual_filenames);
   }
 
+  ASSERT_THAT(actual_filenames,
+              ::testing::UnorderedElementsAreArray(logfiles_));
+
   // Strip off the end of one of the files.  Pick one with a lot of data.
   // For snappy, needs to have enough data to be >1 chunk of compressed data so
   // that we don't corrupt the entire log part.
   ::std::string compressed_contents =
-      aos::util::ReadFileToStringOrDie(logfiles_[3]);
+      aos::util::ReadFileToStringOrDie(logfiles_[4]);
 
   aos::util::WriteStringToFileOrDie(
-      logfiles_[3],
+      logfiles_[4],
       compressed_contents.substr(0, compressed_contents.size() - 100));
 
   const std::vector<LogFile> sorted_parts = SortParts(logfiles_);
@@ -2238,6 +2334,7 @@
 // Tests that we properly populate and extract the logger_start time by setting
 // up a clock difference between 2 nodes and looking at the resulting parts.
 TEST_P(MultinodeLoggerTest, LoggerStartTime) {
+  std::vector<std::string> actual_filenames;
   time_converter_.AddMonotonic(
       {BootTimestamp::epoch(), BootTimestamp::epoch() + chrono::seconds(1000)});
   {
@@ -2248,8 +2345,14 @@
     StartLogger(&pi2_logger);
 
     event_loop_factory_.RunFor(chrono::milliseconds(10000));
+
+    pi1_logger.AppendAllFilenames(&actual_filenames);
+    pi2_logger.AppendAllFilenames(&actual_filenames);
   }
 
+  ASSERT_THAT(actual_filenames,
+              ::testing::UnorderedElementsAreArray(logfiles_));
+
   for (const LogFile &log_file : SortParts(logfiles_)) {
     for (const LogParts &log_part : log_file.parts) {
       if (log_part.node == log_file.logger_node) {
@@ -2322,6 +2425,8 @@
 // This should be enough that we can then re-run the logger and get a valid log
 // back.
 TEST_P(MultinodeLoggerTest, RemoteReboot) {
+  std::vector<std::string> actual_filenames;
+
   const UUID pi1_boot0 = UUID::Random();
   const UUID pi2_boot0 = UUID::Random();
   const UUID pi2_boot1 = UUID::Random();
@@ -2365,8 +2470,15 @@
               pi1_boot0);
     EXPECT_EQ(event_loop_factory_.GetNodeEventLoopFactory("pi2")->boot_uuid(),
               pi2_boot1);
+
+    pi1_logger.AppendAllFilenames(&actual_filenames);
   }
 
+  std::sort(actual_filenames.begin(), actual_filenames.end());
+  std::sort(pi1_reboot_logfiles_.begin(), pi1_reboot_logfiles_.end());
+  ASSERT_THAT(actual_filenames,
+              ::testing::UnorderedElementsAreArray(pi1_reboot_logfiles_));
+
   // Confirm that our new oldest timestamps properly update as we reboot and
   // rotate.
   for (const std::string &file : pi1_reboot_logfiles_) {
@@ -2384,28 +2496,64 @@
         log_header->message().source_node_boot_uuid()->string_view());
 
     if (log_header->message().node()->name()->string_view() != "pi1") {
-      switch (log_header->message().parts_index()) {
-        case 0:
-          EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
-          EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
-          break;
-        case 1:
-          EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
-          ASSERT_EQ(monotonic_start_time,
-                    monotonic_clock::epoch() + chrono::seconds(1));
-          break;
-        case 2:
-          EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
-          EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
-          break;
-        case 3:
-          EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
-          ASSERT_EQ(monotonic_start_time,
-                    monotonic_clock::epoch() + chrono::nanoseconds(2322999462));
-          break;
-        default:
-          FAIL();
-          break;
+      // The remote message channel should rotate later and have more parts.
+      // This only is true on the log files with shared remote messages.
+      //
+      // TODO(austin): I'm not the most thrilled with this test pattern...  It
+      // feels brittle in a different way.
+      if (file.find("aos.message_bridge.RemoteMessage") == std::string::npos ||
+          !shared()) {
+        switch (log_header->message().parts_index()) {
+          case 0:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          case 1:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            ASSERT_EQ(monotonic_start_time,
+                      monotonic_clock::epoch() + chrono::seconds(1));
+            break;
+          case 2:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time) << file;
+            break;
+          case 3:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+            ASSERT_EQ(monotonic_start_time, monotonic_clock::epoch() +
+                                                chrono::nanoseconds(2322999462))
+                << " on " << file;
+            break;
+          default:
+            FAIL();
+            break;
+        }
+      } else {
+        switch (log_header->message().parts_index()) {
+          case 0:
+          case 1:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          case 2:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            ASSERT_EQ(monotonic_start_time,
+                      monotonic_clock::epoch() + chrono::seconds(1));
+            break;
+          case 3:
+          case 4:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time) << file;
+            break;
+          case 5:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+            ASSERT_EQ(monotonic_start_time, monotonic_clock::epoch() +
+                                                chrono::nanoseconds(2322999462))
+                << " on " << file;
+            break;
+          default:
+            FAIL();
+            break;
+        }
       }
       continue;
     }
@@ -2463,6 +2611,34 @@
                 log_header->message()
                     .oldest_local_unreliable_monotonic_timestamps()
                     ->Get(1)));
+    const monotonic_clock::time_point
+        oldest_remote_reliable_monotonic_timestamps =
+            monotonic_clock::time_point(chrono::nanoseconds(
+                log_header->message()
+                    .oldest_remote_reliable_monotonic_timestamps()
+                    ->Get(1)));
+    const monotonic_clock::time_point
+        oldest_local_reliable_monotonic_timestamps =
+            monotonic_clock::time_point(chrono::nanoseconds(
+                log_header->message()
+                    .oldest_local_reliable_monotonic_timestamps()
+                    ->Get(1)));
+    const monotonic_clock::time_point
+        oldest_logger_remote_unreliable_monotonic_timestamps =
+            monotonic_clock::time_point(chrono::nanoseconds(
+                log_header->message()
+                    .oldest_logger_remote_unreliable_monotonic_timestamps()
+                    ->Get(0)));
+    const monotonic_clock::time_point
+        oldest_logger_local_unreliable_monotonic_timestamps =
+            monotonic_clock::time_point(chrono::nanoseconds(
+                log_header->message()
+                    .oldest_logger_local_unreliable_monotonic_timestamps()
+                    ->Get(0)));
+    EXPECT_EQ(oldest_logger_remote_unreliable_monotonic_timestamps,
+              monotonic_clock::max_time);
+    EXPECT_EQ(oldest_logger_local_unreliable_monotonic_timestamps,
+              monotonic_clock::max_time);
     switch (log_header->message().parts_index()) {
       case 0:
         EXPECT_EQ(oldest_remote_monotonic_timestamps,
@@ -2472,6 +2648,10 @@
                   monotonic_clock::max_time);
         EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
                   monotonic_clock::max_time);
+        EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                  monotonic_clock::max_time);
+        EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                  monotonic_clock::max_time);
         break;
       case 1:
         EXPECT_EQ(oldest_remote_monotonic_timestamps,
@@ -2482,9 +2662,33 @@
                   monotonic_clock::time_point(chrono::microseconds(90200)));
         EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
                   monotonic_clock::time_point(chrono::microseconds(90350)));
+        EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                  monotonic_clock::max_time);
+        EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                  monotonic_clock::max_time);
         break;
       case 2:
         EXPECT_EQ(oldest_remote_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(90200)))
+            << file;
+        EXPECT_EQ(oldest_local_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(90350)))
+            << file;
+        EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(90200)))
+            << file;
+        EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(90350)))
+            << file;
+        EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(100000)))
+            << file;
+        EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(100150)))
+            << file;
+        break;
+      case 3:
+        EXPECT_EQ(oldest_remote_monotonic_timestamps,
                   monotonic_clock::time_point(chrono::milliseconds(1323) +
                                               chrono::microseconds(200)));
         EXPECT_EQ(oldest_local_monotonic_timestamps,
@@ -2494,6 +2698,30 @@
                                               chrono::microseconds(200)));
         EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
                   monotonic_clock::time_point(chrono::microseconds(10100350)));
+        EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                  monotonic_clock::max_time)
+            << file;
+        EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                  monotonic_clock::max_time)
+            << file;
+        break;
+      case 4:
+        EXPECT_EQ(oldest_remote_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::milliseconds(1323) +
+                                              chrono::microseconds(200)));
+        EXPECT_EQ(oldest_local_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(10100350)));
+        EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::milliseconds(1323) +
+                                              chrono::microseconds(200)));
+        EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(10100350)));
+        EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(1423000)))
+            << file;
+        EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                  monotonic_clock::time_point(chrono::microseconds(10200150)))
+            << file;
         break;
       default:
         FAIL();
@@ -2569,6 +2797,8 @@
     pi1_logger.AppendAllFilenames(&filenames);
   }
 
+  std::sort(filenames.begin(), filenames.end());
+
   // Confirm that our new oldest timestamps properly update as we reboot and
   // rotate.
   size_t timestamp_file_count = 0;
@@ -2605,6 +2835,32 @@
                   .oldest_local_unreliable_monotonic_timestamps()
                   ->size(),
               2u);
+    ASSERT_TRUE(log_header->message()
+                    .has_oldest_remote_reliable_monotonic_timestamps());
+    ASSERT_EQ(log_header->message()
+                  .oldest_remote_reliable_monotonic_timestamps()
+                  ->size(),
+              2u);
+    ASSERT_TRUE(log_header->message()
+                    .has_oldest_local_reliable_monotonic_timestamps());
+    ASSERT_EQ(log_header->message()
+                  .oldest_local_reliable_monotonic_timestamps()
+                  ->size(),
+              2u);
+
+    ASSERT_TRUE(
+        log_header->message()
+            .has_oldest_logger_remote_unreliable_monotonic_timestamps());
+    ASSERT_EQ(log_header->message()
+                  .oldest_logger_remote_unreliable_monotonic_timestamps()
+                  ->size(),
+              2u);
+    ASSERT_TRUE(log_header->message()
+                    .has_oldest_logger_local_unreliable_monotonic_timestamps());
+    ASSERT_EQ(log_header->message()
+                  .oldest_logger_local_unreliable_monotonic_timestamps()
+                  ->size(),
+              2u);
 
     if (log_header->message().node()->name()->string_view() != "pi1") {
       ASSERT_TRUE(file.find("aos.message_bridge.RemoteMessage") !=
@@ -2623,11 +2879,16 @@
       const monotonic_clock::time_point
           expected_oldest_remote_monotonic_timestamps(
               chrono::nanoseconds(msg->message().monotonic_remote_time()));
+      const monotonic_clock::time_point
+          expected_oldest_timestamp_monotonic_timestamps(
+              chrono::nanoseconds(msg->message().monotonic_timestamp_time()));
 
       EXPECT_NE(expected_oldest_local_monotonic_timestamps,
                 monotonic_clock::min_time);
       EXPECT_NE(expected_oldest_remote_monotonic_timestamps,
                 monotonic_clock::min_time);
+      EXPECT_NE(expected_oldest_timestamp_monotonic_timestamps,
+                monotonic_clock::min_time);
 
       ++timestamp_file_count;
       // Since the log file is from the perspective of the other node,
@@ -2651,39 +2912,267 @@
                   log_header->message()
                       .oldest_local_unreliable_monotonic_timestamps()
                       ->Get(0)));
-      // Confirm that the oldest timestamps match what we expect.  Based on what
-      // we are doing, we know that the oldest time is the first message's time.
-      //
-      // This makes the test robust to both the split and combined config tests.
-      switch (log_header->message().parts_index()) {
-        case 0:
-        case 1:
-          EXPECT_EQ(oldest_remote_monotonic_timestamps,
-                    expected_oldest_remote_monotonic_timestamps);
-          EXPECT_EQ(oldest_local_monotonic_timestamps,
-                    expected_oldest_local_monotonic_timestamps);
-          EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
-                    expected_oldest_remote_monotonic_timestamps);
-          EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
-                    expected_oldest_local_monotonic_timestamps);
-          break;
-        default:
-          FAIL();
-          break;
-      }
+      const monotonic_clock::time_point
+          oldest_remote_reliable_monotonic_timestamps =
+              monotonic_clock::time_point(chrono::nanoseconds(
+                  log_header->message()
+                      .oldest_remote_reliable_monotonic_timestamps()
+                      ->Get(0)));
+      const monotonic_clock::time_point
+          oldest_local_reliable_monotonic_timestamps =
+              monotonic_clock::time_point(chrono::nanoseconds(
+                  log_header->message()
+                      .oldest_local_reliable_monotonic_timestamps()
+                      ->Get(0)));
+      const monotonic_clock::time_point
+          oldest_logger_remote_unreliable_monotonic_timestamps =
+              monotonic_clock::time_point(chrono::nanoseconds(
+                  log_header->message()
+                      .oldest_logger_remote_unreliable_monotonic_timestamps()
+                      ->Get(1)));
+      const monotonic_clock::time_point
+          oldest_logger_local_unreliable_monotonic_timestamps =
+              monotonic_clock::time_point(chrono::nanoseconds(
+                  log_header->message()
+                      .oldest_logger_local_unreliable_monotonic_timestamps()
+                      ->Get(1)));
 
-      switch (log_header->message().parts_index()) {
-        case 0:
-          EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
-          EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
-          break;
-        case 1:
-          EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
-          EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
-          break;
-        default:
-          FAIL();
-          break;
+      const Channel *channel =
+          event_loop_factory_.configuration()->channels()->Get(
+              msg->message().channel_index());
+      const Connection *connection = configuration::ConnectionToNode(
+          channel, configuration::GetNode(
+                       event_loop_factory_.configuration(),
+                       log_header->message().node()->name()->string_view()));
+
+      const bool reliable = connection->time_to_live() == 0;
+
+      SCOPED_TRACE(file);
+      SCOPED_TRACE(aos::FlatbufferToJson(
+          *log_header, {.multi_line = true, .max_vector_size = 100}));
+
+      if (shared()) {
+        // Confirm that the oldest timestamps match what we expect.  Based on
+        // what we are doing, we know that the oldest time is the first
+        // message's time.
+        //
+        // This makes the test robust to both the split and combined config
+        // tests.
+        switch (log_header->message().parts_index()) {
+          case 0:
+            EXPECT_EQ(oldest_remote_monotonic_timestamps,
+                      expected_oldest_remote_monotonic_timestamps);
+            EXPECT_EQ(oldest_local_monotonic_timestamps,
+                      expected_oldest_local_monotonic_timestamps);
+            EXPECT_EQ(oldest_logger_remote_unreliable_monotonic_timestamps,
+                      expected_oldest_local_monotonic_timestamps) << file;
+            EXPECT_EQ(oldest_logger_local_unreliable_monotonic_timestamps,
+                      expected_oldest_timestamp_monotonic_timestamps) << file;
+
+            if (reliable) {
+              EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+            } else {
+              EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+            }
+            break;
+          case 1:
+            EXPECT_EQ(oldest_remote_monotonic_timestamps,
+                      monotonic_clock::epoch() + chrono::nanoseconds(90000000));
+            EXPECT_EQ(oldest_local_monotonic_timestamps,
+                      monotonic_clock::epoch() + chrono::nanoseconds(90150000));
+            EXPECT_EQ(oldest_logger_remote_unreliable_monotonic_timestamps,
+                      monotonic_clock::epoch() + chrono::nanoseconds(90150000));
+            EXPECT_EQ(oldest_logger_local_unreliable_monotonic_timestamps,
+                      monotonic_clock::epoch() + chrono::nanoseconds(90250000));
+            if (reliable) {
+              EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+              EXPECT_EQ(
+                  oldest_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(90000000));
+              EXPECT_EQ(
+                  oldest_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(90150000));
+            } else {
+              EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+            }
+            break;
+          case 2:
+            EXPECT_EQ(
+                oldest_remote_monotonic_timestamps,
+                monotonic_clock::epoch() + chrono::nanoseconds(10000000000));
+            EXPECT_EQ(
+                oldest_local_monotonic_timestamps,
+                monotonic_clock::epoch() + chrono::nanoseconds(1323100000));
+            EXPECT_EQ(oldest_logger_remote_unreliable_monotonic_timestamps,
+                      expected_oldest_local_monotonic_timestamps) << file;
+            EXPECT_EQ(oldest_logger_local_unreliable_monotonic_timestamps,
+                      expected_oldest_timestamp_monotonic_timestamps) << file;
+            if (reliable) {
+              EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+            } else {
+              EXPECT_EQ(oldest_remote_reliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_reliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+            }
+            break;
+
+          case 3:
+            EXPECT_EQ(
+                oldest_remote_monotonic_timestamps,
+                monotonic_clock::epoch() + chrono::nanoseconds(10000000000));
+            EXPECT_EQ(
+                oldest_local_monotonic_timestamps,
+                monotonic_clock::epoch() + chrono::nanoseconds(1323100000));
+            EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                      expected_oldest_remote_monotonic_timestamps);
+            EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                      expected_oldest_local_monotonic_timestamps);
+            EXPECT_EQ(
+                oldest_logger_remote_unreliable_monotonic_timestamps,
+                monotonic_clock::epoch() + chrono::nanoseconds(1323100000));
+            EXPECT_EQ(
+                oldest_logger_local_unreliable_monotonic_timestamps,
+                monotonic_clock::epoch() + chrono::nanoseconds(10100200000));
+            break;
+          default:
+            FAIL();
+            break;
+        }
+
+        switch (log_header->message().parts_index()) {
+          case 0:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          case 1:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          case 2:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          case 3:
+            if (shared()) {
+              EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+              EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+              break;
+            }
+            [[fallthrough]];
+          default:
+            FAIL();
+            break;
+        }
+      } else {
+        switch (log_header->message().parts_index()) {
+          case 0:
+            if (reliable) {
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(
+                  oldest_logger_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(100150000))
+                  << file;
+              EXPECT_EQ(
+                  oldest_logger_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(100250000))
+                  << file;
+            } else {
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+              EXPECT_EQ(
+                  oldest_logger_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(90150000))
+                  << file;
+              EXPECT_EQ(
+                  oldest_logger_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(90250000))
+                  << file;
+            }
+            break;
+          case 1:
+            if (reliable) {
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        monotonic_clock::max_time);
+              EXPECT_EQ(
+                  oldest_logger_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(1323100000));
+              EXPECT_EQ(
+                  oldest_logger_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(10100200000));
+            } else {
+              EXPECT_EQ(oldest_remote_unreliable_monotonic_timestamps,
+                        expected_oldest_remote_monotonic_timestamps);
+              EXPECT_EQ(oldest_local_unreliable_monotonic_timestamps,
+                        expected_oldest_local_monotonic_timestamps);
+              EXPECT_EQ(
+                  oldest_logger_remote_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(1323150000));
+              EXPECT_EQ(
+                  oldest_logger_local_unreliable_monotonic_timestamps,
+                  monotonic_clock::epoch() + chrono::nanoseconds(10100250000));
+            }
+            break;
+          default:
+            FAIL();
+            break;
+        }
+
+        switch (log_header->message().parts_index()) {
+          case 0:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot0);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          case 1:
+            EXPECT_EQ(source_node_boot_uuid, pi2_boot1);
+            EXPECT_EQ(monotonic_start_time, monotonic_clock::min_time);
+            break;
+          default:
+            FAIL();
+            break;
+        }
       }
 
       continue;
@@ -2737,7 +3226,11 @@
     }
   }
 
-  EXPECT_TRUE(timestamp_file_count == 2u || timestamp_file_count == 4u);
+  if (shared()) {
+    EXPECT_EQ(timestamp_file_count, 4u);
+  } else {
+    EXPECT_EQ(timestamp_file_count, 4u);
+  }
 
   // Confirm that we can actually sort the resulting log and read it.
   {
@@ -3108,7 +3601,8 @@
 // when a local message is in the log before a forwarded message, so there is no
 // point in the interpolation function.  This delays the reboot.  So, we need to
 // recreate that situation and make sure it doesn't come back.
-TEST(MultinodeRebootLoggerTest, LocalMessageBeforeRemoteBeforeStartAfterReboot) {
+TEST(MultinodeRebootLoggerTest,
+     LocalMessageBeforeRemoteBeforeStartAfterReboot) {
   aos::FlatbufferDetachedBuffer<aos::Configuration> config =
       aos::configuration::ReadConfig(ArtifactPath(
           "aos/events/logging/multinode_pingpong_split3_config.json"));
@@ -3163,9 +3657,9 @@
     time_converter.AddNextTimestamp(
         distributed_clock::epoch() + reboot_time,
         {BootTimestamp::epoch() + reboot_time,
-         BootTimestamp{
-             .boot = 1,
-             .time = monotonic_clock::epoch() + reboot_time + chrono::seconds(100)},
+         BootTimestamp{.boot = 1,
+                       .time = monotonic_clock::epoch() + reboot_time +
+                               chrono::seconds(100)},
          BootTimestamp::epoch() + reboot_time});
   }
 
diff --git a/aos/events/shm_event_loop.h b/aos/events/shm_event_loop.h
index 71dff4e..e51f21b 100644
--- a/aos/events/shm_event_loop.h
+++ b/aos/events/shm_event_loop.h
@@ -48,10 +48,10 @@
   // Exits the event loop.  Async safe.
   void Exit();
 
-  aos::monotonic_clock::time_point monotonic_now() override {
+  aos::monotonic_clock::time_point monotonic_now() const override {
     return aos::monotonic_clock::now();
   }
-  aos::realtime_clock::time_point realtime_now() override {
+  aos::realtime_clock::time_point realtime_now() const override {
     return aos::realtime_clock::now();
   }
 
diff --git a/aos/events/simulated_event_loop.cc b/aos/events/simulated_event_loop.cc
index 2a276e7..a2428a4 100644
--- a/aos/events/simulated_event_loop.cc
+++ b/aos/events/simulated_event_loop.cc
@@ -584,11 +584,11 @@
     send_delay_ = send_delay;
   }
 
-  monotonic_clock::time_point monotonic_now() override {
+  monotonic_clock::time_point monotonic_now() const override {
     return node_event_loop_factory_->monotonic_now();
   }
 
-  realtime_clock::time_point realtime_now() override {
+  realtime_clock::time_point realtime_now() const override {
     return node_event_loop_factory_->realtime_now();
   }
 
diff --git a/aos/network/message_bridge_server_lib.cc b/aos/network/message_bridge_server_lib.cc
index 2ca8e48..e8e0261 100644
--- a/aos/network/message_bridge_server_lib.cc
+++ b/aos/network/message_bridge_server_lib.cc
@@ -257,7 +257,7 @@
       }) {
   CHECK(event_loop_->node() != nullptr) << ": No nodes configured.";
 
-  int32_t max_size = 0;
+  size_t max_size = 0;
 
   // Seed up all the per-node connection state.
   // We are making the assumption here that every connection is bidirectional
@@ -269,14 +269,14 @@
     // Find the largest connection message so we can size our buffers big enough
     // to receive a connection message.  The connect message comes from the
     // client to the server, so swap the node arguments.
-    const int32_t connect_size = static_cast<int32_t>(
+    const size_t connect_size =
         MakeConnectMessage(event_loop->configuration(),
                            configuration::GetNode(event_loop->configuration(),
                                                   destination_node_name),
                            event_loop->node()->name()->string_view(),
                            UUID::Zero())
             .span()
-            .size());
+            .size();
     VLOG(1) << "Connection to " << destination_node_name << " has size "
             << connect_size;
     max_size = std::max(max_size, connect_size);
@@ -310,7 +310,10 @@
           any_reliable = true;
         }
       }
-      max_size = std::max(channel->max_size(), max_size);
+      max_size =
+          std::max(static_cast<size_t>(channel->max_size() *
+                                       channel->destination_nodes()->size()),
+                   max_size);
       std::unique_ptr<ChannelState> state(new ChannelState{
           channel, channel_index,
           any_reliable ? event_loop_->MakeRawFetcher(channel) : nullptr});
@@ -371,7 +374,7 @@
 
   // Buffer up the max size a bit so everything fits nicely.
   LOG(INFO) << "Max message size for all clients is " << max_size;
-  server_.SetMaxSize(max_size + 100);
+  server_.SetMaxSize(max_size + 100u);
 }
 
 void MessageBridgeServer::NodeConnected(sctp_assoc_t assoc_id) {
diff --git a/aos/network/sctp_lib.cc b/aos/network/sctp_lib.cc
index 33d3352..5bc1b37 100644
--- a/aos/network/sctp_lib.cc
+++ b/aos/network/sctp_lib.cc
@@ -319,6 +319,9 @@
   if (size == -1) {
     if (errno == EPIPE || errno == EAGAIN || errno == ESHUTDOWN ||
         errno == EINTR) {
+      if (VLOG_IS_ON(1)) {
+        PLOG(WARNING) << "sendmsg on sctp socket failed";
+      }
       return false;
     }
     PLOG(FATAL) << "sendmsg on sctp socket failed";
diff --git a/aos/network/timestamp_channel.cc b/aos/network/timestamp_channel.cc
index 88b6ed0..ab61051 100644
--- a/aos/network/timestamp_channel.cc
+++ b/aos/network/timestamp_channel.cc
@@ -27,6 +27,15 @@
   return absl::StrCat("/aos/remote_timestamps/", remote_node);
 }
 
+const Channel *ChannelTimestampFinder::SplitChannelForChannel(
+    const Channel *channel, const Connection *connection) {
+  const std::string split_timestamp_channel_name =
+      SplitChannelName(channel, connection);
+  return configuration::GetChannel(configuration_, split_timestamp_channel_name,
+                                   RemoteMessage::GetFullyQualifiedName(),
+                                   name_, node_, true);
+}
+
 const Channel *ChannelTimestampFinder::ForChannel(
     const Channel *channel, const Connection *connection) {
   const std::string split_timestamp_channel_name =
diff --git a/aos/network/timestamp_channel.h b/aos/network/timestamp_channel.h
index b78c83a..e35a170 100644
--- a/aos/network/timestamp_channel.h
+++ b/aos/network/timestamp_channel.h
@@ -15,6 +15,9 @@
 
 // Class to find the corresponding channel where timestamps for a specified data
 // channel and connection will be logged.
+//
+// This abstracts (and detects) when we have combined or split remote timestamp
+// logging channels.
 class ChannelTimestampFinder {
  public:
   ChannelTimestampFinder(aos::EventLoop *event_loop)
@@ -23,6 +26,11 @@
   ChannelTimestampFinder(const Configuration *configuration,
                          const std::string_view name, const Node *node);
 
+  // Returns the split timestamp logging channel for the provide channel and
+  // connection if one exists, or nullptr otherwise.
+  const Channel *SplitChannelForChannel(const Channel *channel,
+                                        const Connection *connection);
+
   // Finds the timestamp logging channel for the provided data channel and
   // connection.
   const Channel *ForChannel(const Channel *channel,
diff --git a/aos/time/time.cc b/aos/time/time.cc
index a956f94..05510e9 100644
--- a/aos/time/time.cc
+++ b/aos/time/time.cc
@@ -91,11 +91,11 @@
     return std::nullopt;
   }
 
-  if (now.substr(now.size() - 13, 1) != ".") {
+  if (now[now.size() - 13] != '.') {
     return std::nullopt;
   }
 
-  bool negative = now.substr(0, 1) == "-";
+  bool negative = now[0] == '-';
 
   std::string sec(
       now.substr(negative ? 1 : 0, now.size() - (negative ? 14 : 13)));
@@ -119,7 +119,7 @@
     return std::nullopt;
   }
 
-  if (now.substr(now.size() - 10, 1) != ".") {
+  if (now[now.size() - 10] != '.') {
     return std::nullopt;
   }
 
diff --git a/debian/BUILD b/debian/BUILD
index 76bcf28..2aeabf2 100644
--- a/debian/BUILD
+++ b/debian/BUILD
@@ -47,6 +47,10 @@
     python_gtk_debs = "files",
 )
 load(
+    ":opencv_arm64.bzl",
+    opencv_arm64_debs = "files",
+)
+load(
     ":opencv_armhf.bzl",
     opencv_armhf_debs = "files",
 )
@@ -387,6 +391,12 @@
 )
 
 generate_deb_tarball(
+    name = "opencv_arm64",
+    files = opencv_arm64_debs,
+    target_compatible_with = ["@platforms//os:linux"],
+)
+
+generate_deb_tarball(
     name = "opencv_armhf_v4",
     files = opencv_armhf_debs,
     target_compatible_with = ["@platforms//os:linux"],
diff --git a/debian/gstreamer.BUILD b/debian/gstreamer.BUILD
index 205db5a..b203772 100644
--- a/debian/gstreamer.BUILD
+++ b/debian/gstreamer.BUILD
@@ -159,6 +159,7 @@
             "usr/lib/arm-linux-gnueabihf/libxcb.so.1",
             "usr/lib/arm-linux-gnueabihf/libxml2.so.2",
         ],
+        "arm64": [],
         "cortex-m": [],
         "cortex-m0plus": [],
     }),
@@ -181,6 +182,7 @@
             "usr/include/glib-2.0",
             "usr/include/gstreamer-1.0",
         ],
+        "arm64": [],
         "roborio": [],
         "cortex-m": [],
         "cortex-m0plus": [],
diff --git a/debian/opencv.BUILD b/debian/opencv.BUILD
index 0fad68c..c989e22 100644
--- a/debian/opencv.BUILD
+++ b/debian/opencv.BUILD
@@ -266,6 +266,7 @@
             "usr/lib/x86_64-linux-gnu/libnuma.so.1",
         ],
         "@platforms//cpu:armv7": [s % "arm-linux-gnueabihf" if "%" in s else s for s in _common_srcs_list],
+        "@platforms//cpu:arm64": [s % "aarch64-linux-gnu" if "%" in s else s for s in _common_srcs_list],
     }),
     hdrs = glob([
         "usr/include/opencv4/**",
@@ -285,6 +286,9 @@
         "@platforms//cpu:armv7": [
             "@platforms//os:linux",
         ],
+        "@platforms//cpu:arm64": [
+            "@platforms//os:linux",
+        ],
     }),
     visibility = ["//visibility:public"],
 )
diff --git a/debian/opencv_arm64.bzl b/debian/opencv_arm64.bzl
new file mode 100644
index 0000000..6596271
--- /dev/null
+++ b/debian/opencv_arm64.bzl
@@ -0,0 +1,362 @@
+files = {
+    "adwaita-icon-theme_3.38.0-1_all.deb": "2046876c82fc1c342b38ace9aa0661bcb3e167837c984b4bdc89702bc78df5ac",
+    "coreutils_8.32-4_arm64.deb": "6210c84d6ff84b867dc430f661f22f536e1704c27bdb79de38e26f75b853d9c0",
+    "dconf-gsettings-backend_0.38.0-2_arm64.deb": "8d1df67abd7bca93f361ca26f5bb70821c91b5c4d5924941fef524f76fc6d473",
+    "dconf-service_0.38.0-2_arm64.deb": "b76a7d724cc9f18f91cc22aff58afcb090cc7a28ec9b9a21cdd1aa069c7a4cef",
+    "fontconfig-config_2.13.1-4.2_all.deb": "48afb6ad7d15e6104a343b789f73697301ad8bff77b69927bc998f5a409d8e90",
+    "fontconfig_2.13.1-4.2_arm64.deb": "166e5e6d47af2e1a48eff6fb66e89f0e6e0f390d04f7c9abe8b4f36812378267",
+    "fonts-croscore_20201225-1_all.deb": "64904820b729ff40038f85683004e3b94b328d969bc0fbba263c58d635452923",
+    "fonts-dejavu-core_2.37-2_all.deb": "1f67421437b6eb18669d2868e3e02cb88668683d635198142f48aacc5b397118",
+    "fonts-freefont-otf_20120503-10_all.deb": "0b63996c80c6c660424af6d3832818e647960d6f65a51de010bb57dd0762faa7",
+    "fonts-freefont-ttf_20120503-10_all.deb": "4ca1c21ebc479198a3a5879d236c8317d6f7b2f1c403f7890e24c02eead05615",
+    "fonts-liberation2_2.1.3-1_all.deb": "e0805f0085132f5e6dd30f88c0d7260caf1e5450832fe2e3988a20fa9fa2150e",
+    "fonts-liberation_1.07.4-11_all.deb": "efd381517f958b01969343634ffcbdd60056be7779af84c6f53a005090430204",
+    "fonts-texgyre_20180621-3.1_all.deb": "cb7e9a4b2471cfdd57194c16364f9102f0639816a2662fed4b30d2a158747076",
+    "fonts-urw-base35_20200910-1_all.deb": "f95a139adb7f1b60626e76d4d45d1b35aad1bc2c2597394c291ef5f84b5dcb43",
+    "gdal-data_3.2.2+dfsg-2+deb11u1_all.deb": "3ae44cc2f51dccc023f9c3cfbea3411508e24f1335651fa0e6cba74b7b9b87aa",
+    "glib-networking-common_2.66.0-2_all.deb": "a07370151ce5169e48ee7799b9bd9a7a035467a21f5cf3373b2aff090968609c",
+    "glib-networking-services_2.66.0-2_arm64.deb": "7fc232a31f09d8d08242ce08f53286e3b296c7b146834cdb41da75303f5417e1",
+    "glib-networking_2.66.0-2_arm64.deb": "3da5e3ba93a1e9a18a4cb14cfda44f938b270291004f0e88fa7d6316dfc9aebb",
+    "gsettings-desktop-schemas_3.38.0-2_all.deb": "3758968491a770e50cd85122c00d141944ffb43cb7a5c886a37290fef848cee3",
+    "gtk-update-icon-cache_3.24.24-4_arm64.deb": "00ced0c66d177d53b41739a9e8cc79cb891e2c1675c98feabf30014575cd55ce",
+    "hicolor-icon-theme_0.17-2_all.deb": "20304d34b85a734ec1e4830badf3a3a70a5dc5f9c1afc0b2230ecd760c81b5e0",
+    "iso-codes_4.6.0-1_all.deb": "4e044d72a9f810aabf2c8addf126327fa845eaf8e983b51eb6356b9ed5108348",
+    "libaec0_1.0.4-1_arm64.deb": "d71f8f3448d2aa6df31707bb97db9b307305011f9e3ae19a270f2a8858657a45",
+    "libaom0_1.0.0.errata1-3_arm64.deb": "600536f50bf36cbcfabfc8eacb43a8f26bff7a8f8f52304ce35fc1a117dcd06e",
+    "libarchive13_3.4.3-2+b1_arm64.deb": "b3fea698fc1c3ce6448a7c92e2c3197cfa5d24c42bd74b11f168dfcdc025a217",
+    "libarmadillo10_10.1.2+dfsg-6_arm64.deb": "d937aa4788605c00a4cb6e1bfcc233ab53981761e7f04cc6b1c324607f397bbe",
+    "libarpack2_3.8.0-1_arm64.deb": "d9baf93cd93b528c331f9fd23ed0ad8fa901cd15ff6b07c914ceffa051eb02fe",
+    "libatk-bridge2.0-0_2.38.0-1_arm64.deb": "a977c4a2be6d46185cd4f529ba226017dfcdc9a4056ff80ae97f7c67799885b2",
+    "libatk1.0-0_2.36.0-2_arm64.deb": "53c9c45ba719f1df44f5477396d276530e7e2fc210bc598e48e624194b8a1173",
+    "libatk1.0-data_2.36.0-2_all.deb": "86c1acae473977f8a78b905090847df654306996324493f9a39d9f27807778b2",
+    "libatspi2.0-0_2.38.0-4_arm64.deb": "ca3db48d64bdd6f6e26931a5d52ddb73bc6d915957a678bc2779e03aeacf467b",
+    "libattr1_2.4.48-6_arm64.deb": "cb9b59be719a6fdbaabaa60e22aa6158b2de7a68c88ccd7c3fb7f41a25fb43d0",
+    "libavahi-client3_0.8-5_arm64.deb": "b255f51854f9984a9e69a52bbce6cab5ce6141ed7dc232cc810053c228514ce3",
+    "libavahi-common-data_0.8-5_arm64.deb": "59e6d7f43ab387989e4cb116fe35292e07cb6e6a9125b944acd417c288f88f35",
+    "libavahi-common3_0.8-5_arm64.deb": "bd51e2159b0350ffbaf653f44131ce522f0a6eeab0d60c2ccd72182a0001cb5b",
+    "libavcodec-dev_4.3.3-0+deb11u1_arm64.deb": "189dae2788f471312d20bc57587a2fda447eaf992d6ebfb63657f75254e8e424",
+    "libavcodec58_4.3.3-0+deb11u1_arm64.deb": "f86160f51ad8e2feb6bd6a4c241e9724f6e48248a7d6f54f844df08867f196a7",
+    "libavformat-dev_4.3.3-0+deb11u1_arm64.deb": "321ffce7b99458ea358112d16c803465821370328cbda19d2c3edc3c574ed7c2",
+    "libavformat58_4.3.3-0+deb11u1_arm64.deb": "1d8dcb480bf88ab3920eecbed6531210fd756748e869222e7ed9eeb1e806355f",
+    "libavutil-dev_4.3.3-0+deb11u1_arm64.deb": "c39d6ede6e43123843db5d25ae006c2e7c8cae16cc07053550d1430e62f53b86",
+    "libavutil56_4.3.3-0+deb11u1_arm64.deb": "b0e732ed95860f3098ddcfa95b52e865376a5ad279fe34fa61324e472406094c",
+    "libblas3_3.9.0-3_arm64.deb": "99806c4a490d55ae1ba50640460a2f62c491a8920af4e804560849fc5fe9d73f",
+    "libblkid1_2.36.1-8+deb11u1_arm64.deb": "f6daca6d84eab01e281bf59d7c06f55125b8af443da936afdd255fa32f939928",
+    "libbluray2_1.2.1-4+deb11u1_arm64.deb": "ccc3d64b9f3d5aa37223ce9f331b137ebeb10eb26d933bf83179772d2571fb2a",
+    "libbrotli1_1.0.9-2+b2_arm64.deb": "52ca7f90de6cb6576a0a5cf5712fc4ae7344b79c44b8a1548087fd5d92bf1f64",
+    "libbsd0_0.11.3-1_arm64.deb": "b6e7fa54a05e5a3a5e1ec5dceb57a470e9a0883081594aea643ca58264071e7a",
+    "libcairo-gobject2_1.16.0-5_arm64.deb": "971311ba4fa99a4611205b2f7fbb8278b3798faf3409336e6f0b0d32106f7b7b",
+    "libcairo2_1.16.0-5_arm64.deb": "5b18336974b045dda5fbd64799f06247f6b216ee54f3391adc90f0bf81596de5",
+    "libcap2-bin_2.44-1_arm64.deb": "37915f4cc73cfaef7862043f2bdad062d1b1639df04c511665f1f9321c84d0db",
+    "libcap2_2.44-1_arm64.deb": "7c5729a1cfd14876685217c5f0545301e7ff1b839262fb487d6a778e8cd8c05a",
+    "libcfitsio9_3.490-3_arm64.deb": "b538f9fedc7055a6b878f4de16e0a6de96a26c3e03ddc857aeda22da946be7d8",
+    "libcharls2_2.2.0+dfsg-2_arm64.deb": "bbbf7b985fd559d4fb6226dd04a35eb745bbd22285bea98e2550cef785ff55ac",
+    "libchromaprint1_1.5.0-2_arm64.deb": "b2462da2f97fdd9ff14861451309ce3d86c4a579422c41dbdf858b2d1cfd237d",
+    "libcodec2-0.9_0.9.2-4_arm64.deb": "325c2106a587e3c054b31cf6ab8325f61a32f141eee2cea3f2eaa0423b3398d5",
+    "libcolord2_1.4.5-3_arm64.deb": "6b0d8c1debb73d9d4a97f38804e46e384d28869c6d4458d5fe807d207eecf7a6",
+    "libcups2_2.3.3op2-3+deb11u1_arm64.deb": "5badecf1e5c921ae50584378af49e4e185221e855d209ba9f2c4955d825de06d",
+    "libcurl3-gnutls_7.74.0-1.3+deb11u1_arm64.deb": "8e8ae4b7c49ff981aab871069a6457f3413d3648597b26c73500cbe7474141c4",
+    "libcurl4_7.74.0-1.3+deb11u1_arm64.deb": "1ec03ae6ededc45d9bc0906259e72ec27dd91d6d4b50e69904a5c1de13606176",
+    "libdap27_3.20.7-6_arm64.deb": "0df6382891cf0196c94d3d3edfb85991c85cc0345aa007011b69be5f3afce520",
+    "libdapclient6v5_3.20.7-6_arm64.deb": "12dd444ba9a7e2f4949af376dc874cd0cf2bdcb6fc15beba0855de0b8d861088",
+    "libdatrie1_0.2.13-1_arm64.deb": "e2953eec7abf0addebb53d6690a5b52b0e8429492328636e495ce016d819c4c7",
+    "libdav1d4_0.7.1-3_arm64.deb": "4ec26df2faceb35a9d0d821e4ad4a15ff899b07dea83263ccd51d38c57d8c760",
+    "libdb5.3_5.3.28+dfsg1-0.8_arm64.deb": "cf9aa3eae9cfc4c84f93e32f3d11e2707146e4d9707712909e3c61530b50353e",
+    "libdbus-1-3_1.12.20-2_arm64.deb": "2c8e07966435aae522bca50c4d699d940b34931f7906202e83b36982d9783c2b",
+    "libdc1394-25_2.2.6-3_arm64.deb": "09ba9019e1797379a0b662fe24309887a2c6c3e6e8de32f6e33704c65bccdbd4",
+    "libdc1394-dev_2.2.6-3_arm64.deb": "947c7302121d61af8e8b7dfdde910bc2b4342d239fbf5da719bc1b37dc7b5174",
+    "libdconf1_0.38.0-2_arm64.deb": "c89b11d34fbba55da7fc8a43fda7975eec59cd1b65695d485b7139815c44fb65",
+    "libde265-0_1.0.8-1_arm64.deb": "34097553c0c1075d16e59a8514f318000e26961592e135bf9c0f742a49f9f7d8",
+    "libdeflate-dev_1.7-1_arm64.deb": "782494693fb9e1306dfd0933f16d3be46fb1295666f3f7665a585555fddcf842",
+    "libdeflate0_1.7-1_arm64.deb": "a1adc22600ea5e44e8ea715972ac2af7994cc7ff4d94bba8e8b01abb9ddbdfd0",
+    "libdouble-conversion3_3.1.5-6.1_arm64.deb": "63b41ee55d95505e4dcd7ec6f7bb840fdc5a0527f6ecd29069f6ce7ff8536ba2",
+    "libdpkg-perl_1.20.9_all.deb": "134bd00e60fa30d39d5f676d306d6f1d61c7f6ec6086c1785dbc355ce6190f29",
+    "libdrm-amdgpu1_2.4.104-1_arm64.deb": "660f8dced02cb67826e61a91e09101d70c904ff0231d6696d6b45dbd789e86cb",
+    "libdrm-common_2.4.104-1_all.deb": "60c69026fb8e4cfdf8d80a4a86ee30516c611dcc4de4aa1c8ccbf06dff563e2b",
+    "libdrm-nouveau2_2.4.104-1_arm64.deb": "d54e628289717d6751367767e9b1867ed8b863890a64724cda4e76f3be773cde",
+    "libdrm-radeon1_2.4.104-1_arm64.deb": "e8ecdbc425f3ce293d85eb43bd759f657db9eccef24637c4edb36a147fed93cb",
+    "libdrm2_2.4.104-1_arm64.deb": "1dc606aa361307a8c5277c2a5ddedea40a4125874e887c98e82b33dacaa853b0",
+    "libdw1_0.183-1_arm64.deb": "66805e4b00001a0e76ea5291705942b542be53e8d1149867cfbd4cd5fd92285f",
+    "libedit2_3.1-20191231-2+b1_arm64.deb": "43cbfd69ef591a66cfd06aedf930e3fc3c370b3a7ad514a33399d0e1a4d7343e",
+    "libelf1_0.183-1_arm64.deb": "12e14110cd66b3bf0564e3b184985b3e91c9cd76e909531a7f7bd2cb9b35a1f3",
+    "libepoxy0_1.5.5-1_arm64.deb": "cc66fbfd7b08de406873d792b07f17e19e70e37c8255fe33335b57d17379fdd5",
+    "libepsilon1_0.9.2+dfsg-5_arm64.deb": "d39e9ac0d3fe3fc6824c902f9085b70d70af2b40ff4a395bea8937e15516b619",
+    "libexif-dev_0.6.22-3_arm64.deb": "9b002b7311fa12eb59366f77e8c6de17932fad1f7c441f7eec91da2addbd8cd3",
+    "libexif12_0.6.22-3_arm64.deb": "2b2f428d688630275822104dad11d08203640076e91ea2df37dabcf2a6ae7e0e",
+    "libexpat1_2.2.10-2_arm64.deb": "b78b5634cf2826451fbf453039fdbcc91fff62cc3fc51c839537b013f3abc2ac",
+    "libffi7_3.3-6_arm64.deb": "eb748e33ae4ed46f5a4c14b7a2a09792569f2029ede319d0979c373829ba1532",
+    "libfontconfig1_2.13.1-4.2_arm64.deb": "18b13ef8a46e9d79ba6a6ba2db0c86e42583277b5d47f6942f3223e349c22641",
+    "libfreetype6_2.10.4+dfsg-1_arm64.deb": "43e3b1cc3dd70e00f930bca8b69488f5979ab358e53084361424cca7c49a3fa0",
+    "libfreexl1_1.0.6-1_arm64.deb": "8bdabfe5826a662d1082bf58bfb5d15c95082a5438f57239b9e5427b6f4701cf",
+    "libfribidi0_1.0.8-2_arm64.deb": "c6c92f3dbe2f1894b09aa1eeb74933d4dc0df6dacfadaa1ff66d96ee753d41de",
+    "libfyba0_4.1.1-7_arm64.deb": "3a7578a717d1104c55486dbd4a69271b44ce72ce0ac5d34d216417cc171a8dbb",
+    "libgcc-s1_10.2.1-6_arm64.deb": "e2fcdb378d3c1ad1bcb64d4fb6b37aab44011152beca12a4944f435a2582df1f",
+    "libgcrypt20_1.8.7-6_arm64.deb": "61ec779149f20923b30adad7bdf4732957e88a5b6a26d94b2210dfe79409959b",
+    "libgd3_2.3.0-2_arm64.deb": "1e6d6af0c90fe62193b3e51e45f69d075b86d7bde3fb4fd30b60da763aeca43f",
+    "libgdal28_3.2.2+dfsg-2+deb11u1_arm64.deb": "bc5ad6148ce06f07397f07f7a96bc865975248821fb01124184049d741e93c1a",
+    "libgdcm-dev_3.0.8-2_arm64.deb": "1795d571d83196ec248fcdfc6153fe2d21599aa1debf40ebdeec00393764ca0c",
+    "libgdcm3.0_3.0.8-2_arm64.deb": "59472494d90363690f34a7cbe3f5ba66028fab6f242161785401ba7d7c223ef7",
+    "libgdk-pixbuf-2.0-0_2.42.2+dfsg-1_arm64.deb": "e35997839534e69d093cef790dc4a43d0b102e7aed5f21492d330f810717f4ce",
+    "libgdk-pixbuf2.0-common_2.42.2+dfsg-1_all.deb": "61ff764860dafbd7e3fe2050b9c17db3ae109dea15ac748212eff56fdb3111e1",
+    "libgeos-3.9.0_3.9.0-1_arm64.deb": "9f8b32c8814c8ad72df61bc8cdf0eb95b4d625f06fb7a945e146e523c664b874",
+    "libgeos-c1v5_3.9.0-1_arm64.deb": "efffd604a25acd4088a2f34e24c779490f1389f0dd34e2b62cb1e3bc364d8ec9",
+    "libgeotiff5_1.6.0-1_arm64.deb": "94e2329dcc4236aa1bb455613f9b1795b8f8974ac40f8a77568312c677bd14fe",
+    "libgfortran5_10.2.1-6_arm64.deb": "967866fc2caf00fd28a3a515234131d7301043288b4a36e46b7dc16050a6f862",
+    "libgif7_5.1.9-2_arm64.deb": "90751dcd054948236424aac6bddc0666013621fc6d3aec9fba62e275ba12db17",
+    "libgl1-mesa-dri_20.3.5-1_arm64.deb": "13bc343bc0bb7ec8bf52ecceb2c41ac8b70bc80d4d76784252caceadf0c83b73",
+    "libgl1_1.3.2-1_arm64.deb": "18075b79c22b06bb25b9b4f3fa44a42127c40e573b34703af3f4c0b780cae29a",
+    "libgl2ps1.4_1.4.2+dfsg1-1_arm64.deb": "989c066bd91aa9ac18d9d571fba404038e25893da99c0fd86fbecda15be89372",
+    "libglapi-mesa_20.3.5-1_arm64.deb": "2678cf4bcfe4dfeba85deabaf869cb78a2f3263388baaff53e826f52cf17088b",
+    "libglew2.1_2.1.0-4+b1_arm64.deb": "68ed07175805f1b827ff4db50387ca8dfb5bdfffa98e23c897de34706c04b42d",
+    "libglib2.0-0_2.66.8-1_arm64.deb": "667d1b891bcf9b8cc47385c19b39271c74f48fd2b6b457474184f85ce63ea261",
+    "libglvnd0_1.3.2-1_arm64.deb": "5d7a05a966d1df43ca440245dfc7e18a51fc974f665441fc87180a605a0481d9",
+    "libglx-mesa0_20.3.5-1_arm64.deb": "b1f653df820ecda31ed3d6a39429befbced67093dc9b964cbfa5f7991a23eda8",
+    "libglx0_1.3.2-1_arm64.deb": "a53aec87ff8d3ae181c320c01bc190f752a276d5e5ca87e624f9058a4b520bf2",
+    "libgme0_0.6.3-2_arm64.deb": "2324dc1a5c06845f56eac43d9efb2446fb3119758eee46e793e33aa158a3325d",
+    "libgmp10_6.2.1+dfsg-1+deb11u1_arm64.deb": "d52619b6ff8829aa5424dfe3189dd05f22118211e69273e9576030584ffcce80",
+    "libgnutls30_3.7.1-5_arm64.deb": "6b7429aba5f642c258d351b2ebac5a00170f6761331f6b367edfb9401c10d8da",
+    "libgomp1_10.2.1-6_arm64.deb": "813af2e0b8ba0a7cea18c988cd843412ef6d0415700fc860d62816750e741670",
+    "libgpg-error0_1.38-2_arm64.deb": "d1116f4281d6db35279799a21051e0d0e2600d110d7ee2b95b3cca6bec28067c",
+    "libgphoto2-6_2.5.27-1_arm64.deb": "b83525eb32514b47362ab6b8c16f37f002d9a531405b1dbdaed9dd20de19b61a",
+    "libgphoto2-dev_2.5.27-1_arm64.deb": "8a13c502539d0ff3d431f5d1099c784f0ab358c81065af788d0bba65e46ba24f",
+    "libgphoto2-port12_2.5.27-1_arm64.deb": "64892ba635e3f67816748d33700262d73a0b727fd31c89c5f260eceff0c9bbea",
+    "libgraphite2-3_1.3.14-1_arm64.deb": "473362a74ba74ae630fc43675460fb5a1058564a635a301875e00f1c6f9b27cb",
+    "libgsm1_1.0.18-2_arm64.deb": "f7ea67da4f664d6c7b49ca7f31bf6c5fee61e2ff679da61d0613efbb0fdd6cb9",
+    "libgstreamer-plugins-base1.0-0_1.18.4-2_arm64.deb": "276163043a18cabe6b65b6116072cfd0c186b8aa69e5150b1e556edc5c227af2",
+    "libgstreamer1.0-0_1.18.4-2.1_arm64.deb": "0aee9a595e4fe784a59244dfe10567fd6b12cd94c86657a1dd6d07453154d21f",
+    "libgtk-3-0_3.24.24-4_arm64.deb": "245dfac9509c4e5d98386d5a5429f13ba40e2c599bbfff4dc052262d2ce79cb1",
+    "libgtk-3-common_3.24.24-4_all.deb": "c7ce143bed115bc868976538089dc15c0c469ea67cbf84ab412e55d95ee5b488",
+    "libharfbuzz0b_2.7.4-1_arm64.deb": "d9f0345391cc661503d1508ccd318b3db48add354e706cf9d66fa16bf99e2d03",
+    "libhdf4-0-alt_4.2.15-3_arm64.deb": "5ce23c00a3726049c74e149cf08ea46809143eb106a49e38aad973287abafdb3",
+    "libhdf5-103-1_1.10.6+repack-4+deb11u1_arm64.deb": "5f051dfa0c2f6d369e9322fa0c175717c61dce21d3bfa316e0271c949b3b834d",
+    "libhdf5-hl-100_1.10.6+repack-4+deb11u1_arm64.deb": "d3748cffd412ae9dce22016a3a4106a6e0cd2eedf79ae98a79873a0b2f64d9e7",
+    "libheif1_1.11.0-1_arm64.deb": "a8db0c9a9e0311ad2624319f1ef76e89b1c188f424adf101a581a62c28853381",
+    "libhogweed6_3.7.3-1_arm64.deb": "3e9eea5e474dd98a7de9e4c1ecfbfd6f6efb1d40bf51d6473de9713cf41d2191",
+    "libicu67_67.1-7_arm64.deb": "776303db230b275d8a17dfe8f0012bf61093dfc910f0d51f175be36707686efe",
+    "libidn2-0_2.3.0-5_arm64.deb": "0d2e6d39bf65f16861f284be567c1a6c5d4dc6b54dcfdf9dba631546ff4e6796",
+    "libilmbase-dev_2.5.4-1_arm64.deb": "f7efd0776113ae36e03f08d349ba7c17862746c076f54a27f366d0927579923f",
+    "libilmbase25_2.5.4-1_arm64.deb": "9d09d54ad2ceb8148fd1be0fe2e065a7c35ffecb48b329592271aaf860f620e9",
+    "libjbig-dev_2.1-3.1+b2_arm64.deb": "611c5cdf980db966432c39b8ae3cf6449f84e224e9369b7c8fe492d2d48378b1",
+    "libjbig0_2.1-3.1+b2_arm64.deb": "b71b3e62e162f64cb24466bf7c6e40b05ce2a67ca7fed26d267d498f2896d549",
+    "libjpeg-dev_2.0.6-4_arm64.deb": "ce0f75c7f632be0c980d01f2e2ae863089f99bca47e4cff3015a490e1c886d41",
+    "libjpeg62-turbo-dev_2.0.6-4_arm64.deb": "8535e4bd12e026ff79991f69c62ead5ff1750df980a20a6a7ab540587439f06f",
+    "libjpeg62-turbo_2.0.6-4_arm64.deb": "8903394de23dc6ead5abfc80972c8fd44300c9903ad4589d0df926e71977d881",
+    "libjson-c5_0.15-2_arm64.deb": "451820024e4d1023cc74758bc22beccf1aa235227026b3e24bafa67bf81d215e",
+    "libjson-glib-1.0-0_1.6.2-1_arm64.deb": "a2167dd43e00154f2418f19e819cd90db1559f78cd8d783cfac1063bba7509e8",
+    "libjson-glib-1.0-common_1.6.2-1_all.deb": "a938ec35a20dca2e5878a8750fb44683b67a5f7c2d23d383963803a9fcfac1a3",
+    "libjsoncpp24_1.9.4-4_arm64.deb": "b8e04a858f2059d4e9d66b79f359995e38ff8c9509175647d6f98ba1f8b29b2a",
+    "libkmlbase1_1.3.0-9_arm64.deb": "e8c4b8513c10b763ec97133bc1c6180e027aeddeaba8965cbed8fba790a8a30b",
+    "libkmldom1_1.3.0-9_arm64.deb": "301808035031ac8c3c39baf0a854a3fc4c9e4de36a45ba5cd9abf4b06cbe4432",
+    "libkmlengine1_1.3.0-9_arm64.deb": "a6cf70031c33b85d5be04d1ad0ab4ff4b580674431e1ec0ba0596017b75045cf",
+    "liblapack3_3.9.0-3_arm64.deb": "30858f2b44bd6e24100bf119ab7e4da0e9db9df1ade3957e65952d95d4c308e6",
+    "liblcms2-2_2.12~rc1-2_arm64.deb": "6d92ee1f0d427b88ab9bff32c769b61e2597c8fb289589ca0731a7e77d490d6e",
+    "libldap-2.4-2_2.4.57+dfsg-3_arm64.deb": "443959ae0679f73a1587e26f0f5a78e2c9909d1328c0f267c71da349dfccfe8e",
+    "liblept5_1.79.0-1.1_arm64.deb": "134c600adc419c058777f87dd5254932a03e4591e6e230a290747657d7557390",
+    "libllvm11_11.0.1-2_arm64.deb": "0f4d1c3e259e203cc25ca5e07a4f88a018a775bdbf4376f33b06c8ffb7bb688b",
+    "libltdl7_2.4.6-15_arm64.deb": "e13bc091b1493c3bbb0e6554e73bd4d913358fdcd267152d1dcb32a5a3b94e27",
+    "liblz4-1_1.9.3-2_arm64.deb": "83f0ee547cd42854e1b2a2e4c1a5705e28259ee5fa6560119f918f961a5dada2",
+    "liblzma-dev_5.2.5-2_arm64.deb": "44c7d3b32401fa0406fd9e271b10038342b7d04bba377a917847b063eb428e9b",
+    "libmariadb3_10.5.12-0+deb11u1_arm64.deb": "822972508938f2bcc2e5afc392a69259cab349968fc16e5249b25bd0e2025461",
+    "libmd0_1.0.3-3_arm64.deb": "3c490cdcce9d25e702e6587b6166cd8e7303fce8343642d9d5d99695282a9e5c",
+    "libminizip1_1.1-8+b1_arm64.deb": "9db7fb955cc168a591fa5cc052f02606a39333ce4b15177426baf2d956018400",
+    "libmount1_2.36.1-8+deb11u1_arm64.deb": "fd1dff93bdaba84d3f45f25448e2ada8c867674cd4e8af9fe25604ddf9a2f8de",
+    "libmp3lame0_3.100-3_arm64.deb": "ad02a132721a5569476235b3fbe8f88af0c9e06fa37d8d68f5eda604cfe8ec92",
+    "libmpg123-0_1.26.4-1_arm64.deb": "4a7351593002fc11acc50d4d86a457332b6f3de488491803e1c3eafdeb440630",
+    "libnetcdf18_4.7.4-1_arm64.deb": "48e40a3ed8d48043996d858b8a28277f239cb43685cf13f047b3084aaeec0794",
+    "libnettle8_3.7.3-1_arm64.deb": "5061c931f95dc7277d95fc58bce7c17b1a95c6aa9a9aac781784f3b3dc909047",
+    "libnghttp2-14_1.43.0-1_arm64.deb": "317b58d2654d5875eee1dbf147ea810b8e3eb007f3bf4e2dcbca8ed76e425763",
+    "libnorm1_1.5.9+dfsg-2_arm64.deb": "3974ddba57ed9bbfff10a144baae64d647adbb38ebce5fa122e4f0dd16b77866",
+    "libnspr4_4.29-1_arm64.deb": "f83f450a49499e320b1d7723a596be2414bf7adfd20315ba72df0e7735d98bc5",
+    "libnss3_3.61-1+deb11u2_arm64.deb": "c10ac0797a3c5f694ab86251d22d8d1452b03e50fc30999edd91a45bc202696d",
+    "libnuma1_2.0.12-1+b1_arm64.deb": "4eda519ae1f36f6376380fb2798ca0f50e104930845d8c51561ec455e98c57fc",
+    "libodbc1_2.3.6-0.1+b1_arm64.deb": "bd0060e4333b038300fb9f4a225adebbdd5054fb6298960eef36f52c50733bc8",
+    "libogdi4.1_4.1.0+ds-5_arm64.deb": "48903ccacb992d04131def3accceac424f6226aa4059201303131cf89eaeddbe",
+    "libogg0_1.3.4-0.1_arm64.deb": "910d1f3893a9340ea83bf19deebbc4e0d2362f22c274c2c2d3f00e4ba386c871",
+    "libopencv-calib3d-dev_4.5.1+dfsg-5_arm64.deb": "9b32b570cb47735db6879a925956168e85155ef048d9895c7cbce014ef9b4529",
+    "libopencv-calib3d4.5_4.5.1+dfsg-5_arm64.deb": "189fbfc0fa850bf8ee4f3484c5a9a71d38bf1ba7694ebe0a218cf398a755f1d5",
+    "libopencv-contrib-dev_4.5.1+dfsg-5_arm64.deb": "36838790208c16db93236aee39bf77fee8ab8bff6ef4b35c22e9ebfd0d900099",
+    "libopencv-contrib4.5_4.5.1+dfsg-5_arm64.deb": "7d80388fc6ce9662a2706cd0371ec1b75b2a0473cc17536d6bb329c597317e65",
+    "libopencv-core-dev_4.5.1+dfsg-5_arm64.deb": "b8d8873d726345a0029f524dd7791b581a8379e1cd6e3aed6cc462b47c495fe0",
+    "libopencv-core4.5_4.5.1+dfsg-5_arm64.deb": "4736fc101e6fffd1f11f7dba48a1fc72aca5683cac123c25160718628a682e9f",
+    "libopencv-dev_4.5.1+dfsg-5_arm64.deb": "cca973f700a3177cfeb77bbdf0fbb34d49a894b93719b3e3fc13dea145ed9d6b",
+    "libopencv-dnn-dev_4.5.1+dfsg-5_arm64.deb": "e506228fc13a8533bec5a88b6b73cc931f31e0032218465dac81d2fb659d8988",
+    "libopencv-dnn4.5_4.5.1+dfsg-5_arm64.deb": "0c121b36ba1cd55c9355aa9f2dc42b78afe83a5ab3734e3a5b16ba68ae5ef7d1",
+    "libopencv-features2d-dev_4.5.1+dfsg-5_arm64.deb": "d9cc52691782a9e0714814f33ce177cfe56d78e2b6a828e920a0739286806ae3",
+    "libopencv-features2d4.5_4.5.1+dfsg-5_arm64.deb": "ac5c82fff13f36eea33fb92e04cfd7078642cca527382ce8cb2e5e7c75b1d698",
+    "libopencv-flann-dev_4.5.1+dfsg-5_arm64.deb": "ef70c57db440c2805be002d5638fb381727e9b0c9faf3462857565884eed81b9",
+    "libopencv-flann4.5_4.5.1+dfsg-5_arm64.deb": "20c490a68d367e4ac47b443850697757d97dc30e74ffd31b0025841f2829ad80",
+    "libopencv-highgui-dev_4.5.1+dfsg-5_arm64.deb": "27c41d0d726870b9245327857e6207657971b023d5fe251fd0a539ff84ce69ba",
+    "libopencv-highgui4.5_4.5.1+dfsg-5_arm64.deb": "5af44ec6da17dc8d000215ff9377a6eff00ab7970c078f3cd46492ceec1be7c8",
+    "libopencv-imgcodecs-dev_4.5.1+dfsg-5_arm64.deb": "27ec650759d661651dbfa9fe9d571b106048f0e6a7863c6fa8e9c224b6c64ad6",
+    "libopencv-imgcodecs4.5_4.5.1+dfsg-5_arm64.deb": "8d68e9a89517628082fb5d58d47d865c69c7e070bd91fcab8240d8f082e85e54",
+    "libopencv-imgproc-dev_4.5.1+dfsg-5_arm64.deb": "22fd1fd47d57f4085472653b420fcfd8d4180d85ccfb8b9ffaab7e110643fbe0",
+    "libopencv-imgproc4.5_4.5.1+dfsg-5_arm64.deb": "fcbf06a539696d53a1386a1609f51c0890a7d58a58fd67e153facd8e13728754",
+    "libopencv-ml-dev_4.5.1+dfsg-5_arm64.deb": "fb7022c93d9c4f4bc050d9f3ca545133d1d5a70efcc692ef7fc78f4d902fcf5c",
+    "libopencv-ml4.5_4.5.1+dfsg-5_arm64.deb": "b7eb23cc968b4409d044addcad4b534282fc208eb4958b811f4404eebb3c52a7",
+    "libopencv-objdetect-dev_4.5.1+dfsg-5_arm64.deb": "db41fa5f2a3eee924d2bb9cad42e8c416a66f87a498562c4399e6690176d61d0",
+    "libopencv-objdetect4.5_4.5.1+dfsg-5_arm64.deb": "9f3784842133a8ffc764a72572b8631049d8627ea8d7c120ced7e44f8a981641",
+    "libopencv-photo-dev_4.5.1+dfsg-5_arm64.deb": "13db446289d550a9dbf7214e49cbb46c895331c3a7fc7813594423b9f9d6bd3c",
+    "libopencv-photo4.5_4.5.1+dfsg-5_arm64.deb": "af684d483ab247c47941682ec6acd5d045438ccca8085c442973529da783cb4f",
+    "libopencv-shape-dev_4.5.1+dfsg-5_arm64.deb": "04313d5b4408784e50e314fcdf27002cba2c19c8b28d1ef3d4b3ea75d92ca303",
+    "libopencv-shape4.5_4.5.1+dfsg-5_arm64.deb": "b30cf313926d1064a676c857b2f57afba78e0a3942bdd0993b83eb17144b9a8d",
+    "libopencv-stitching-dev_4.5.1+dfsg-5_arm64.deb": "e0998b49adf404ff376c725bb6ad62663ef50bac4ec9bacebeda9fc19fe61dc8",
+    "libopencv-stitching4.5_4.5.1+dfsg-5_arm64.deb": "2316563a7b487d0b245f84e87eb0db61423f3f730c474a7776d3910ac1a1b210",
+    "libopencv-superres-dev_4.5.1+dfsg-5_arm64.deb": "f8524762200001fbe8acff2d2dd1e110ac37ed5e124837778d0acb703fdc9d22",
+    "libopencv-superres4.5_4.5.1+dfsg-5_arm64.deb": "1da58f51aa2ca2fe8a425a7f6f84ee5252c90fef10cfc90d2ccd2190ebaa8e43",
+    "libopencv-video-dev_4.5.1+dfsg-5_arm64.deb": "a37ea4ef728084858601f8a61b018e28e146951311dd55b4b75b83e4788b8f49",
+    "libopencv-video4.5_4.5.1+dfsg-5_arm64.deb": "c49598f7fe96b473c8a6c0e091a407a50f1b9514c398800497c0ae2691be3dc7",
+    "libopencv-videoio-dev_4.5.1+dfsg-5_arm64.deb": "60464425514a01b82dc4798d777d17cb9802119b05283cc4a44623a60bc98a48",
+    "libopencv-videoio4.5_4.5.1+dfsg-5_arm64.deb": "ab79662142f6aa93b54c30eb0f388da3445fc11fb56b7888825b9b6bde2a7536",
+    "libopencv-videostab-dev_4.5.1+dfsg-5_arm64.deb": "2c517c7d4394cd702e417c33b7fa5154ba650d2850ee2530dcc3b86943a471a7",
+    "libopencv-videostab4.5_4.5.1+dfsg-5_arm64.deb": "ce078bd6fe7da56efbdb4b03b7bde1db3fdf8923a70e99528e9eaf3a966e89b5",
+    "libopencv-viz-dev_4.5.1+dfsg-5_arm64.deb": "e0ca0f7422c742889831ee3a9836e6efa15beef70784e7a93f03187efd073d34",
+    "libopencv-viz4.5_4.5.1+dfsg-5_arm64.deb": "fb92c16c42a4faf7f3393f82428865929225a073894100f75c4535cf56e06f97",
+    "libopenexr-dev_2.5.4-2_arm64.deb": "fc0a9ffdda657e668ee164ffeebfbc27f9c5fb80556567f9c25bd1cbec05ff8d",
+    "libopenexr25_2.5.4-2_arm64.deb": "15bc59e1f8abf11d17e9b028a54b15f8f9b9448b6ea98175ddfb3acdd6f8ecd4",
+    "libopengl0_1.3.2-1_arm64.deb": "0dcd4846079fd8106f92f943331e4d035b3c79d9eae5a337135ced490a0deb15",
+    "libopenjp2-7_2.4.0-3_arm64.deb": "4fb3637093bbbde4499f1344b399c6eb61bbe51bdc3c40a09c5fcc1efec660cb",
+    "libopenmpt0_0.4.11-1_arm64.deb": "a78a20536f771b360f757999dc02e2c6c9033491afd5f7875d0561ec28caf19e",
+    "libopus0_1.3.1-0.1_arm64.deb": "86d96e6e99820be150e4e1d335cf8503c5802a3ac47103ba25eebf77a0699a13",
+    "liborc-0.4-0_0.4.32-1_arm64.deb": "45948981ff4afa3890eca22edf709445088b301b593a3ad3eb5f8e524f661261",
+    "libp11-kit0_0.23.22-1_arm64.deb": "ac6e8eda3277708069bc6f03aff06dc319855d64ede9fca219938e52f92ee09c",
+    "libpango-1.0-0_1.46.2-3_arm64.deb": "6bbb5d942bf5075e07ba2290687cf03939e29dd89c67cba4d868a5d5ca94d360",
+    "libpangocairo-1.0-0_1.46.2-3_arm64.deb": "6947061235f6a3fce541b985ae38509298f7b46299e31fd985d7c596e31e4bbf",
+    "libpangoft2-1.0-0_1.46.2-3_arm64.deb": "75c9b7f28b80822b6e4edea5e2235257f877ac6cade7930c6683e24395e952ec",
+    "libpcre3_8.39-13_arm64.deb": "21cac4064a41dbc354295c437f37bf623f9004513a97a6fa030248566aa986e9",
+    "libpgm-5.3-0_5.3.128~dfsg-2_arm64.deb": "6a632aa13cafe154d6212a57710f058405dd895fb62d464e16961b28d7325cbb",
+    "libpixman-1-0_0.40.0-1_arm64.deb": "0be923132af8fa7102c09a3c8d200cd8475b633a9a5f1609f7ad653851fb6448",
+    "libpng-dev_1.6.37-3_arm64.deb": "15da2d4389f62ac5daf0f91f0a34db8007986ce77f079ea1f6f2ee92ea6a620d",
+    "libpng16-16_1.6.37-3_arm64.deb": "f5f61274aa5f71b9e44b077bd7b9fa9cd5ff71d8b8295f47dc1b2d45378aa73e",
+    "libpoppler102_20.09.0-3.1_arm64.deb": "cb9d93e535446d9662722614109a3d9c2df89dd970a473623f31895c37be1513",
+    "libpq5_13.5-0+deb11u1_arm64.deb": "84daa5d6407c51e5cab185d053c896c58a6035e05157adb197d077f2d417f7af",
+    "libproj19_7.2.1-1_arm64.deb": "535a698960d6fe73de45099fa289b7e9382319b8ea3ab3ec535db52f893e7af2",
+    "libprotobuf23_3.12.4-1_arm64.deb": "f3935bf86962ef3df68aca17c9f1fa0a55a9a0c74a78a225c2dbf78ed5746cfa",
+    "libproxy1v5_0.4.17-1_arm64.deb": "b81ca7f47e384ed7117a75cfe9798bd11d0085b1b5edce4f9ffcf4e678e24b71",
+    "libpsl5_0.21.0-1.2_arm64.deb": "12637647316e770c37a4bfec7aef27ed472f2850b5f59dd508505dda32519584",
+    "libqhull8.0_2020.2-3_arm64.deb": "e42e31c799c6017158dcc6b5fce9ccc68550b54255df50f52c947a34f2a63943",
+    "librabbitmq4_0.10.0-1_arm64.deb": "9460d0c3c016883bd5bfb5e66a47cae445f93e2e6f43c1c951738ab51d2ec982",
+    "libraw1394-11_2.1.2-2_arm64.deb": "81a1e9bd2b790aa7ce7c426dd1742d3310894b01f124bc41962331b834da6cc3",
+    "libraw1394-dev_2.1.2-2_arm64.deb": "84d7280c5e0d38a77c7d8ccc6d5d2986b42bd64245533947aa9c684077fa7243",
+    "librest-0.7-0_0.8.1-1.1_arm64.deb": "c1b636d7a66996743d248cb9e6042167b03b442150d39b349b12348a31690ee0",
+    "librsvg2-2_2.50.3+dfsg-1_arm64.deb": "79688f922da4c45e546c77667f1ea9480e3b96dd65e8eef88bcb44ef335cd6bd",
+    "librtmp1_2.4+20151223.gitfa8646d.1-2+b2_arm64.deb": "a3a1a6e4b02bcd3254e932b1972ed744082fd7dd5cc1545eec3dd3d539ce6c93",
+    "librttopo1_1.1.0-2_arm64.deb": "f20685ed048635be16e8abf790e14d7e88bb4e2335d371dff631b4adbdebc6a8",
+    "libsasl2-2_2.1.27+dfsg-2.1_arm64.deb": "e96088393cfe034560c2d3f84931b7e17d69f8af4faf34e3cb790560a1ae937d",
+    "libsasl2-modules-db_2.1.27+dfsg-2.1_arm64.deb": "71db9d46ed83c02d1cb3ed51b0a7aedddc54ce60ae00fd846c3dd26989a91089",
+    "libsensors-config_3.6.0-7_all.deb": "4265811140a591d27c99d026b63707d8235d98c73d7543c66ab9ec73c28523fc",
+    "libsensors5_3.6.0-7_arm64.deb": "fd5533c6293d881d67e488ad0549c0b0351e1b770037a08018e5e88996cb95dc",
+    "libshine3_3.1.1-2_arm64.deb": "d64982ddfbdfcef714dc35a760a752ab1f3739aaee8011bc5c3f03afa73f465f",
+    "libsnappy1v5_1.1.8-1_arm64.deb": "48cb00030ca2e87780f815e6b5e354c76642c6de5c7a287a18d70b24880c70d1",
+    "libsocket++1_1.12.13-11_arm64.deb": "ea74ce1260340fedc20131f4f8183c5ee55fdc4e153c2850bceb0fc7173f4de3",
+    "libsodium23_1.0.18-1_arm64.deb": "f00a5c6ccaeb6a70bc07c7a56d61c605281c2b1cbea29611b69dfc789274db0c",
+    "libsoup-gnome2.4-1_2.72.0-2_arm64.deb": "7e0aacd0e2e484844b86fce2dcaf842ff95f209ad353c52d685ed1b2bc346eb2",
+    "libsoup2.4-1_2.72.0-2_arm64.deb": "c11fd27b2cd0639d642d1b852c6e89f94742228d5a866d1e32f122bdb2b324d1",
+    "libsoxr0_0.1.3-4_arm64.deb": "8f1b1780a5ced7a0821a8040d0f3ab5815acd5d2f38b12cca915a9a9d0ee28b7",
+    "libspatialite7_5.0.1-2_arm64.deb": "0268c4e54141c222e6ae6984f1c402f78a806e0e70e411235524445b0e2beb2a",
+    "libspeex1_1.2~rc1.2-1.1_arm64.deb": "5bdef83a0a8f0afda640f0166a64f9160c459faa0c2681f9cef7a96933733340",
+    "libsqlite3-0_3.34.1-3_arm64.deb": "1e33cd39dc4fff2a7edd7bb7e90a71e20fb528f6a581fe0287652e4dae77e0d0",
+    "libsrt1.4-gnutls_1.4.2-1.3_arm64.deb": "08a94b07becea150a1385614fb7b4c1b09f864db5ab8185b586d099ffdf11b8a",
+    "libssh-gcrypt-4_0.9.5-1+deb11u1_arm64.deb": "ab5a221194b84dbbb75961555efd98eca8d84d568a15dd971a8c5579c0c4d9dd",
+    "libssh2-1_1.9.0-2_arm64.deb": "c3847ce093a395c4425f23c0a1601516248e2d241bedaab94ecd9686536214a7",
+    "libstdc++6_10.2.1-6_arm64.deb": "7869aa540cc46e9f3d4267d5bde2af0e5b429a820c1d6f1a4cfccfe788c31890",
+    "libsuperlu5_5.2.2+dfsg1-2_arm64.deb": "28d7257ec368508de05df08d87bdf1a535469758744e20ed93fc7c401b2740b9",
+    "libswresample-dev_4.3.3-0+deb11u1_arm64.deb": "790d775017119434a6eddbb68b85e29392119aaae5ed52bbe7a2734a45cb0b64",
+    "libswresample3_4.3.3-0+deb11u1_arm64.deb": "58ae7a8c97a8763ef3e3352915f65b9bb2bbfa1c977042fb7761a89c2f1f1e93",
+    "libswscale-dev_4.3.3-0+deb11u1_arm64.deb": "bbf45093f7ca81b3b3f3cfdcac5c01546e0ae5e7f99029361ab1b78672917c73",
+    "libswscale5_4.3.3-0+deb11u1_arm64.deb": "e29e8d8a7e4f1ad8f71e1c0b05859e1fe6840cbe36a3955aaf8536b587dfd028",
+    "libsystemd0_247.3-6_arm64.deb": "d75536a275b2cca3054459ae5d1a54ff7bdd4e9bf7a389e02f07bce57ef605f0",
+    "libsz2_1.0.4-1_arm64.deb": "8ed09313ff4408291fd0e3942b4129280babf882f2d3fe8ba48aac4be6def051",
+    "libtasn1-6_4.16.0-2_arm64.deb": "a89b659a3cf2d040885a7d00f3c547b6e362cdfeb5f89d0c777495d82a58e64f",
+    "libtbb-dev_2020.3-1_arm64.deb": "85bc6b8697aac9f2c481589de3471ca44402c383b3738304fc325ea8ec776b14",
+    "libtbb2_2020.3-1_arm64.deb": "c69b242838b3829c16b9580fa218387f0f0f7f976062df8a66c7d9c0a6e95a9e",
+    "libtcl8.6_8.6.11+dfsg-1_arm64.deb": "39047359624bb8229a0e26291ad56012ae6ad17e443e73dd9f38635102c9a0e4",
+    "libtesseract4_4.1.1-2.1_arm64.deb": "762932cc1bc533cf6d1f6ad08c108fb1f5f13131cc4c5a37ce777e5715fb56a0",
+    "libthai-data_0.1.28-3_all.deb": "64750cb822e54627a25b5a00cde06e233b5dea28571690215f672af97937f01b",
+    "libthai0_0.1.28-3_arm64.deb": "e7ac0d861936385cca0ea7e3b9b04d20e85a7dfbfaa801e093d9f7fcbcf841f6",
+    "libtheora0_1.1.1+dfsg.1-15_arm64.deb": "e1ca65eaa5c90af2f88a5ba157e4b61b38b3cdf7ca8b83f20db4ee2dc271c344",
+    "libtiff-dev_4.2.0-1_arm64.deb": "279b1e5116d83babba81f28cf87ffd2d4c6abe2f1d76a08936c60e4618f81a7a",
+    "libtiff5_4.2.0-1_arm64.deb": "31765435a432e1804e93d9b20a5f2733f23e602ecf6fab1336eb04c30aeae043",
+    "libtiffxx5_4.2.0-1_arm64.deb": "34375252037a41491957e0a5060383f058be9ba1543760fccace516ae2b6e2bb",
+    "libtinfo6_6.2+20201114-2_arm64.deb": "21c0c33e00d091d0f326a083a77531270b8c56468500f0948d149f3e20b95385",
+    "libtk8.6_8.6.11-2_arm64.deb": "c189bdef93e4caebd58f952e99dfd1922ca182996fd4d0b4b33fb5fa5828dfa8",
+    "libtwolame0_0.4.0-2_arm64.deb": "73780c555796569a252ec5cf1ea919e59ef4031298bc4be1516a6a0e83f06b5a",
+    "libudev1_247.3-6_arm64.deb": "97236008d585a478916e6e1bb0f3edfe2f32336a48e2a95aba19dce801ed18d9",
+    "libudfread0_1.1.1-1_arm64.deb": "a54c888952f0eab05024e26a09cef78182144f7f11763af5d24c3947a8002648",
+    "libunistring2_0.9.10-4_arm64.deb": "53ff395ea4d8cf17c52155a452a0dc15af0ee2fa5cb3b0085b9c7335de8d5f7f",
+    "libunwind8_1.3.2-2_arm64.deb": "2a93283482cd89b6e6d3e1ee16497c911d6aacdc7414b9d24ca6851e221cf66c",
+    "liburiparser1_0.9.4+dfsg-1+deb11u1_arm64.deb": "4a184076b07eb3f6a409db354b651b5c024e3c783e35702038782c4300dbd037",
+    "libusb-1.0-0_1.0.24-3_arm64.deb": "61ca0a0412c4182cb007f4e447608857cd4c70ddbe730ecda15a495de7d2178f",
+    "libuuid1_2.36.1-8+deb11u1_arm64.deb": "3d677da6a22e9cac519fed5a2ed5b20a4217f51ca420fce57434b5e813c26e03",
+    "libva-drm2_2.10.0-1_arm64.deb": "45bf4248d72ce42940ee658bbba3025db0a233587bc49d773b1e3e9606d613a3",
+    "libva-x11-2_2.10.0-1_arm64.deb": "d6a845c9456a8b22366a25cbcd68a4ced425243a924e4b0048dc82f8b1f8853c",
+    "libva2_2.10.0-1_arm64.deb": "6dbb204d43bcaed495719fc6a793dcbce77f4ab12bc0ea4617ae4e816139d5a1",
+    "libvdpau1_1.4-3_arm64.deb": "9f44679cee3e9ed99525b2becff5b0ef040736b1a450e68fd3e2e536de52b99a",
+    "libvorbis0a_1.3.7-1_arm64.deb": "2f902ae456bcada7b0d494d7bd7c994feb81c4158209d8a12c0b2d9e255edda7",
+    "libvorbisenc2_1.3.7-1_arm64.deb": "f1089e220c81e267caec859bf2e440bb78ed9f318bbb51cfd6c85d35bf80144b",
+    "libvorbisfile3_1.3.7-1_arm64.deb": "f8f418e15f99905d4a2d532617511a11d700e814f8ead1a883deea2f7241970c",
+    "libvpx6_1.9.0-1_arm64.deb": "878f04bcd1089f8f2aa47d20b7ec4922877e31e562c90f613756d5452b3814fd",
+    "libvtk9_9.0.1+dfsg1-8_arm64.deb": "3271f972b20a26fed4781466dbaff083833c30208ac7b71376d96dec2a402aa2",
+    "libvulkan1_1.2.162.0-1_arm64.deb": "303d48229ccab57342ad9cc36174429ee7b8b0cdb6e6e292b87ce3415fe5d65a",
+    "libwavpack1_5.4.0-1_arm64.deb": "81317be561d0cdb1239225772a8a9260d8178634a41263f072c340448c029490",
+    "libwayland-client0_1.18.0-2~exp1.1_arm64.deb": "7bde74828c3508b5b81400c4b9ef51d65b566d7645747b777ec171eb8ecfce47",
+    "libwayland-cursor0_1.18.0-2~exp1.1_arm64.deb": "1c201b449abf41e864dd48c960c9ef7f180b5164a3e7562ae5fd467d60fd074e",
+    "libwayland-egl1_1.18.0-2~exp1.1_arm64.deb": "8b5e2d8975330f308c226e3b51624a76ea8ebe5115dd8e929e05ea23c4169008",
+    "libwebp6_0.6.1-2.1_arm64.deb": "c4e7e63f283aaa9913ac78b9871434f543f87ff4641a9a1737e86a0b32a679a7",
+    "libwebpmux3_0.6.1-2.1_arm64.deb": "bf59f5c2e958af997f9754f78de0ed48178b4c33688c354b52ffdcb970876ca8",
+    "libx11-6_1.7.2-1_arm64.deb": "8872962f2a0a6b9e16cafc6acd2be56cee4ec7a16c2a0abdb9fcda6d0b31be3b",
+    "libx11-data_1.7.2-1_all.deb": "049b7eabced516acfdf44a5e81c26d108b16e4987e5d7604ea53eaade74027fb",
+    "libx11-xcb1_1.7.2-1_arm64.deb": "4714e973f35a3fa8e4687ec86d9239bead13ebdc3990173d5520afa7504ab65d",
+    "libx264-160_0.160.3011+gitcde9a93-2.1_arm64.deb": "3e379d7147e548a43f8a4ca4fb291d839d0a3d8cc07df73e5cbc88b856818b92",
+    "libx265-192_3.4-2_arm64.deb": "4da85b00f95645ab01832af0bec627534608481a6f181c0fbbabeb5c04c2a1cc",
+    "libxau6_1.0.9-1_arm64.deb": "36c2bf400641a80521093771dc2562c903df4065f9eb03add50d90564337ea6c",
+    "libxcb-dri2-0_1.14-3_arm64.deb": "b99d1f4909d0a3fe85dfba6524fb0a8c4d08703366ed99f034417684e91ded5b",
+    "libxcb-dri3-0_1.14-3_arm64.deb": "9a6dc75fd8e9fe720f62f99cfebef0cbe51cf2909aa5e777254cb6a3ddf47f6f",
+    "libxcb-glx0_1.14-3_arm64.deb": "754902f3399fd5c9c851dbf150306f537acab7152b9f32206a7910b7c3f353e1",
+    "libxcb-present0_1.14-3_arm64.deb": "9e84b49e46833fd3fb366b04d19c652a0e8d2ebe3d40f19f9b530763ba2f7b88",
+    "libxcb-render0_1.14-3_arm64.deb": "e794ba2657c5f21dcca327343b41b1997a150b6ac27977970404d60f471be48a",
+    "libxcb-shm0_1.14-3_arm64.deb": "e7f59fc41744fe6b8b9ba97b262a051621173689e2a3e5ebb26dc253c9bdc48b",
+    "libxcb-sync1_1.14-3_arm64.deb": "ba70a2194fa7875f1a3ef6fb31bdd4cca5ca970926e3201994a166c5026ce442",
+    "libxcb-xfixes0_1.14-3_arm64.deb": "8185fca98fda89fc5a020b9a8cd92b093929830a29cf92e6a1eba04bd88bcf5f",
+    "libxcb1_1.14-3_arm64.deb": "48f82b65c93adb7af7757961fdd2730928316459f008d767b7104a56bc20a8f1",
+    "libxcomposite1_0.4.5-1_arm64.deb": "cfe39326fdb822e9d060ed5eb3f95b14459dd6b73793c5290000f9b27f8bad37",
+    "libxcursor1_1.2.0-2_arm64.deb": "2e309833ccf7e6b62560240cd84e325cafcb2ce70b1fb297469957360cee4478",
+    "libxdamage1_1.1.5-2_arm64.deb": "696cb56f414e7c0ea9a3bcbcb63b07f6ed8e980023c1b35006d5c1dc0d0213ed",
+    "libxdmcp6_1.1.2-3_arm64.deb": "e92569ac33247261aa09adfadc34ced3994ac301cf8b58de415a2d5dbf15ccfc",
+    "libxerces-c3.2_3.2.3+debian-3_arm64.deb": "59311a05cc1d2c693840db899c02c0e8d1ba2e9e66961e8284053255d4c38866",
+    "libxext6_1.3.3-1.1_arm64.deb": "57237ecf54662372e206b154c0ab6096e05955e048552575b45d3ad14a6ff6e5",
+    "libxfixes3_5.0.3-2_arm64.deb": "eb70f12af1d13e1632ee29ddf103617af00a078faf6c3a2531ab3d01b395606b",
+    "libxft2_2.3.2-2_arm64.deb": "6941176bcc78bf02d1635575a8cc726a7eb0628d8476efca7607718bdc1f50c5",
+    "libxi6_1.7.10-1_arm64.deb": "0a6788844441f160d970fc7d61004607fe92cfad8966d0b371291703201b3971",
+    "libxinerama1_1.1.4-2_arm64.deb": "bbdef95d025fb804797a5b1a71f319f5f74c0114e90d759b3e9ecf7654442598",
+    "libxkbcommon0_1.0.3-2_arm64.deb": "91c19f642af34ae8cb909d00d08bc83f0b4f8e87ddde6984bd8bff0f7bf83204",
+    "libxml2_2.9.10+dfsg-6.7_arm64.deb": "655b9374cdb737f2c12e021cb0a705c8225b25f5a9bee2eeab9c485303cd115e",
+    "libxpm4_3.5.12-1_arm64.deb": "48ae9f8f91e36956e25bf724fc0fb815ce6202ca610570bd6eb5a077f3580b5a",
+    "libxrandr2_1.5.1-1_arm64.deb": "04c9c59ba1e9648e0ec80d66fa34bab7bf039822f32eb31f706656968052e915",
+    "libxrender1_0.9.10-1_arm64.deb": "fcae69900b599e7b31b31eafa203a184e00376ade1d2f74f9b3d7b24991573a0",
+    "libxshmfence1_1.3-1_arm64.deb": "fa2edaae6902681ecd02534dcdf8389ac48714d3385d572cc17747160957acc8",
+    "libxss1_1.2.3-1_arm64.deb": "e0ff80e309eacda5face68b9a3bd56718fed2750af429324c35d7c9491c335f4",
+    "libxvidcore4_1.3.7-1_arm64.deb": "b950edcb42803f158f94cd2ee44850082c98c92d54e20982ad933936f5f1d181",
+    "libxxf86vm1_1.1.4-1+b2_arm64.deb": "8a4826772ac480804d6b8d776a4130d95260c036b9b218de4c8a0f07eb9c5bba",
+    "libz3-4_4.8.10-1_arm64.deb": "ae1ea58ecfdd4b5ec53d734e60ac2df37fddb11888b7730a19335f1a9b09f489",
+    "libzmq5_4.3.4-1_arm64.deb": "e5eaecc454d6792848a2ff866d73e3123b9ccdd08c9bd9e6096b52f08e484a13",
+    "libzstd1_1.4.8+dfsg-2.1_arm64.deb": "dd01659c6c122f983a3369a04ede63539f666585d52a03f8aa2c27b307e547e0",
+    "libzvbi-common_0.2.35-18_all.deb": "53ed21370b937a9385e1fcf1626400891bd4fd86a76b31654fb45e0875d8bfb8",
+    "libzvbi0_0.2.35-18_arm64.deb": "40626984d48d486b62f08c255eb9397a04f52ca2983e8d689e5de5e94e29a4b1",
+    "lsb-base_11.1.0_all.deb": "89ed6332074d827a65305f9a51e591dff20641d61ff5e11f4e1822a9987e96fe",
+    "mariadb-common_10.5.12-0+deb11u1_all.deb": "08df6568dd15e9e4b72717539e84ada558a868472e26144f114db82bc22f6421",
+    "mysql-common_5.8+1.0.7_all.deb": "22b3130e002c2c2fa6a1124aaccbe3a6ddbbb4d6bf03beed8a6f044027dcb720",
+    "ocl-icd-libopencl1_2.2.14-2_arm64.deb": "988af69eca9c4b7433572d11ecbc048a7680ae15afa78941782945b18ff185d7",
+    "odbcinst1debian2_2.3.6-0.1+b1_arm64.deb": "3b22a944687b3007a3484673a68e2a4483fd9237530b0ec359e423c8a745ca0e",
+    "odbcinst_2.3.6-0.1+b1_arm64.deb": "c0a9b4ade83bc9a28967e1e981cd1c93f9e6654cf6d53f1fa86db84b3bb8ad89",
+    "perl_5.32.1-4+deb11u2_arm64.deb": "625a2d0cafb5089953012d60d3a5ba726b692d9d49955a251b78b8cce884d05b",
+    "pkg-config_0.29.2-1_arm64.deb": "074d64f7a6bb5fb9139661aea20815438d8ffe8d7bb44b7c3f58e220c153fdbd",
+    "proj-data_7.2.1-1_all.deb": "40c64f7808d8233c08f3aa2745211e705828b4ae6fc5dbd62a934d8c3e9fd6e5",
+    "sensible-utils_0.0.14_all.deb": "b9a447dc4ec8714196b037e20a2209e62cd669f5450222952f259bda4416b71f",
+    "shared-mime-info_2.0-1_arm64.deb": "33257bc679bee7b2627a001eb747f3378ea8c8063863d2cb6edb6ad7f37f280a",
+    "ttf-bitstream-vera_1.10-8.1_all.deb": "ba622edf73744b2951bbd20bfc113a1a875a9b0c6fed1ac9e9c7f4b54dd8a048",
+    "tzdata_2021a-1+deb11u2_all.deb": "4a34cbe17d391e6351386f3530b7ffd096c2cc8582e970f745addc636fa7c397",
+    "ucf_3.0043_all.deb": "ebef6bcd777b5c0cc2699926f2159db08433aed07c50cb321fd828b28c5e8d53",
+    "x11-common_7.7+22_all.deb": "5d1c3287826f60c3a82158b803b9c0489b8aad845ca23a53a982eba3dbb82aa3",
+    "xkb-data_2.29-2_all.deb": "9122cccc67e6b3c3aef2fa9c50ef9d793a12f951c76698a02b1f4ceb9e3634e5",
+    "zlib1g-dev_1.2.11.dfsg-2_arm64.deb": "1e6ed652eebd3761454d75db5459c247cd5d29e58bbe9f6b4b62a62d96f7c279",
+}
diff --git a/frc971/control_loops/python/angular_system.py b/frc971/control_loops/python/angular_system.py
index 7ccb4af..cad3221 100755
--- a/frc971/control_loops/python/angular_system.py
+++ b/frc971/control_loops/python/angular_system.py
@@ -193,6 +193,8 @@
     x_plot = []
     v_plot = []
     a_plot = []
+    motor_current_plot = []
+    battery_current_plot = []
     x_goal_plot = []
     v_goal_plot = []
     x_hat_plot = []
@@ -239,7 +241,14 @@
             v_goal_plot.append(end_goal[1, 0])
 
         U = U_uncapped.copy()
+
         U[0, 0] = numpy.clip(U[0, 0], -vbat, vbat)
+
+        motor_current = (U[0, 0] - plant.X[1, 0] / plant.G / plant.motor.Kv
+                         ) / plant.motor.resistance
+        motor_current_plot.append(motor_current)
+        battery_current = U[0, 0] * motor_current / 12.0
+        battery_current_plot.append(battery_current)
         x_plot.append(plant.X[0, 0])
 
         if v_plot:
@@ -282,8 +291,16 @@
     pylab.plot(t_plot, offset_plot, label='voltage_offset')
     pylab.legend()
 
-    pylab.subplot(3, 1, 3)
-    pylab.plot(t_plot, a_plot, label='a')
+    ax1 = pylab.subplot(3, 1, 3)
+    ax1.set_xlabel("time(s)")
+    ax1.set_ylabel("rad/s^2")
+    ax1.plot(t_plot, a_plot, label='a')
+
+    ax2 = ax1.twinx()
+    ax2.set_xlabel("time(s)")
+    ax2.set_ylabel("Amps")
+    ax2.plot(t_plot, battery_current_plot, 'g', label='battery')
+    ax2.plot(t_plot, motor_current_plot, 'r', label='motor')
     pylab.legend()
 
     pylab.show()
diff --git a/go.mod b/go.mod
index a05f6de..fb6a573 100644
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@
 
 require (
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
+	github.com/davecgh/go-spew v1.1.0
 	github.com/google/go-querystring v1.1.0 // indirect
 	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
 	golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
diff --git a/go.sum b/go.sum
index c832e6c..9ac49b8 100644
--- a/go.sum
+++ b/go.sum
@@ -15,6 +15,7 @@
 github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
diff --git a/platform_mappings b/platform_mappings
index 04f55df..1816850 100644
--- a/platform_mappings
+++ b/platform_mappings
@@ -15,6 +15,9 @@
   //tools/platforms:rp2040
     --cpu=rp2040
 
+  //tools/platforms:linux_arm64
+    --cpu=arm64
+
 flags:
   --cpu=k8
     //tools/platforms:linux_x86
@@ -30,3 +33,6 @@
 
   --cpu=rp2040
     //tools/platforms:rp2040
+
+  --cpu=arm64
+    //tools/platforms:linux_arm64
diff --git a/scouting/BUILD b/scouting/BUILD
index 8188a20..836e5c3 100644
--- a/scouting/BUILD
+++ b/scouting/BUILD
@@ -13,5 +13,5 @@
     importpath = "github.com/frc971/971-Robot-Code/scouting",
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:private"],
-    deps = ["@com_github_mattn_go_sqlite3//:go_default_library"],
+    deps = ["@com_github_mattn_go_sqlite3//:go-sqlite3"],
 )
diff --git a/scouting/scraping/BUILD b/scouting/scraping/BUILD
new file mode 100644
index 0000000..d9248f8
--- /dev/null
+++ b/scouting/scraping/BUILD
@@ -0,0 +1,23 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "scraping",
+    srcs = [
+        "scrape.go",
+        "types.go",
+    ],
+    importpath = "github.com/frc971/971-Robot-Code/scouting/scraping",
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
+)
+
+go_binary(
+    name = "scraping_demo",
+    srcs = ["scraping_demo.go"],
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "scraping",
+        "@com_github_davecgh_go_spew//spew:go_default_library",
+    ],
+)
diff --git a/scouting/scraping/scrape.go b/scouting/scraping/scrape.go
new file mode 100644
index 0000000..fa20f7b
--- /dev/null
+++ b/scouting/scraping/scrape.go
@@ -0,0 +1,86 @@
+package scraping
+
+// A library to grab match data from The Blue Alliance.
+import (
+	"encoding/json"
+	"errors"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+)
+
+// Stores the TBA API key to access the API.
+type params struct {
+	ApiKey string `json:"api_key"`
+}
+
+// Takes in year and FIRST event code and returns all matches in that event according to TBA.
+// Also takes in a file path to the JSON config file that contains your TBA API key.
+// It defaults to <workspace root>/config.json
+// the config is expected to have the following contents:
+//{
+//    api_key:"myTBAapiKey"
+//}
+func AllMatches(year, eventCode, filePath string) ([]Match, error) {
+	if filePath == "" {
+		filePath = os.Getenv("BUILD_WORKSPACE_DIRECTORY") + "/scouting_config.json"
+	}
+	// Takes the filepath and grabs the api key from the json.
+	content, err := ioutil.ReadFile(filePath)
+	if err != nil {
+		log.Fatal(err)
+	}
+	// Parses the JSON parameters into a struct.
+	var passed_params params
+	error := json.Unmarshal([]byte(content), &passed_params)
+	if error != nil {
+		log.Fatalf("You forgot to add the api_key parameter in the json file")
+		log.Fatalf("%s", err)
+	}
+
+	// Create the TBA event key for the year and event code.
+	eventKey := year + eventCode
+
+	// Create the client for HTTP requests.
+	client := &http.Client{}
+
+	// Create a get request for the match info.
+	req, err := http.NewRequest("GET", "https://www.thebluealliance.com/api/v3/event/"+eventKey+"/matches", nil)
+
+	if err != nil {
+		return nil, errors.New("failed to build http request")
+	}
+
+	// Add the auth key header to the request.
+	req.Header.Add("X-TBA-Auth-Key", passed_params.ApiKey)
+
+	// Make the API request
+	resp, err := client.Do(req)
+
+	if err != nil {
+		return nil, err
+	}
+
+	if resp.Status != "200 OK" {
+		return nil, errors.New("Recieved a status of " + resp.Status + " expected : 200 OK")
+	}
+
+	// Wait until the response is done.
+	defer resp.Body.Close()
+
+	// Get all bytes from response body.
+	bodyBytes, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return nil, errors.New("failed to read response body with error :" + err.Error())
+	}
+
+	var matches []Match
+	// Unmarshal json into go usable format.
+	jsonError := json.Unmarshal([]byte(bodyBytes), &matches)
+	if jsonError != nil {
+		return nil, errors.New("failed to unmarshal json recieved from TBA")
+	}
+
+	return matches, nil
+}
diff --git a/scouting/scraping/scraping_demo.go b/scouting/scraping/scraping_demo.go
new file mode 100644
index 0000000..1d727f3
--- /dev/null
+++ b/scouting/scraping/scraping_demo.go
@@ -0,0 +1,20 @@
+package main
+
+// To run the demo, ensure that you have a file named scouting_config.json at the workspace root with your TBA api key in it.
+import (
+	"log"
+
+	"github.com/davecgh/go-spew/spew"
+	"github.com/frc971/971-Robot-Code/scouting/scraping"
+)
+
+func main() {
+	// Get all the matches.
+	matches, err := scraping.AllMatches("2016", "nytr", "")
+	// Fail on error.
+	if err != nil {
+		log.Fatal("Error:", err.Error)
+	}
+	// Dump the matches.
+	spew.Dump(matches)
+}
diff --git a/scouting/scraping/types.go b/scouting/scraping/types.go
new file mode 100644
index 0000000..aa0d4be
--- /dev/null
+++ b/scouting/scraping/types.go
@@ -0,0 +1,82 @@
+package scraping
+
+// Match holds the TBA data for a given match
+type Match struct {
+	Key             string
+	CompLevel       string          `json:"comp_level"`
+	SetNumber       int             `json:"set_number"`
+	MatchNumber     int             `json:"match_number"`
+	Alliances       Alliances       `json:"alliances"`
+	WinningAlliance string          `json:"winning_alliance"`
+	EventKey        string          `json:"event_key"`
+	Time            int             `json:"time"`
+	PredictedTime   int             `json:"predicted_time"`
+	ActualTime      int             `json:"actual_time"`
+	PostResultTime  int             `json:"post_result_time"`
+	ScoreBreakdowns ScoreBreakdowns `json:"score_breakdowns"`
+}
+
+// Holds score breakdowns for each alliance
+type ScoreBreakdowns struct {
+	Blue ScoreBreakdownAlliance `json:"blue"`
+	Red  ScoreBreakdownAlliance `json:"red"`
+}
+
+// Stores the actual data for the breakdown
+type ScoreBreakdownAlliance struct {
+	TaxiRobot1    string `json:"taxiRobot1"`
+	EndgameRobot1 string `json:"endgameRobot1"`
+	TaxiRobot2    string `json:"taxiRobot2"`
+	EndgameRobot2 string `json:"endgameRobot2"`
+	TaxiRobot3    string `json:"taxiRobot3"`
+	EndgameRobot3 string `json:"endgameRobot3"`
+
+	AutoCargoLowerNear      int  `json:"autoCargoLowerNear"`
+	AutoCargoLowerFar       int  `json:"autoCargoLowerFar"`
+	AutoCargoLowerBlue      int  `json:"autoCargoLowerBlue"`
+	AutoCargoLowerRed       int  `json:"autoCargoLowerRed"`
+	AutoCargoUpperNear      int  `json:"autoCargoUpperNear"`
+	AutoCargoUpperFar       int  `json:"autoCargoUpperFar"`
+	AutoCargoUpperBlue      int  `json:"autoCargoUpperBlue"`
+	AutoCargoUpperRed       int  `json:"autoCargoUpperRed"`
+	AutoCargoTotal          int  `json:"autoCargoTotal"`
+	TeleOpCargoLowerNear    int  `json:"teleopCargoLowerNear"`
+	TeleOpCargoLowerFar     int  `json:"teleopCargoLowerFar"`
+	TeleOpCargoLowerBlue    int  `json:"teleopCargoLowerBlue"`
+	TeleOpCargoLowerRed     int  `json:"teleopCargoLowerRed"`
+	TeleOpCargoUpperNear    int  `json:"teleopCargoUpperNear"`
+	TeleOpCargoUpperFar     int  `json:"teleopCargoUpperFar"`
+	TeleOpCargoUpperBlue    int  `json:"teleopCargoUpperBlue"`
+	TeleOpCargoUpperRed     int  `json:"teleopCargoUpperRed"`
+	TeleopCargoTotal        int  `json:"teleopCargoTotal"`
+	MatchCargoTotal         int  `json:"matchCargoTotal"`
+	AutoTaxiPoints          int  `json:"autoTaxiPoints"`
+	AutoCargoPoints         int  `json:"autoCargoPoints"`
+	AutoPoints              int  `json:"autoPoints"`
+	QuintetAchieved         bool `json:"quintetAchieved"`
+	TeleOpCargoPoints       int  `json:"teleopCargoPoints"`
+	EndgamePoints           int  `json:"endgamePoints"`
+	TeleOpPoints            int  `json:"teleopPoints"`
+	CargoBonusRankingPoint  bool `json:"cargoBonusRankingPoint"`
+	HangerBonusRankingPoint bool `json:"hangarBonusRankingPoint"`
+	FoulCount               bool `json:"foulCount"`
+	TechFoulCount           int  `json:"techFoulCount"`
+	AdjustPoints            int  `json:"adjustPoints"`
+	FoulPoints              int  `json:"foulPoints"`
+	RankingPoints           int  `json:"rp"`
+	TotalPoints             int  `json:"totalPoints"`
+}
+
+// Alliances holds the two alliances for a match
+type Alliances struct {
+	Red  Alliance `json:"red"`
+	Blue Alliance `json:"blue"`
+}
+
+// Alliance holds the info for the alliance
+type Alliance struct {
+	Score             int      `json:"score"`
+	TeamKeys          []string `json:"team_keys"`
+	SurrogateTeamKeys []string `json:"surrogate_team_keys"`
+	DqTeamKeys        []string `json:"dq_team_keys"`
+}
diff --git a/third_party/BUILD b/third_party/BUILD
index 040d3f7..aff739f 100644
--- a/third_party/BUILD
+++ b/third_party/BUILD
@@ -42,6 +42,7 @@
     deps = select({
         "//tools:cpu_k8": ["@opencv_k8//:opencv"],
         "//tools:cpu_armhf": ["@opencv_armhf//:opencv"],
+        "//tools:cpu_arm64": ["@opencv_arm64//:opencv"],
         "//conditions:default": [":unavailable"],
     }),
 )
@@ -86,6 +87,7 @@
     deps = select({
         "//tools:cpu_k8": ["@halide_k8//:runtime"],
         "//tools:cpu_armhf": ["@halide_armhf//:runtime"],
+        "//tools:cpu_arm64": ["@halide_arm64//:runtime"],
         "//conditions:default": [":unavailable"],
     }),
 )
@@ -95,7 +97,7 @@
     visibility = ["//visibility:public"],
     deps = select({
         "//tools:cpu_k8": ["@lzma_amd64//:lib"],
-        "//tools:cpu_aarch64": ["@lzma_arm64//:lib"],
+        "//tools:cpu_arm64": ["@lzma_arm64//:lib"],
         "//conditions:default": [":unavailable"],
     }),
 )
diff --git a/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl b/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl
index 4ba4601..4cdbf75 100644
--- a/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl
+++ b/third_party/bazel-toolchain/toolchain/internal/llvm_distributions.bzl
@@ -180,6 +180,7 @@
     "clang+llvm-13.0.0-x86_64-apple-darwin.tar.xz": "d051234eca1db1f5e4bc08c64937c879c7098900f7a0370f3ceb7544816a8b09",
     "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz": "76d0bf002ede7a893f69d9ad2c4e101d15a8f4186fbfe24e74856c8449acd7c1",
     "clang+llvm-13.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz": "2c2fb857af97f41a5032e9ecadf7f78d3eff389a5cd3c9ec620d24f134ceb3c8",
+    "clang+llvm-13.0.0-aarch64-linux-gnu.tar.xz": "968d65d2593850ee9b37fcda074fb7641529bd45d2f976af6c8197de3c22612f",
 }
 
 # Note: Unlike the user-specified llvm_mirror attribute, the URL prefixes in
diff --git a/third_party/eigen/unsupported/Eigen/src/MatrixFunctions/MatrixExponential.h b/third_party/eigen/unsupported/Eigen/src/MatrixFunctions/MatrixExponential.h
index e5ebbcf..3519611 100644
--- a/third_party/eigen/unsupported/Eigen/src/MatrixFunctions/MatrixExponential.h
+++ b/third_party/eigen/unsupported/Eigen/src/MatrixFunctions/MatrixExponential.h
@@ -264,7 +264,7 @@
 struct matrix_exp_computeUV<MatrixType, long double>
 {
   template <typename ArgType>
-  static void run(const ArgType& arg, MatrixType& U, MatrixType& V, int& squarings)
+  static void run(const ArgType& arg, [[maybe_unused]] MatrixType& U, [[maybe_unused]] MatrixType& V, int& squarings)
   {
 #if   LDBL_MANT_DIG == 53   // double precision
     matrix_exp_computeUV<MatrixType, double>::run(arg, U, V, squarings);
@@ -273,7 +273,7 @@
   
     using std::frexp;
     using std::pow;
-    const long double l1norm = arg.cwiseAbs().colwise().sum().maxCoeff();
+    [[maybe_unused]] const long double l1norm = arg.cwiseAbs().colwise().sum().maxCoeff();
     squarings = 0;
   
 #if LDBL_MANT_DIG <= 64   // extended precision
diff --git a/third_party/gperftools/BUILD b/third_party/gperftools/BUILD
index 7dd86dc..2022ee6 100644
--- a/third_party/gperftools/BUILD
+++ b/third_party/gperftools/BUILD
@@ -102,12 +102,18 @@
         "-DPRIuS=\\\"lu\\\"",
         "-DPRIxS=\\\"lx\\\"",
     ],
-    "arm": [
+    "arm32": [
         "-DPC_FROM_UCONTEXT=uc_mcontext.arm_pc",
         "-DPRIdS=\\\"d\\\"",
         "-DPRIuS=\\\"u\\\"",
         "-DPRIxS=\\\"x\\\"",
     ],
+    "arm64": [
+        "-DPC_FROM_UCONTEXT=uc_mcontext.pc",
+        "-DPRIdS=\\\"ld\\\"",
+        "-DPRIuS=\\\"lu\\\"",
+        "-DPRIxS=\\\"lx\\\"",
+    ],
 }) + compiler_select({
     "clang": [
         "-Wno-unused-const-variable",
diff --git a/tools/BUILD b/tools/BUILD
index 2e470fa..a7d165d 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -47,11 +47,14 @@
 
 config_setting(
     name = "cpu_armhf",
-    constraint_values = ["@//tools/platforms/hardware:raspberry_pi"],
+    constraint_values = [
+        "@platforms//cpu:armv7",
+        "//tools/platforms/hardware:raspberry_pi",
+    ],
 )
 
 config_setting(
-    name = "cpu_aarch64",
+    name = "cpu_arm64",
     constraint_values = ["@platforms//cpu:arm64"],
 )
 
diff --git a/tools/build_rules/select.bzl b/tools/build_rules/select.bzl
index cdeeb25..5d78a2b 100644
--- a/tools/build_rules/select.bzl
+++ b/tools/build_rules/select.bzl
@@ -7,6 +7,7 @@
     "amd64",
     "roborio",
     "armhf",
+    "arm64",
     "cortex-m",
     "cortex-m0plus",
 ]
@@ -30,10 +31,23 @@
             if cpu != "arm":
                 new_values[cpu] = values[cpu]
         new_values["armhf"] = values["arm"]
+        new_values["arm64"] = values["arm"]
         new_values["roborio"] = values["arm"]
         new_values["cortex-m"] = values["arm"]
         new_values["cortex-m0plus"] = values["arm"]
         values = new_values
+    elif "arm32" in values:
+        if "arm64" not in values:
+            fail("Need to handle 'arm64' CPU if handling 'arm32' CPU.")
+        new_values = {}
+        for cpu in values:
+            if cpu != "arm32":
+                new_values[cpu] = values[cpu]
+        new_values["armhf"] = values["arm32"]
+        new_values["roborio"] = values["arm32"]
+        new_values["cortex-m"] = values["arm32"]
+        new_values["cortex-m0plus"] = values["arm32"]
+        values = new_values
     for cpu in all_cpus:
         if cpu not in values:
             if "else" in values:
@@ -47,6 +61,7 @@
         "@//tools:cpu_k8": values["amd64"],
         "@//tools:cpu_roborio": values["roborio"],
         "@//tools:cpu_armhf": values["armhf"],
+        "@//tools:cpu_arm64": values["arm64"],
         "@//tools:cpu_cortex_m4f": values["cortex-m"],
         "@//tools:cpu_cortex_m0plus": values["cortex-m0plus"],
         # TODO(phil): Support this properly.
@@ -67,6 +82,7 @@
         fail("Need to handle 64 bit addresses!", "values")
     return select({
         "@//tools:cpu_k8": values["64"],
+        "@//tools:cpu_arm64": values["64"],
         "@//tools:cpu_roborio": values["32"],
         "@//tools:cpu_armhf": values["32"],
         "@//tools:cpu_cortex_m4f": values["32"],
diff --git a/tools/ci/BUILD b/tools/ci/BUILD
index 7fa13d5..59b38ea 100644
--- a/tools/ci/BUILD
+++ b/tools/ci/BUILD
@@ -6,7 +6,7 @@
     importpath = "github.com/frc971/971-Robot-Code/tools/ci",
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:private"],
-    deps = ["@com_github_buildkite_go_buildkite//buildkite:go_default_library"],
+    deps = ["@com_github_buildkite_go_buildkite//buildkite"],
 )
 
 go_binary(
diff --git a/tools/ci/buildkite.yaml b/tools/ci/buildkite.yaml
index b2ee1e3..c4af4a4 100644
--- a/tools/ci/buildkite.yaml
+++ b/tools/ci/buildkite.yaml
@@ -29,6 +29,11 @@
       - tools/ci/clean-disk.sh
       - tools/bazel ${STARTUP} --output_base=../armv7_output_base build ${COMMON} --config=armv7 ${TARGETS}
 
+  - label: "arm64"
+    commands:
+      - tools/ci/clean-disk.sh
+      - tools/bazel ${STARTUP} --output_base=../arm64_output_base build ${COMMON} --config=arm64 ${TARGETS}
+
   - label: "cortex-m4f"
     commands:
       - tools/ci/clean-disk.sh
diff --git a/tools/cpp/BUILD b/tools/cpp/BUILD
index 6ff661a..db5fa06 100644
--- a/tools/cpp/BUILD
+++ b/tools/cpp/BUILD
@@ -7,6 +7,7 @@
     toolchains = {
         "k8": "@llvm_toolchain//:cc-clang-x86_64-linux",
         "armv7": "@llvm_toolchain//:cc-clang-armv7-linux",
+        "arm64": "@llvm_toolchain//:cc-clang-aarch64-linux",
         "roborio": ":cc-compiler-roborio",
         "cortex-m4f": ":cc-compiler-cortex-m4f",
         "rp2040": ":cc-compiler-rp2040",
@@ -20,7 +21,6 @@
         cpu = cpu,
     )
     for cpu in [
-        "armeabi-v7a",
         "cortex-m4f",
         "cortex-m4f-k22",
         "rp2040",
diff --git a/tools/cpp/toolchain_config.bzl b/tools/cpp/toolchain_config.bzl
index fb6576c..526d831 100644
--- a/tools/cpp/toolchain_config.bzl
+++ b/tools/cpp/toolchain_config.bzl
@@ -19,25 +19,19 @@
         toolchain_identifier = "cortex-m4f-k22"
     elif ctx.attr.cpu == "roborio":
         toolchain_identifier = "roborio_linux"
-    elif ctx.attr.cpu == "armeabi-v7a":
-        toolchain_identifier = "stub_armeabi-v7a"
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        host_system_name = "armeabi-v7a"
-    elif (ctx.attr.cpu == "rp2040" or
-          ctx.attr.cpu == "cortex-m4f" or
-          ctx.attr.cpu == "cortex-m4f-k22"):
+    if (ctx.attr.cpu == "rp2040" or
+        ctx.attr.cpu == "cortex-m4f" or
+        ctx.attr.cpu == "cortex-m4f-k22"):
         host_system_name = "local"
     elif ctx.attr.cpu == "roborio":
         host_system_name = "roborio"
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        target_system_name = "armeabi-v7a"
-    elif ctx.attr.cpu == "rp2040":
+    if ctx.attr.cpu == "rp2040":
         target_system_name = "rp2040"
     elif ctx.attr.cpu == "cortex-m4f":
         target_system_name = "cortex-m4f"
@@ -48,9 +42,7 @@
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        target_cpu = "armeabi-v7a"
-    elif ctx.attr.cpu == "rp2040":
+    if ctx.attr.cpu == "rp2040":
         target_cpu = "rp2040"
     elif ctx.attr.cpu == "cortex-m4f":
         target_cpu = "cortex-m4f"
@@ -61,9 +53,7 @@
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        target_libc = "armeabi-v7a"
-    elif ctx.attr.cpu == "rp2040":
+    if ctx.attr.cpu == "rp2040":
         target_libc = "rp2040"
     elif ctx.attr.cpu == "cortex-m4f":
         target_libc = "cortex-m4f"
@@ -74,19 +64,15 @@
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        compiler = "compiler"
-    elif (ctx.attr.cpu == "rp2040" or
-          ctx.attr.cpu == "cortex-m4f" or
-          ctx.attr.cpu == "cortex-m4f-k22" or
-          ctx.attr.cpu == "roborio"):
+    if (ctx.attr.cpu == "rp2040" or
+        ctx.attr.cpu == "cortex-m4f" or
+        ctx.attr.cpu == "cortex-m4f-k22" or
+        ctx.attr.cpu == "roborio"):
         compiler = "gcc"
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        abi_version = "armeabi-v7a"
-    elif ctx.attr.cpu == "rp2040":
+    if ctx.attr.cpu == "rp2040":
         abi_version = "rp2040"
     elif ctx.attr.cpu == "cortex-m4f":
         abi_version = "cortex-m4f"
@@ -97,9 +83,7 @@
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        abi_libc_version = "armeabi-v7a"
-    elif ctx.attr.cpu == "rp2040":
+    if ctx.attr.cpu == "rp2040":
         abi_libc_version = "rp2040"
     elif ctx.attr.cpu == "cortex-m4f":
         abi_libc_version = "cortex-m4f"
@@ -183,12 +167,10 @@
     else:
         objcopy_embed_data_action = None
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        action_configs = []
-    elif (ctx.attr.cpu == "rp2040" or
-          ctx.attr.cpu == "cortex-m4f" or
-          ctx.attr.cpu == "cortex-m4f-k22" or
-          ctx.attr.cpu == "roborio"):
+    if (ctx.attr.cpu == "rp2040" or
+        ctx.attr.cpu == "cortex-m4f" or
+        ctx.attr.cpu == "cortex-m4f-k22" or
+        ctx.attr.cpu == "roborio"):
         action_configs = [objcopy_embed_data_action]
     else:
         fail("Unreachable")
@@ -1162,14 +1144,10 @@
             sysroot_feature,
             unfiltered_compile_flags_feature,
         ]
-    elif ctx.attr.cpu == "armeabi-v7a":
-        features = [supports_pic_feature]
     else:
         fail("Unreachable")
 
-    if ctx.attr.cpu == "armeabi-v7a":
-        cxx_builtin_include_directories = []
-    elif ctx.attr.cpu == "roborio":
+    if ctx.attr.cpu == "roborio":
         cxx_builtin_include_directories = [
             "%package(@arm_frc_linux_gnueabi_repo//arm-frc2020-linux-gnueabi/usr/lib/gcc/arm-frc2020-linux-gnueabi/7.3.0/include)%",
             "%package(@arm_frc_linux_gnueabi_repo//arm-frc2020-linux-gnueabi/usr/lib/gcc/arm-frc2020-linux-gnueabi/7.3.0/include-fixed)%",
@@ -1289,20 +1267,6 @@
                 path = "gcc_arm_none_eabi/arm-none-eabi-strip",
             ),
         ]
-    elif ctx.attr.cpu == "armeabi-v7a":
-        tool_paths = [
-            tool_path(name = "ar", path = "/bin/false"),
-            tool_path(name = "compat-ld", path = "/bin/false"),
-            tool_path(name = "cpp", path = "/bin/false"),
-            tool_path(name = "dwp", path = "/bin/false"),
-            tool_path(name = "gcc", path = "/bin/false"),
-            tool_path(name = "gcov", path = "/bin/false"),
-            tool_path(name = "ld", path = "/bin/false"),
-            tool_path(name = "nm", path = "/bin/false"),
-            tool_path(name = "objcopy", path = "/bin/false"),
-            tool_path(name = "objdump", path = "/bin/false"),
-            tool_path(name = "strip", path = "/bin/false"),
-        ]
     else:
         fail("Unreachable")
 
@@ -1336,7 +1300,7 @@
 cc_toolchain_config = rule(
     implementation = _impl,
     attrs = {
-        "cpu": attr.string(mandatory = True, values = ["armeabi-v7a", "cortex-m4f", "cortex-m4f-k22", "roborio", "rp2040"]),
+        "cpu": attr.string(mandatory = True, values = ["cortex-m4f", "cortex-m4f-k22", "roborio", "rp2040"]),
     },
     provides = [CcToolchainConfigInfo],
     executable = True,
diff --git a/tools/go/BUILD b/tools/go/BUILD
index 25b3adb..2c251a0 100644
--- a/tools/go/BUILD
+++ b/tools/go/BUILD
@@ -6,6 +6,13 @@
     srcs = [
         "mirror_lib.py",
     ],
+    data = [
+        "@com_github_bazelbuild_buildtools//buildifier",
+    ],
+    target_compatible_with = ["@platforms//cpu:x86_64"],
+    deps = [
+        "@bazel_tools//tools/python/runfiles",
+    ],
 )
 
 py_binary(
@@ -25,13 +32,12 @@
         "mirror_go_repos.py",
     ],
     data = [
-        "@com_github_bazelbuild_buildtools//buildifier",
         "@go_sdk//:bin/go",
     ],
     target_compatible_with = ["@platforms//cpu:x86_64"],
+    visibility = ["//visibility:public"],
     deps = [
         ":mirror_lib",
-        "@bazel_tools//tools/python/runfiles",
     ],
 )
 
diff --git a/tools/go/go_mirrors.bzl b/tools/go/go_mirrors.bzl
index 0e8f394..bfee0a9 100644
--- a/tools/go/go_mirrors.bzl
+++ b/tools/go/go_mirrors.bzl
@@ -147,6 +147,13 @@
         "strip_prefix": "github.com/grpc-ecosystem/grpc-gateway@v1.16.0",
         "version": "v1.16.0",
     },
+    "com_github_mattn_go_sqlite3": {
+        "filename": "com_github_mattn_go_sqlite3__v1.14.10.zip",
+        "importpath": "github.com/mattn/go-sqlite3",
+        "sha256": "3c1e6497b023fc4741bf1bbfb39ae657b99cf44cfb33f5cbf1e88b315d25a306",
+        "strip_prefix": "github.com/mattn/go-sqlite3@v1.14.10",
+        "version": "v1.14.10",
+    },
     "com_github_pmezard_go_difflib": {
         "filename": "com_github_pmezard_go_difflib__v1.0.0.zip",
         "importpath": "github.com/pmezard/go-difflib",
@@ -315,11 +322,4 @@
         "strip_prefix": "golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
         "version": "v0.0.0-20200804184101-5ec99f83aff1",
     },
-    "com_github_mattn_go_sqlite3": {
-        "filename": "com_github_mattn_go_sqlite3__v1.14.10.zip",
-        "importpath": "github.com/mattn/go-sqlite3",
-        "sha256": "3c1e6497b023fc4741bf1bbfb39ae657b99cf44cfb33f5cbf1e88b315d25a306",
-        "strip_prefix": "github.com/mattn/go-sqlite3@v1.14.10",
-        "version": "v1.14.10",
-    },
 }
diff --git a/tools/go/mirror_go_repos.py b/tools/go/mirror_go_repos.py
index 52e9e26..ae8f722 100644
--- a/tools/go/mirror_go_repos.py
+++ b/tools/go/mirror_go_repos.py
@@ -14,9 +14,6 @@
 import sys
 import tarfile
 from typing import List, Dict
-import urllib.request
-
-from bazel_tools.tools.python.runfiles import runfiles
 
 # Need a fully qualified import here because @bazel_tools interferes.
 import org_frc971.tools.go.mirror_lib
@@ -101,7 +98,14 @@
 
 def main(argv):
     parser = argparse.ArgumentParser()
-    parser.add_argument(
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument(
+        "--prune",
+        action="store_true",
+        help=("When set, makes the tool prune go_mirrors_bzl to match the "
+              "repositories specified in go_deps_bzl. Incompatible with "
+              "--ssh_host."))
+    group.add_argument(
         "--ssh_host",
         type=str,
         help=("The SSH host to copy the downloaded Go repositories to. This "
@@ -121,37 +125,38 @@
     else:
         existing_mirrored_repos = {}
 
-    with tarfile.open("go_deps.tar", "w") as tar:
-        cached_info = download_repos(repos, existing_mirrored_repos, tar)
-        num_not_already_mirrored = len(tar.getnames())
+    exit_code = 0
 
-    print(f"Found {num_not_already_mirrored}/{len(cached_info)} libraries "
-          "that need to be mirrored.")
-
-    # Only mirror the deps if we've specified an SSH host and we actually have
-    # something to mirror.
-    if args.ssh_host and num_not_already_mirrored:
-        copy_to_host_and_unpack("go_deps.tar", args.ssh_host)
+    if args.prune:
+        # Delete all mirror info that is not needed anymore.
+        existing_cache_info = org_frc971.tools.go.mirror_lib.parse_go_mirror_info(args.go_mirrors_bzl)
+        cached_info = {}
+        for repo in repos:
+            try:
+                cached_info[repo["name"]] = existing_cache_info[repo["name"]]
+            except KeyError:
+                print(f"{repo['name']} needs to be mirrored still.")
+                exit_code = 1
     else:
-        print("Skipping mirroring because of lack of --ssh_host or there's "
-              "nothing to actually mirror.")
+        # Download all the repositories that need to be mirrored.
+        with tarfile.open("go_deps.tar", "w") as tar:
+            cached_info = download_repos(repos, existing_mirrored_repos, tar)
+            num_not_already_mirrored = len(tar.getnames())
 
-    with open(args.go_mirrors_bzl, "w") as file:
-        file.write("# This file is auto-generated. Do not edit.\n")
-        file.write("GO_MIRROR_INFO = ")
-        # Format as JSON first. It's parsable as Starlark.
-        json.dump(cached_info, file, indent=4, sort_keys=True)
-        file.write("\n")
+        print(f"Found {num_not_already_mirrored}/{len(cached_info)} libraries "
+              "that need to be mirrored.")
 
+        # Only mirror the deps if we've specified an SSH host and we actually have
+        # something to mirror.
+        if args.ssh_host and num_not_already_mirrored:
+            copy_to_host_and_unpack("go_deps.tar", args.ssh_host)
+        else:
+            print("Skipping mirroring because of lack of --ssh_host or there's "
+                  "nothing to actually mirror.")
 
-    # Properly format the file now so that the linter doesn't complain.
-    r = runfiles.Create()
-    subprocess.run(
-        [
-            r.Rlocation("com_github_bazelbuild_buildtools/buildifier/buildifier_/buildifier"),
-            args.go_mirrors_bzl,
-        ],
-        check=True)
+    org_frc971.tools.go.mirror_lib.write_go_mirror_info(args.go_mirrors_bzl, cached_info)
+
+    return exit_code
 
 
 if __name__ == "__main__":
diff --git a/tools/go/mirror_lib.py b/tools/go/mirror_lib.py
index faad896..ea23abc 100644
--- a/tools/go/mirror_lib.py
+++ b/tools/go/mirror_lib.py
@@ -1,7 +1,11 @@
 """Provides helper functions for mirroring Go repositories."""
 
-import unittest.mock
+import json
+import subprocess
 from typing import List, Dict
+import unittest.mock
+
+from bazel_tools.tools.python.runfiles import runfiles
 
 
 def read_file(filepath: str) -> str:
@@ -39,3 +43,30 @@
 
     return repositories
 
+
+def parse_go_mirror_info(filepath: str) -> Dict[str, Dict[str, str]]:
+    """Parses the tools/go/go_mirrors.bzl file and returns the GO_MIRROR_INFO dictionary."""
+    global_data = {}
+    compiled_code = compile(read_file(filepath), filepath, "exec")
+    eval(compiled_code, global_data)
+
+    return global_data["GO_MIRROR_INFO"]
+
+
+def write_go_mirror_info(filepath: str, mirror_info: Dict[str, Dict[str, str]]):
+    """Saves the specified mirror_info as GO_MIRROR_INFO into tools/go/go_mirrors.bzl."""
+    with open(filepath, "w") as file:
+        file.write("# This file is auto-generated. Do not edit.\n")
+        file.write("GO_MIRROR_INFO = ")
+        # Format as JSON first. It's parsable as Starlark.
+        json.dump(mirror_info, file, indent=4, sort_keys=True)
+        file.write("\n")
+
+    # Properly format the file now so that the linter doesn't complain.
+    r = runfiles.Create()
+    subprocess.run(
+        [
+            r.Rlocation("com_github_bazelbuild_buildtools/buildifier/buildifier_/buildifier"),
+            filepath,
+        ],
+        check=True)
diff --git a/tools/go/tweak_gazelle_go_deps.py b/tools/go/tweak_gazelle_go_deps.py
index 7189d43..d9d7901 100644
--- a/tools/go/tweak_gazelle_go_deps.py
+++ b/tools/go/tweak_gazelle_go_deps.py
@@ -12,14 +12,14 @@
 import sys
 import textwrap
 
-import tools.go.mirror_lib
+import org_frc971.tools.go.mirror_lib
 
 def main(argv):
     parser = argparse.ArgumentParser()
     parser.add_argument("go_deps_bzl", type=str)
     args = parser.parse_args(argv[1:])
 
-    repos = tools.go.mirror_lib.parse_go_repositories(args.go_deps_bzl)
+    repos = org_frc971.tools.go.mirror_lib.parse_go_repositories(args.go_deps_bzl)
 
     with open(args.go_deps_bzl, "w") as file:
         file.write(textwrap.dedent("""\
diff --git a/tools/lint/BUILD b/tools/lint/BUILD
index d33d786..8d9b150 100644
--- a/tools/lint/BUILD
+++ b/tools/lint/BUILD
@@ -32,6 +32,7 @@
         ":buildifier",
         ":gofmt",
         "//:gazelle-runner",
+        "//tools/go:mirror_go_repos",
         "//tools/go:tweak_gazelle_go_deps",
         "@go_sdk//:bin/go",
     ],
diff --git a/tools/lint/run-ci.sh b/tools/lint/run-ci.sh
index 6228552..32f110a 100755
--- a/tools/lint/run-ci.sh
+++ b/tools/lint/run-ci.sh
@@ -34,6 +34,12 @@
 }
 
 update_repos() {
+    # Clear out the go_deps.bzl file so that gazelle won't hesitate to update
+    # it. Without this step gazelle would never try to remove a dependency.
+    cat > "${BUILD_WORKSPACE_DIRECTORY}"/go_deps.bzl <<EOF
+def go_dependencies():
+    pass
+EOF
     ./gazelle-runner.bash update-repos \
         -from_file=go.mod \
         -to_macro=go_deps.bzl%go_dependencies \
@@ -50,6 +56,10 @@
     "${tweaker}" ./go_deps.bzl
 }
 
+clean_up_go_mirrors() {
+    ./tools/go/mirror_go_repos --prune
+}
+
 buildifier() {
     ./tools/lint/buildifier
 }
@@ -69,6 +79,7 @@
     update_repos
     gazelle
     tweak_gazelle_go_deps
+    clean_up_go_mirrors
     buildifier
     git_status_is_clean  # This must the last linter.
 )
diff --git a/tools/platforms/BUILD b/tools/platforms/BUILD
index d6a63c7..4127883 100644
--- a/tools/platforms/BUILD
+++ b/tools/platforms/BUILD
@@ -26,6 +26,7 @@
     constraint_values = [
         "@platforms//os:linux",
         "@platforms//cpu:arm64",
+        "//tools/platforms/hardware:raspberry_pi",
         "//tools/platforms/go:lacks_support",
         "//tools/platforms/rust:has_support",
     ],
diff --git a/y2020/vision/BUILD b/y2020/vision/BUILD
index 5156b59..75ef7aa 100644
--- a/y2020/vision/BUILD
+++ b/y2020/vision/BUILD
@@ -106,7 +106,10 @@
         "//y2020:config",
     ],
     target_compatible_with = ["@platforms//os:linux"],
-    visibility = ["//y2020:__subpackages__"],
+    visibility = [
+        "//y2020:__subpackages__",
+        "//y2022:__subpackages__",
+    ],
     deps = [
         ":charuco_lib",
         "//aos:init",
diff --git a/y2020/vision/sift/fast_gaussian.bzl b/y2020/vision/sift/fast_gaussian.bzl
index fdf5afe..1560f6a 100644
--- a/y2020/vision/sift/fast_gaussian.bzl
+++ b/y2020/vision/sift/fast_gaussian.bzl
@@ -38,15 +38,16 @@
             "amd64": "k8",
             "roborio": "roborio",
             "armhf": "armv7",
+            "arm64": "aarch64",
             "cortex-m": "cortex-m",
             "cortex-m0plus": "cortex-m0plus",
         }),
         outs = headers + objects + htmls,
-        # The tool doesn't support anything other than k8 and armv7.
-        # right now.
+        # The tool doesn't support everything right now.
         target_compatible_with = platforms.any_of([
+            "@platforms//cpu:arm64",
             "@platforms//cpu:x86_64",
-            "//tools/platforms/hardware:raspberry_pi",
+            "//tools:cpu_armhf",
         ]),
     )
 
diff --git a/y2020/vision/sift/fast_gaussian_runner.py b/y2020/vision/sift/fast_gaussian_runner.py
index cc45208..d812f3f 100755
--- a/y2020/vision/sift/fast_gaussian_runner.py
+++ b/y2020/vision/sift/fast_gaussian_runner.py
@@ -16,6 +16,7 @@
   target_cpu = sys.argv[3]
 
   target = {
+      'aarch64': 'arm-64-linux-no_asserts',
       'armv7': 'arm-32-linux-no_asserts',
       'k8': 'x86-64-linux-no_asserts',
   }[target_cpu]
diff --git a/y2022/BUILD b/y2022/BUILD
index 812cac1..67e8587 100644
--- a/y2022/BUILD
+++ b/y2022/BUILD
@@ -26,6 +26,7 @@
 robot_downloader(
     name = "pi_download",
     binaries = [
+        "//y2020/vision:calibration",
         "//y2022/vision:viewer",
     ],
     data = [
diff --git a/y2022/vision/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json b/y2022/vision/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json
deleted file mode 100644
index 39c7911..0000000
--- a/y2022/vision/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "node_name": "pi1",
- "team_number": 971,
- "intrinsics": [
-  392.276093,
-  0.0,
-  293.934753,
-  0.0,
-  392.30838,
-  212.287537,
-  0.0,
-  0.0,
-  1.0
- ],
- "dist_coeffs": [
-  0.149561,
-  -0.261432,
-  -0.000182,
-  -0.000697,
-  0.099255
- ],
- "calibration_timestamp": 1597994992500905688
-}
diff --git a/y2022/vision/calib_files/calibration_pi-971-1_2022-02-06_15-19-00.000000000.json b/y2022/vision/calib_files/calibration_pi-971-1_2022-02-06_15-19-00.000000000.json
new file mode 100755
index 0000000..6a4f05c
--- /dev/null
+++ b/y2022/vision/calib_files/calibration_pi-971-1_2022-02-06_15-19-00.000000000.json
@@ -0,0 +1,23 @@
+{
+ "node_name": "pi1",
+ "team_number": 971,
+ "intrinsics": [
+  398.312439,
+  0.0,
+  348.653015,
+  0.0,
+  397.627533,
+  257.368805,
+  0.0,
+  0.0,
+  1.0
+ ],
+ "dist_coeffs": [
+  0.143741,
+  -0.274336,
+  -0.000311,
+  -0.000171,
+  0.10252
+ ],
+ "calibration_timestamp": 1635600750700335075
+}
diff --git a/y2022/vision/viewer.cc b/y2022/vision/viewer.cc
index 7c13d1b..6ee6144 100644
--- a/y2022/vision/viewer.cc
+++ b/y2022/vision/viewer.cc
@@ -68,8 +68,10 @@
 
   // TODO(Milind) Store the target estimates and match them by timestamp to make
   // sure we're getting the right one.
-  CHECK(target_estimate_fetcher.FetchNext());
-  const TargetEstimate *target = target_estimate_fetcher.get();
+  const TargetEstimate *target_est = nullptr;
+  if (target_estimate_fetcher.Fetch()) {
+    target_est = target_estimate_fetcher.get();
+  }
 
   // Create color image:
   cv::Mat image_color_mat(cv::Size(image->cols(), image->rows()), CV_8UC2,
@@ -82,19 +84,23 @@
     return false;
   }
 
-  LOG(INFO) << image->monotonic_timestamp_ns()
-            << ": # blobs: " << target->blob_result()->filtered_blobs()->size();
+  LOG(INFO) << image->monotonic_timestamp_ns() << ": # unfiltered blobs: "
+            << target_est->blob_result()->unfiltered_blobs()->size()
+            << "; # filtered blobs: "
+            << target_est->blob_result()->filtered_blobs()->size();
 
-  cv::Mat ret_image;
-  BlobDetector::DrawBlobs(
-      ret_image, FbsToCvBlobs(*target->blob_result()->filtered_blobs()),
-      FbsToCvBlobs(*target->blob_result()->unfiltered_blobs()),
-      FbsToBlobStats(*target->blob_result()->blob_stats()),
-      cv::Point{target->blob_result()->centroid()->x(),
-                target->blob_result()->centroid()->y()});
+  cv::Mat ret_image(cv::Size(image->cols(), image->rows()), CV_8UC3);
+  if (target_est != nullptr) {
+    BlobDetector::DrawBlobs(
+        ret_image, FbsToCvBlobs(*target_est->blob_result()->filtered_blobs()),
+        FbsToCvBlobs(*target_est->blob_result()->unfiltered_blobs()),
+        FbsToBlobStats(*target_est->blob_result()->blob_stats()),
+        cv::Point{target_est->blob_result()->centroid()->x(),
+                  target_est->blob_result()->centroid()->y()});
+    cv::imshow("blobs", ret_image);
+  }
 
   cv::imshow("image", rgb_image);
-  cv::imshow("blobs", ret_image);
 
   int keystroke = cv::waitKey(1);
   if ((keystroke & 0xFF) == static_cast<int>('c')) {
@@ -119,6 +125,9 @@
   image_fetcher =
       event_loop.MakeFetcher<frc971::vision::CameraImage>(FLAGS_channel);
 
+  target_estimate_fetcher =
+      event_loop.MakeFetcher<y2022::vision::TargetEstimate>(FLAGS_channel);
+
   // Run the display loop
   event_loop.AddPhasedLoop(
       [&event_loop](int) {