Merge "Prevent aos_send from sending invalid messages"
diff --git a/aos/mutex/mutex_test.cc b/aos/mutex/mutex_test.cc
index 54e9201..8d10b26 100644
--- a/aos/mutex/mutex_test.cc
+++ b/aos/mutex/mutex_test.cc
@@ -241,10 +241,11 @@
       static_cast<Mutex *>(shm_malloc_aligned(sizeof(Mutex), alignof(Mutex)));
   new (mutex) Mutex();
 
-  std::thread thread([&]() { ASSERT_FALSE(mutex->Lock()); });
-  thread.join();
   EXPECT_DEATH(
       {
+        std::thread thread([&]() { ASSERT_FALSE(mutex->Lock()); });
+        thread.join();
+
         logging::SetImplementation(
             std::make_shared<util::DeathTestLogImplementation>());
         MutexLocker locker(mutex);
diff --git a/aos/network/multinode_timestamp_filter.cc b/aos/network/multinode_timestamp_filter.cc
index cae9e1f..1869ffb 100644
--- a/aos/network/multinode_timestamp_filter.cc
+++ b/aos/network/multinode_timestamp_filter.cc
@@ -399,7 +399,11 @@
             << a_solution_index << ") -> "
             << filter.filter->node_b()->name()->string_view() << "("
             << b_solution_index << "): " << std::setprecision(12)
-            << error.first.count() << " + " << error.second;
+            << error.first.count() << " + " << error.second << " "
+            << filter.filter->DebugOffsetErrorPoints(
+                   filter.b_filter, filter.pointer, base_clock_[i],
+                   time_offsets(a_solution_index), base_clock_[filter.b_index],
+                   time_offsets(b_solution_index));
       }
 
       // Reminder, our cost function has the following form.
diff --git a/aos/network/timestamp_filter.cc b/aos/network/timestamp_filter.cc
index f3aea62..b2e7294 100644
--- a/aos/network/timestamp_filter.cc
+++ b/aos/network/timestamp_filter.cc
@@ -1141,6 +1141,44 @@
                          rise, run, std::get<1>(points.first).count());
 }
 
+std::string NoncausalTimestampFilter::DebugOffsetErrorPoints(
+    const NoncausalTimestampFilter *other, Pointer pointer,
+    BootTimestamp ta_base, double ta, BootTimestamp tb_base, double tb) const {
+  NormalizeTimestamps(&ta_base, &ta);
+  NormalizeTimestamps(&tb_base, &tb);
+
+  const BootFilter *f = maybe_filter(pointer, ta_base.boot, tb_base.boot);
+  if (f == nullptr || f->filter.timestamps_size() == 0u) {
+    return "0";
+  }
+
+  if (f->filter.IsOutsideSamples(ta_base.time, ta)) {
+    auto reference_timestamp =
+        f->filter.GetReferenceTimestamp(ta_base.time, ta);
+
+    return std::string("Extrapolating using ") +
+           TimeString(reference_timestamp.second);
+  }
+
+  // FindTimestamps expects nullptr if we don't have the other direction.  But,
+  // any of the indirections to go get it might also be nullptr.  So keep
+  // checking if it's safe to continue, or give up and return nullptr.
+  const BootFilter *other_boot_filter =
+      other == nullptr ? nullptr : maybe_filter(tb_base.boot, ta_base.boot);
+  const SingleFilter *other_filter =
+      other_boot_filter == nullptr ? nullptr : &other_boot_filter->filter;
+
+  std::pair<std::tuple<monotonic_clock::time_point, chrono::nanoseconds>,
+            std::tuple<monotonic_clock::time_point, chrono::nanoseconds>>
+      points =
+          f->filter
+              .FindTimestamps(other_filter, true, pointer, ta_base.time, ta)
+              .second;
+
+  return std::string("Interpolating using ") + TimeString(points.first) + " " +
+         TimeString(points.second);
+}
+
 std::string NoncausalTimestampFilter::NodeNames() const {
   return absl::StrCat(node_a_->name()->string_view(), " -> ",
                       node_b_->name()->string_view());
diff --git a/aos/network/timestamp_filter.h b/aos/network/timestamp_filter.h
index 2361797..04ee605 100644
--- a/aos/network/timestamp_filter.h
+++ b/aos/network/timestamp_filter.h
@@ -361,6 +361,13 @@
                                Pointer pointer, logger::BootTimestamp ta_base,
                                double ta, logger::BootTimestamp tb_base,
                                double tb, size_t node_a, size_t node_b) const;
+  // Returns the string representation of the offset error at the provided
+  // point.
+  std::string DebugOffsetErrorPoints(const NoncausalTimestampFilter *other,
+                                     Pointer pointer,
+                                     logger::BootTimestamp ta_base, double ta,
+                                     logger::BootTimestamp tb_base,
+                                     double tb) const;
 
   // Confirms that the solution meets the constraints.  Returns true on success.
   bool ValidateSolution(const NoncausalTimestampFilter *other, Pointer pointer,