Merge "Ignore pitch from camera readings"
diff --git a/aos/configuration.cc b/aos/configuration.cc
index 25d59ff..b388bd3 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -272,55 +272,6 @@
   return a->name()->string_view() == name;
 }
 
-// Maps name for the provided maps.  Modifies name.
-void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
-                std::string *name, std::string_view type, const Node *node) {
-  // For the same reason we merge configs in reverse order, we want to process
-  // maps in reverse order.  That lets the outer config overwrite channels from
-  // the inner configs.
-  for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
-    if (!i->has_match() || !i->match()->has_name()) {
-      continue;
-    }
-    if (!i->has_rename() || !i->rename()->has_name()) {
-      continue;
-    }
-
-    // Handle normal maps (now that we know that match and rename are filled
-    // out).
-    const std::string_view match_name = i->match()->name()->string_view();
-    if (match_name != *name) {
-      if (match_name.back() == '*' &&
-          std::string_view(*name).substr(
-              0, std::min(name->size(), match_name.size() - 1)) ==
-              match_name.substr(0, match_name.size() - 1)) {
-        CHECK_EQ(match_name.find('*'), match_name.size() - 1);
-      } else {
-        continue;
-      }
-    }
-
-    // Handle type specific maps.
-    if (i->match()->has_type() && i->match()->type()->string_view() != type) {
-      continue;
-    }
-
-    // Now handle node specific maps.
-    if (node != nullptr && i->match()->has_source_node() &&
-        i->match()->source_node()->string_view() !=
-            node->name()->string_view()) {
-      continue;
-    }
-
-    std::string new_name(i->rename()->name()->string_view());
-    if (match_name.back() == '*') {
-      new_name += std::string(name->substr(match_name.size() - 1));
-    }
-    VLOG(1) << "Renamed \"" << *name << "\" to \"" << new_name << "\"";
-    *name = std::move(new_name);
-  }
-}
-
 void ValidateConfiguration(const Flatbuffer<Configuration> &config) {
   // No imports should be left.
   CHECK(!config.message().has_imports());
@@ -419,6 +370,55 @@
 
 }  // namespace
 
+// Maps name for the provided maps.  Modifies name.
+void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
+                std::string *name, std::string_view type, const Node *node) {
+  // For the same reason we merge configs in reverse order, we want to process
+  // maps in reverse order.  That lets the outer config overwrite channels from
+  // the inner configs.
+  for (auto i = maps->rbegin(); i != maps->rend(); ++i) {
+    if (!i->has_match() || !i->match()->has_name()) {
+      continue;
+    }
+    if (!i->has_rename() || !i->rename()->has_name()) {
+      continue;
+    }
+
+    // Handle normal maps (now that we know that match and rename are filled
+    // out).
+    const std::string_view match_name = i->match()->name()->string_view();
+    if (match_name != *name) {
+      if (match_name.back() == '*' &&
+          std::string_view(*name).substr(
+              0, std::min(name->size(), match_name.size() - 1)) ==
+              match_name.substr(0, match_name.size() - 1)) {
+        CHECK_EQ(match_name.find('*'), match_name.size() - 1);
+      } else {
+        continue;
+      }
+    }
+
+    // Handle type specific maps.
+    if (i->match()->has_type() && i->match()->type()->string_view() != type) {
+      continue;
+    }
+
+    // Now handle node specific maps.
+    if (node != nullptr && i->match()->has_source_node() &&
+        i->match()->source_node()->string_view() !=
+            node->name()->string_view()) {
+      continue;
+    }
+
+    std::string new_name(i->rename()->name()->string_view());
+    if (match_name.back() == '*') {
+      new_name += std::string(name->substr(match_name.size() - 1));
+    }
+    VLOG(1) << "Renamed \"" << *name << "\" to \"" << new_name << "\"";
+    *name = std::move(new_name);
+  }
+}
+
 FlatbufferDetachedBuffer<Configuration> MergeConfiguration(
     const Flatbuffer<Configuration> &config) {
   // auto_merge_config will contain all the fields of the Configuration that are
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 2eb3094..05555c6 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -39,6 +39,12 @@
     "The time to buffer ahead in the log file to accurately reconstruct time.");
 
 namespace aos {
+namespace configuration {
+// We don't really want to expose this publicly, but log reader doesn't really
+// want to re-implement it.
+void HandleMaps(const flatbuffers::Vector<flatbuffers::Offset<aos::Map>> *maps,
+                std::string *name, std::string_view type, const Node *node);
+}
 namespace logger {
 namespace {
 
@@ -892,6 +898,65 @@
         nullptr));
     channel_offsets.emplace_back(
         CopyChannel(c, pair.second.remapped_name, "", &fbb));
+
+    if (c->has_destination_nodes()) {
+      for (const Connection *connection : *c->destination_nodes()) {
+        switch (connection->timestamp_logger()) {
+          case LoggerConfig::LOCAL_LOGGER:
+          case LoggerConfig::NOT_LOGGED:
+            // There is no timestamp channel associated with this, so ignore it.
+            break;
+
+          case LoggerConfig::REMOTE_LOGGER:
+          case LoggerConfig::LOCAL_AND_REMOTE_LOGGER:
+            // We want to make a split timestamp channel regardless of what type
+            // of log this used to be.  No sense propagating the single
+            // timestamp channel.
+
+            CHECK(connection->has_timestamp_logger_nodes());
+            for (const flatbuffers::String *timestamp_logger_node :
+                 *connection->timestamp_logger_nodes()) {
+              const Node *node = configuration::GetNode(
+                  logged_configuration(), timestamp_logger_node->string_view());
+              message_bridge::ChannelTimestampFinder finder(
+                  logged_configuration(), "log_reader", node);
+
+              // We are assuming here that all the maps are setup correctly to
+              // handle arbitrary timestamps.  Apply the maps for this node to
+              // see what name this ends up with.
+              std::string name = finder.SplitChannelName(
+                  pair.second.remapped_name, c->type()->str(), connection);
+              std::string unmapped_name = name;
+              configuration::HandleMaps(logged_configuration()->maps(), &name,
+                                        "aos.message_bridge.RemoteMessage",
+                                        node);
+              CHECK_NE(name, unmapped_name)
+                  << ": Remote timestamp channel was not remapped, this is "
+                     "very fishy";
+              flatbuffers::Offset<flatbuffers::String> channel_name_offset =
+                  fbb.CreateString(name);
+              flatbuffers::Offset<flatbuffers::String> channel_type_offset =
+                  fbb.CreateString("aos.message_bridge.RemoteMessage");
+              flatbuffers::Offset<flatbuffers::String> source_node_offset =
+                  fbb.CreateString(timestamp_logger_node->string_view());
+
+              // Now, build a channel.  Don't log it, 2 senders, and match the
+              // source frequency.
+              Channel::Builder channel_builder(fbb);
+              channel_builder.add_name(channel_name_offset);
+              channel_builder.add_type(channel_type_offset);
+              channel_builder.add_source_node(source_node_offset);
+              channel_builder.add_logger(LoggerConfig::NOT_LOGGED);
+              channel_builder.add_num_senders(2);
+              if (c->has_frequency()) {
+                channel_builder.add_frequency(c->frequency());
+              }
+              channel_offsets.emplace_back(channel_builder.Finish());
+            }
+            break;
+        }
+      }
+    }
   }
 
   // Now reconstruct the original channels, translating types as needed
diff --git a/aos/events/logging/logger_test.cc b/aos/events/logging/logger_test.cc
index 13b2312..2b504f3 100644
--- a/aos/events/logging/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -1767,6 +1767,83 @@
   reader.Deregister();
 }
 
+// Tests that we can remap a forwarded channel as well.
+TEST_P(MultinodeLoggerTest, RemapForwardedLoggedChannel) {
+  time_converter_.StartEqual();
+  {
+    LoggerState pi1_logger = MakeLogger(pi1_);
+    LoggerState pi2_logger = MakeLogger(pi2_);
+
+    event_loop_factory_.RunFor(chrono::milliseconds(95));
+
+    StartLogger(&pi1_logger);
+    StartLogger(&pi2_logger);
+
+    event_loop_factory_.RunFor(chrono::milliseconds(20000));
+  }
+
+  LogReader reader(SortParts(logfiles_));
+
+  reader.RemapLoggedChannel<examples::Ping>("/test");
+
+  SimulatedEventLoopFactory log_reader_factory(reader.configuration());
+  log_reader_factory.set_send_delay(chrono::microseconds(0));
+
+  reader.Register(&log_reader_factory);
+
+  const Node *pi1 =
+      configuration::GetNode(log_reader_factory.configuration(), "pi1");
+  const Node *pi2 =
+      configuration::GetNode(log_reader_factory.configuration(), "pi2");
+
+  // Confirm we can read the data on the remapped channel, just for pi1. Nothing
+  // else should have moved.
+  std::unique_ptr<EventLoop> pi1_event_loop =
+      log_reader_factory.MakeEventLoop("test", pi1);
+  pi1_event_loop->SkipTimingReport();
+  std::unique_ptr<EventLoop> full_pi1_event_loop =
+      log_reader_factory.MakeEventLoop("test", pi1);
+  full_pi1_event_loop->SkipTimingReport();
+  std::unique_ptr<EventLoop> pi2_event_loop =
+      log_reader_factory.MakeEventLoop("test", pi2);
+  pi2_event_loop->SkipTimingReport();
+
+  MessageCounter<examples::Ping> pi1_ping(pi1_event_loop.get(), "/test");
+  MessageCounter<examples::Ping> pi2_ping(pi2_event_loop.get(), "/test");
+  MessageCounter<examples::Ping> pi1_original_ping(pi1_event_loop.get(),
+                                                   "/original/test");
+  MessageCounter<examples::Ping> pi2_original_ping(pi2_event_loop.get(),
+                                                   "/original/test");
+
+  std::unique_ptr<MessageCounter<message_bridge::RemoteMessage>>
+      pi1_original_ping_timestamp;
+  std::unique_ptr<MessageCounter<message_bridge::RemoteMessage>>
+      pi1_ping_timestamp;
+  if (!shared()) {
+    pi1_original_ping_timestamp =
+        std::make_unique<MessageCounter<message_bridge::RemoteMessage>>(
+            pi1_event_loop.get(),
+            "/pi1/aos/remote_timestamps/pi2/original/test/aos-examples-Ping");
+    pi1_ping_timestamp =
+        std::make_unique<MessageCounter<message_bridge::RemoteMessage>>(
+            pi1_event_loop.get(),
+            "/pi1/aos/remote_timestamps/pi2/test/aos-examples-Ping");
+  }
+
+  log_reader_factory.Run();
+
+  EXPECT_EQ(pi1_ping.count(), 0u);
+  EXPECT_EQ(pi2_ping.count(), 0u);
+  EXPECT_NE(pi1_original_ping.count(), 0u);
+  EXPECT_NE(pi2_original_ping.count(), 0u);
+  if (!shared()) {
+    EXPECT_NE(pi1_original_ping_timestamp->count(), 0u);
+    EXPECT_EQ(pi1_ping_timestamp->count(), 0u);
+  }
+
+  reader.Deregister();
+}
+
 // Tests that we properly recreate forwarded timestamps when replaying a log.
 // This should be enough that we can then re-run the logger and get a valid log
 // back.
diff --git a/aos/network/timestamp_channel.cc b/aos/network/timestamp_channel.cc
index bc8c1d9..bbd62c4 100644
--- a/aos/network/timestamp_channel.cc
+++ b/aos/network/timestamp_channel.cc
@@ -12,11 +12,14 @@
 
 std::string ChannelTimestampFinder::SplitChannelName(
     const Channel *channel, const Connection *connection) {
-  std::string type(channel->type()->string_view());
+  return SplitChannelName(channel->name()->string_view(), channel->type()->str(), connection);
+}
+
+std::string ChannelTimestampFinder::SplitChannelName(
+    std::string_view name, std::string type, const Connection *connection) {
   std::replace(type.begin(), type.end(), '.', '-');
   return absl::StrCat("/aos/remote_timestamps/",
-                      connection->name()->string_view(),
-                      channel->name()->string_view(), "/", type);
+                      connection->name()->string_view(), name, "/", type);
 }
 
 std::string ChannelTimestampFinder::CombinedChannelName(
diff --git a/aos/network/timestamp_channel.h b/aos/network/timestamp_channel.h
index 1702632..738ca10 100644
--- a/aos/network/timestamp_channel.h
+++ b/aos/network/timestamp_channel.h
@@ -29,6 +29,8 @@
                             const Connection *connection);
   std::string SplitChannelName(const Channel *channel,
                                const Connection *connection);
+  std::string SplitChannelName(std::string_view name, std::string type,
+                               const Connection *connection);
   std::string CombinedChannelName(std::string_view remote_node);
 
  private:
diff --git a/aos/starter/starter_cmd.cc b/aos/starter/starter_cmd.cc
index 8f9e125..e9b6ed5 100644
--- a/aos/starter/starter_cmd.cc
+++ b/aos/starter/starter_cmd.cc
@@ -2,6 +2,7 @@
 #include <functional>
 #include <iostream>
 #include <optional>
+#include <string_view>
 #include <unordered_map>
 
 #include "absl/strings/str_format.h"
@@ -23,13 +24,14 @@
                         {"restart", aos::starter::Command::RESTART}};
 
 void PrintKey() {
-  absl::PrintF("%-30s %-30s %s\n\n", "Name", "Time since last started", "State");
+  absl::PrintF("%-30s %-30s %s\n\n", "Name", "Time since last started",
+               "State");
 }
 
 void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
-    const aos::monotonic_clock::time_point &time) {
-  const auto last_start_time =
-      aos::monotonic_clock::time_point(chrono::nanoseconds(app_status->last_start_time()));
+                            const aos::monotonic_clock::time_point &time) {
+  const auto last_start_time = aos::monotonic_clock::time_point(
+      chrono::nanoseconds(app_status->last_start_time()));
   const auto time_running =
       chrono::duration_cast<chrono::seconds>(time - last_start_time);
   absl::PrintF("%-30s %-30s %s\n", app_status->name()->string_view(),
@@ -54,7 +56,8 @@
     }
   } else if (argc == 2) {
     // Print status for the specified process.
-    const char *application_name = argv[1];
+    const auto application_name =
+        aos::starter::FindApplication(argv[1], config);
     auto status = aos::starter::GetStatus(application_name, config);
     PrintKey();
     PrintApplicationStatus(&status.message(), aos::monotonic_clock::now());
@@ -68,7 +71,6 @@
 bool InteractWithProgram(int argc, char **argv,
                          const aos::Configuration *config) {
   const char *command_string = argv[0];
-
   if (argc != 2) {
     LOG(ERROR) << "The \"" << command_string
                << "\" command requires an application name as an argument.";
@@ -81,8 +83,7 @@
       << "\" is not in kCommandConversions.";
 
   const aos::starter::Command command = command_search->second;
-  const char *application_name = argv[1];
-
+  const auto application_name = aos::starter::FindApplication(argv[1], config);
   if (aos::starter::SendCommandBlocking(command, application_name, config,
                                         chrono::seconds(3))) {
     switch (command) {
@@ -143,7 +144,7 @@
 
   if (parsing_failed) {
     LOG(ERROR) << "Parsing failed. Valid commands are:";
-    for (auto entry: kCommands) {
+    for (auto entry : kCommands) {
       LOG(ERROR) << " - " << entry.first;
     }
     return 1;
diff --git a/aos/starter/starter_rpc_lib.cc b/aos/starter/starter_rpc_lib.cc
index 20f6f5a..67b3fb2 100644
--- a/aos/starter/starter_rpc_lib.cc
+++ b/aos/starter/starter_rpc_lib.cc
@@ -26,6 +26,19 @@
   return *search;
 }
 
+std::string_view FindApplication(const std::string_view &name,
+                                 const aos::Configuration *config) {
+  std::string_view app_name = name;
+  for (const auto app : *config->applications()) {
+    if (app->executable_name() != nullptr &&
+        app->executable_name()->string_view() == name) {
+      app_name = app->name()->string_view();
+      break;
+    }
+  }
+  return app_name;
+}
+
 bool SendCommandBlocking(aos::starter::Command command, std::string_view name,
                          const aos::Configuration *config,
                          std::chrono::milliseconds timeout) {
@@ -127,15 +140,15 @@
                       aos::starter::ApplicationStatus>::Empty();
 }
 
-std::optional<const aos::FlatbufferVector<aos::starter::Status>> GetStarterStatus(
-    const aos::Configuration *config) {
+std::optional<const aos::FlatbufferVector<aos::starter::Status>>
+GetStarterStatus(const aos::Configuration *config) {
   ShmEventLoop event_loop(config);
   event_loop.SkipAosLog();
 
   auto status_fetcher = event_loop.MakeFetcher<aos::starter::Status>("/aos");
   status_fetcher.Fetch();
-  return (status_fetcher ? std::make_optional(status_fetcher.CopyFlatBuffer()) :
-      std::nullopt);
+  return (status_fetcher ? std::make_optional(status_fetcher.CopyFlatBuffer())
+                         : std::nullopt);
 }
 
 }  // namespace starter
diff --git a/aos/starter/starter_rpc_lib.h b/aos/starter/starter_rpc_lib.h
index 24c757e..7d6de873 100644
--- a/aos/starter/starter_rpc_lib.h
+++ b/aos/starter/starter_rpc_lib.h
@@ -16,6 +16,11 @@
 const aos::starter::ApplicationStatus *FindApplicationStatus(
     const aos::starter::Status &status, std::string_view name);
 
+// Checks if the name is an executable name and if it is, it returns that
+// application's name, otherwise returns name as given
+std::string_view FindApplication(const std::string_view &name,
+                                 const aos::Configuration *config);
+
 // Sends the given command to the application with the name name. Creates a
 // temporary event loop from the provided config for sending the command and
 // receiving back status messages. Returns true if the command executed
@@ -33,8 +38,8 @@
 
 // Fetches the entire status message of starter. Creates a temporary event loop
 // from the provided config for fetching.
-std::optional<const aos::FlatbufferVector<aos::starter::Status>> GetStarterStatus(
-    const aos::Configuration *config);
+std::optional<const aos::FlatbufferVector<aos::starter::Status>>
+GetStarterStatus(const aos::Configuration *config);
 
 }  // namespace starter
 }  // namespace aos
diff --git a/third_party/rawrtc/rawrtc-data-channel/BUILD b/third_party/rawrtc/rawrtc-data-channel/BUILD
index 096024a..500ad8b 100644
--- a/third_party/rawrtc/rawrtc-data-channel/BUILD
+++ b/third_party/rawrtc/rawrtc-data-channel/BUILD
@@ -25,6 +25,10 @@
         "-Wno-cast-qual",
         "-Wno-cast-align",
         "-Wno-missing-braces",
+        "-DUSE_OPENSSL",
+        "-DHAVE_INET6",
+        "-DHAVE_STDBOOL_H",
+        "-DHAVE_INTTYPES_H",
     ],
     defines = [
         "RAWRTCDC_HAVE_SCTP_REDIRECT_TRANSPORT=0",
@@ -34,6 +38,6 @@
     visibility = ["//visibility:public"],
     deps = [
         "@com_github_rawrtc_rawrtc_common//:rawrtcc",
-        "@com_github_rawrtc_usrsctp//:usrsctp_crc32",
+        "@com_github_rawrtc_usrsctp//:usrsctp",
     ],
 )
diff --git a/third_party/rawrtc/rawrtc/BUILD b/third_party/rawrtc/rawrtc/BUILD
index df14384..77416d8 100644
--- a/third_party/rawrtc/rawrtc/BUILD
+++ b/third_party/rawrtc/rawrtc/BUILD
@@ -21,6 +21,11 @@
         "-Wno-cast-qual",
         "-Wno-missing-braces",
         "-Iexternal/com_github_rawrtc_rawrtc/",
+        "-DUSE_OPENSSL",
+        #"-DUSE_ZLIB",
+        "-DHAVE_INET6",
+        "-DHAVE_STDBOOL_H",
+        "-DHAVE_INTTYPES_H",
     ],
     includes = ["include/"],
     local_defines = [
@@ -32,3 +37,29 @@
         "@com_github_rawrtc_rawrtc_data_channel//:rawrtcdc",
     ],
 )
+
+cc_binary(
+    name = "peer-connection",
+    srcs = [
+        "tools/helper/common.c",
+        "tools/helper/common.h",
+        "tools/helper/handler.c",
+        "tools/helper/handler.h",
+        "tools/helper/parameters.c",
+        "tools/helper/parameters.h",
+        "tools/helper/utils.c",
+        "tools/helper/utils.h",
+        "tools/peer-connection.c",
+    ],
+    copts = [
+        "-Wno-missing-braces",
+        "-Wno-incompatible-pointer-types-discards-qualifiers",
+    ] + compiler_select({
+        "clang": [],
+        "gcc": [
+            "-Wno-discarded-qualifiers",
+        ],
+    }),
+    includes = ["tools"],
+    deps = [":rawrtc"],
+)
diff --git a/third_party/rawrtc/re/BUILD b/third_party/rawrtc/re/BUILD
index af720c5..63efff7 100644
--- a/third_party/rawrtc/re/BUILD
+++ b/third_party/rawrtc/re/BUILD
@@ -13,7 +13,9 @@
             "src/hmac/apple/**",
             "src/hmac/hmac.c",
             "src/mqueue/win32/**",
-            "src/tls/openssl/**",
+            "src/sha/**",
+            "src/md5/**",
+            "src/ice/ice.c",
             "src/dns/win32/**",
             "src/mod/win32/**",
             "src/lock/lock.c",
@@ -27,6 +29,7 @@
     copts = compiler_select({
         "clang": [
             "-Wno-incompatible-pointer-types-discards-qualifiers",
+            "-Wno-macro-redefined",
         ],
         "gcc": [
             "-Wno-discarded-qualifiers",
@@ -40,6 +43,38 @@
         "-Wno-cast-qual",
         "-Wno-cast-align",
         "-Wno-implicit-function-declaration",
+        "-DUSE_OPENSSL",
+        "-DUSE_TLS",
+        "-DUSE_OPENSSL_DTLS",
+        "-DUSE_DTLS",
+        "-DUSE_OPENSSL_SRTP",
+        "-DUSE_DTLS_SRTP",
+        #"-DUSE_ZLIB",
+        "-DHAVE_INET6",
+        "-DHAVE_SELECT",
+        "-DHAVE_STDBOOL_H",
+        "-DHAVE_INTTYPES_H",
+        "-DHAVE_NET_ROUTE_H",
+        "-DHAVE_SYS_SYSCTL_H",
+        "-DHAVE_FORK",
+        "-DHAVE_INET_NTOP",
+        "-DHAVE_PWD_H",
+        "-DHAVE_SELECT_H",
+        "-DHAVE_SETRLIMIT",
+        "-DHAVE_SIGNAL",
+        "-DHAVE_STRERROR_R",
+        "-DHAVE_STRINGS_H",
+        "-DHAVE_SYS_TIME_H",
+        "-DHAVE_UNAME",
+        "-DHAVE_UNISTD_H",
+        "-DHAVE_PTHREAD",
+        "-DHAVE_GETIFADDRS",
+        "-DHAVE_DLFCN",
+        "-DHAVE_EPOLL",
+        "-DHAVE_RESOLV",
+        "-DHAVE_POLL",
+        "-DHAVE_INET_PTON",
+        "-DHAVE_ROUTE_LIST",
     ],
     defines = ["HAVE_INTTYPES_H"],
     includes = ["include/"],
diff --git a/third_party/rawrtc/re/include/meson.build b/third_party/rawrtc/re/include/meson.build
new file mode 100644
index 0000000..c9e1b30
--- /dev/null
+++ b/third_party/rawrtc/re/include/meson.build
@@ -0,0 +1,52 @@
+includes = files([
+    're.h',
+    're_aes.h',
+    're_base64.h',
+    're_bfcp.h',
+    're_bitv.h',
+    're_conf.h',
+    're_crc32.h',
+    're_dbg.h',
+    're_dns.h',
+    're_fmt.h',
+    're_hash.h',
+    're_hmac.h',
+    're_httpauth.h',
+    're_http.h',
+    're_ice.h',
+    're_jbuf.h',
+    're_json.h',
+    're_list.h',
+    're_lock.h',
+    're_main.h',
+    're_mbuf.h',
+    're_md5.h',
+    're_mem.h',
+    're_mod.h',
+    're_mqueue.h',
+    're_msg.h',
+    're_natbd.h',
+    're_net.h',
+    're_odict.h',
+    're_rtmp.h',
+    're_rtp.h',
+    're_sa.h',
+    're_sdp.h',
+    're_sha.h',
+    're_sipevent.h',
+    're_sip.h',
+    're_sipreg.h',
+    're_sipsess.h',
+    're_srtp.h',
+    're_stun.h',
+    're_sys.h',
+    're_tcp.h',
+    're_telev.h',
+    're_tls.h',
+    're_tmr.h',
+    're_turn.h',
+    're_types.h',
+    're_udp.h',
+    're_uri.h',
+    're_websock.h',
+])
diff --git a/third_party/rawrtc/re/include/re_sdp.h b/third_party/rawrtc/re/include/re_sdp.h
index f34bab5..457cb4d 100644
--- a/third_party/rawrtc/re/include/re_sdp.h
+++ b/third_party/rawrtc/re/include/re_sdp.h
@@ -99,6 +99,7 @@
 void sdp_media_set_lport_rtcp(struct sdp_media *m, uint16_t port);
 void sdp_media_set_laddr_rtcp(struct sdp_media *m, const struct sa *laddr);
 void sdp_media_set_ldir(struct sdp_media *m, enum sdp_dir dir);
+void sdp_media_ldir_exclude(struct sdp_media *m, bool exclude);
 int  sdp_media_set_lattr(struct sdp_media *m, bool replace,
 			 const char *name, const char *value, ...);
 void sdp_media_del_lattr(struct sdp_media *m, const char *name);
diff --git a/third_party/rawrtc/re/include/re_tls.h b/third_party/rawrtc/re/include/re_tls.h
index 05b3c93..09ad028 100644
--- a/third_party/rawrtc/re/include/re_tls.h
+++ b/third_party/rawrtc/re/include/re_tls.h
@@ -54,6 +54,8 @@
 		     uint8_t *srv_key, size_t srv_key_size);
 const char *tls_cipher_name(const struct tls_conn *tc);
 int tls_set_ciphers(struct tls *tls, const char *cipherv[], size_t count);
+int tls_set_dh_params_pem(struct tls *tls, const char *pem, size_t len);
+int tls_set_dh_params_der(struct tls *tls, const uint8_t *der, size_t len);
 int tls_set_servername(struct tls_conn *tc, const char *servername);
 
 
@@ -66,6 +68,9 @@
 /* UDP (DTLS) */
 
 typedef void (dtls_conn_h)(const struct sa *peer, void *arg);
+typedef int (dtls_send_h)(struct tls_conn *tc, const struct sa *dst,
+		struct mbuf *mb, void *arg);
+typedef size_t (dtls_mtu_h)(struct tls_conn *tc, void *arg);
 typedef void (dtls_estab_h)(void *arg);
 typedef void (dtls_recv_h)(struct mbuf *mb, void *arg);
 typedef void (dtls_close_h)(int err, void *arg);
@@ -75,8 +80,13 @@
 int dtls_listen(struct dtls_sock **sockp, const struct sa *laddr,
 		struct udp_sock *us, uint32_t htsize, int layer,
 		dtls_conn_h *connh, void *arg);
+int dtls_socketless(struct dtls_sock **sockp, uint32_t htsize,
+		dtls_conn_h *connh, dtls_send_h *sendh, dtls_mtu_h *mtuh,
+		void *arg);
 struct udp_sock *dtls_udp_sock(struct dtls_sock *sock);
 void dtls_set_mtu(struct dtls_sock *sock, size_t mtu);
+size_t dtls_headroom(struct dtls_sock *sock);
+void dtls_set_headroom(struct dtls_sock *sock, size_t headroom);
 int dtls_connect(struct tls_conn **ptc, struct tls *tls,
 		 struct dtls_sock *sock, const struct sa *peer,
 		 dtls_estab_h *estabh, dtls_recv_h *recvh,
@@ -86,6 +96,7 @@
 		dtls_estab_h *estabh, dtls_recv_h *recvh,
 		dtls_close_h *closeh, void *arg);
 int dtls_send(struct tls_conn *tc, struct mbuf *mb);
+bool dtls_receive(struct dtls_sock *sock, struct sa *src, struct mbuf *mb);
 void dtls_set_handlers(struct tls_conn *tc, dtls_estab_h *estabh,
 		       dtls_recv_h *recvh, dtls_close_h *closeh, void *arg);
 const struct sa *dtls_peer(const struct tls_conn *tc);
diff --git a/third_party/rawrtc/re/meson.build b/third_party/rawrtc/re/meson.build
new file mode 100644
index 0000000..2757cff
--- /dev/null
+++ b/third_party/rawrtc/re/meson.build
@@ -0,0 +1,373 @@
+# Project definition
+project('rawrre', 'c',
+    version: '0.6.0',
+    default_options: ['c_std=c99'],
+    meson_version: '>=0.46.0')
+
+# Set compiler warning flags
+compiler = meson.get_compiler('c')
+compiler_args = compiler.get_supported_arguments([
+    '-Wall',
+    '-Wmissing-declarations',
+    '-Wmissing-prototypes',
+    '-Wstrict-prototypes',
+    '-Wbad-function-cast',
+    '-Wsign-compare',
+    '-Wnested-externs',
+    '-Wshadow',
+    '-Waggregate-return',
+    '-Wcast-align',
+    '-Wextra',
+    '-Wold-style-definition',
+    '-Wdeclaration-after-statement',
+    '-Wuninitialized',  # TODO: Only when optimising? But why?
+    '-Wshorten-64-to-32',  # Apple-specific
+    '-pedantic',
+])
+add_project_arguments(compiler_args, language: 'c')
+
+# TODO: Include -g flag for extra debug stuff?
+# TODO: Optimise for speed or size?
+
+# Configuration
+# TODO: Generate a configuration file instead of defining? Would simplify the
+#       configuration.
+#       This would allow projects depending on this able to import those
+#       defines, too.
+compile_args = []
+configuration = configuration_data()
+
+# OS
+system = host_machine.system()
+add_project_arguments('-DOS="@0@"'.format(system), language: 'c')
+configuration.set_quoted('OS', system)
+if system == 'darwin'
+    add_project_arguments('-DDARWIN', language: 'c')
+    configuration.set('DARWIN', 1)
+elif system == 'dragonfly'
+    add_project_arguments('-DDRAGONFLY', language: 'c')
+    configuration.set('DRAGONFLY', 1)
+elif system == 'freebsd'
+    add_project_arguments('-DFREEBSD', language: 'c')
+    configuration.set('FREEBSD', 1)
+elif system == 'gnu'
+    add_project_arguments('-DGNU', language: 'c')
+    configuration.set('GNU', 1)
+elif system == 'gnu/kfreebsd'
+    add_project_arguments(
+        '-DKFREEBSD',
+        '-D_GNU_SOURCE',
+        language: 'c')
+    configuration.set('GNU', 1)
+    configuration.set('_GNU_SOURCE', 1)
+elif system == 'linux'
+    add_project_arguments('-DLINUX', language: 'c')
+    configuration.set('LINUX', 1)
+elif system == 'netbsd'
+    add_project_arguments('-DDNETBSD', language: 'c')
+    configuration.set('NETBSD', 1)
+elif system == 'sunos'
+    add_project_arguments('-DSOLARIS', language: 'c')
+    configuration.set('SOLARIS', 1)
+    compile_args += '-DSOLARIS'
+elif system == 'windows'
+    add_project_arguments('-DWIN32', language: 'c')
+    configuration.set('WIN32', 1)
+    compile_args += '-DWIN32'
+else
+    warning('Unhandled OS: @0@'.format(system))
+endif
+
+# Architecture
+cpu = host_machine.cpu()
+add_project_arguments('-DARCH="@0@"'.format(cpu), language: 'c')
+configuration.set_quoted('ARCH', cpu)
+
+# Dependency: Threads
+# TODO: Make this overridable
+thread_dep = dependency('threads', required: false)
+
+# Dependency: OpenSSL
+openssl_dep = dependency('openssl', version: '>=0.9.8', required: false)
+# TODO: Make this overridable
+if openssl_dep.found()
+    add_project_arguments(
+        '-DUSE_OPENSSL',
+        '-DUSE_TLS',
+        language: 'c')
+    configuration.set('USE_OPENSSL', 1)
+    configuration.set('USE_TLS', 1)
+    compile_args += '-DUSE_OPENSSL'
+
+    # Check for DTLS
+    if compiler.has_header('openssl/dtls1.h')
+        add_project_arguments(
+            '-DUSE_OPENSSL_DTLS',
+            '-DUSE_DTLS',
+            language: 'c')
+        configuration.set('USE_OPENSSL_DTLS', 1)
+        configuration.set('USE_DTLS', 1)
+    endif
+
+    # Check for SRTP
+    if compiler.has_header('openssl/srtp.h')
+        add_project_arguments(
+            '-DUSE_OPENSSL_SRTP',
+            '-DUSE_DTLS_SRTP',
+            language: 'c')
+        configuration.set('USE_OPENSSL_SRTP', 1)
+        configuration.set('USE_DTLS_SRTP', 1)
+    endif
+endif
+
+# Dependency: zlib
+# TODO: Make this overridable
+# TODO: Arbitrary version, ask maintainers
+zlib_dep = dependency('zlib', version: '>=1.2.8', required: false)
+if zlib_dep.found()
+    add_project_arguments('-DUSE_ZLIB', language: 'c')
+    configuration.set('USE_ZLIB', 1)
+    compile_args += '-DUSE_ZLIB'
+endif
+
+# Dependencies list
+dependencies = [
+    thread_dep,
+    openssl_dep,
+    zlib_dep,
+]
+
+# Features: Common
+add_project_arguments(
+    '-DHAVE_INET6',
+    '-DHAVE_SELECT',
+    '-DHAVE_STDBOOL_H',
+    language: 'c')
+configuration.set('HAVE_INET6', 1)
+configuration.set('HAVE_SELECT', 1)
+configuration.set('HAVE_STDBOOL_H', 1)
+compile_args += [
+    '-DHAVE_INET6',
+    '-DHAVE_STDBOOL_H',
+]
+
+# Features: Check for fixed size integer types
+if compiler.has_header('inttypes.h')
+    add_project_arguments('-DHAVE_INTTYPES_H', language: 'c')
+    configuration.set('HAVE_INTTYPES_H', 1)
+    compile_args += '-DHAVE_INTTYPES_H'
+endif
+
+# Features: Check for route
+have_net_route = compiler.has_header('net/route.h')
+if have_net_route
+    add_project_arguments('-DHAVE_NET_ROUTE_H', language: 'c')
+    configuration.set('HAVE_NET_ROUTE_H', 1)
+endif
+
+# Features: Check for sysctl
+have_sysctl = compiler.has_header('sys/sysctl.h')
+if have_sysctl
+    add_project_arguments('-DHAVE_SYS_SYSCTL_H', language: 'c')
+    configuration.set('HAVE_SYS_SYSCTL_H', 1)
+endif
+
+# Features: OS-specific
+if system == 'windows'
+    # Windows
+    add_project_arguments(
+        '-DHAVE_IO_H',
+        '-D_WIN32_WINNT=0x0501',
+        '-D__ssize_t_defined',
+        language: 'c')
+    configuration.set('HAVE_IO_H', 1)
+    configuration.set('_WIN32_WINNT', 0x0501)
+    configuration.set('__ssize_t_defined', 1)
+    compile_args += '-D__ssize_t_defined'
+
+    # Additional linking
+    dependencies += compiler.find_library('wsock32', required: true)
+    dependencies += compiler.find_library('ws2_32', required: true)
+    dependencies += compiler.find_library('iphlpapi', required: true)
+
+    # TODO: APP_LFLAGS	+= -Wl,--export-all-symbols
+else
+    # UNIX
+    add_project_arguments(
+        '-DHAVE_FORK',
+        '-DHAVE_INET_NTOP',
+        '-DHAVE_PWD_H',
+        '-DHAVE_SELECT_H',
+        '-DHAVE_SETRLIMIT',
+        '-DHAVE_SIGNAL',
+        '-DHAVE_STRERROR_R',
+        '-DHAVE_STRINGS_H',
+        '-DHAVE_SYS_TIME_H',
+        '-DHAVE_UNAME',
+        '-DHAVE_UNISTD_H',
+        language: 'c')
+    configuration.set('HAVE_FORK', 1)
+    configuration.set('HAVE_INET_NTOP', 1)
+    configuration.set('HAVE_PWD_H', 1)
+    configuration.set('HAVE_SELECT_H', 1)
+    configuration.set('HAVE_SETRLIMIT', 1)
+    configuration.set('HAVE_SIGNAL', 1)
+    configuration.set('HAVE_STRERROR_R', 1)
+    configuration.set('HAVE_STRINGS_H', 1)
+    configuration.set('HAVE_SYS_TIME_H', 1)
+    configuration.set('HAVE_UNAME', 1)
+    configuration.set('HAVE_UNISTD_H', 1)
+
+    # Solaris requires some additional linking
+    if system == 'sunos'
+        dependencies += compiler.find_library('socket', required: true)
+        dependencies += compiler.find_library('nsl', required: true)
+    endif
+
+    # Check for pthread
+    if compiler.has_header('pthread.h')
+        add_project_arguments('-DHAVE_PTHREAD', language: 'c')
+        configuration.set('HAVE_PTHREAD', 1)
+    endif
+
+    # Check for ifaddrs
+    ifaddrs_prefix = '''
+    #include <sys/socket.h>
+    #define __USE_MISC 1   /**< Use MISC code */
+    #include <net/if.h>
+    #include <ifaddrs.h>
+    '''
+    if compiler.has_function('getifaddrs', prefix: ifaddrs_prefix)
+        add_project_arguments('-DHAVE_GETIFADDRS', language: 'c')
+        configuration.set('HAVE_GETIFADDRS', 1)
+    endif
+
+    # Check for dlfcn
+    if compiler.has_header('dlfcn.h')
+        # Linux, GNU and Solaris require linking
+        if system == 'linux' or system == 'gnu' or system == 'sunos'
+            dependencies += compiler.find_library('dl', required: true)
+        endif
+        add_project_link_arguments('-rdynamic', language: 'c')
+        add_project_arguments('-DHAVE_DLFCN', language: 'c')
+        configuration.set('HAVE_DLFCN', 1)
+    endif
+
+    # Check for epoll
+    if compiler.has_header('sys/epoll.h')
+        add_project_arguments('-DHAVE_EPOLL', language: 'c')
+        configuration.set('HAVE_EPOLL', 1)
+    endif
+
+    # Check for resolv
+    resolv_prefix = '''
+    #define _BSD_SOURCE 1
+    #define _DEFAULT_SOURCE 1
+    #include <sys/types.h>
+    #include <netinet/in.h>
+    #include <arpa/nameser.h>
+    #include <resolv.h>
+    '''
+    if compiler.has_type('struct __res_state', prefix: resolv_prefix)
+        # OSX/iOS and Solaris require linking
+        if system == 'darwin' or system == 'sunos'
+            dependencies += compiler.find_library('resolv', required: true)
+        endif
+        add_project_arguments('-DHAVE_RESOLV', language: 'c')
+        configuration.set('HAVE_RESOLV', 1)
+    endif
+
+    # Check for kqueue
+    kqueue_prefix = '''
+    #include <sys/types.h>
+    #include <sys/event.h>
+    #include <sys/time.h>
+    '''
+    if compiler.has_function('kqueue', prefix: kqueue_prefix)
+        add_project_arguments('-DHAVE_KQUEUE', language: 'c')
+        configuration.set('HAVE_KQUEUE', 1)
+    endif
+
+    # Check for arc4random
+    if compiler.has_function('arc4random', prefix: '#include <stdlib.h>')
+        add_project_arguments('-DHAVE_ARC4RANDOM', language: 'c')
+        configuration.set('HAVE_ARC4RANDOM', 1)
+    endif
+
+    # Features OSX/iOS is lacking
+    if not (system == 'darwin')
+        # OSX/iOS's poll() does not support devices
+        add_project_arguments(
+            '-DHAVE_POLL',
+            '-DHAVE_INET_PTON',
+            language: 'c')
+        configuration.set('HAVE_POLL', 1)
+        configuration.set('HAVE_INET_PTON', 1)
+    endif
+endif
+
+# Features: Routing for Linux and *BSD
+if system == 'linux' or (have_sysctl and have_net_route)
+    add_project_arguments('-DHAVE_ROUTE_LIST', language: 'c')
+    configuration.set('HAVE_ROUTE_LIST', 1)
+endif
+
+# Version
+version = meson.project_version()
+version_array = version.split('.')
+add_project_arguments(
+    '-DVERSION="@0@"'.format(version),
+    '-DVER_MAJOR=@0@'.format(version_array[0]),
+    '-DVER_MINOR=@0@'.format(version_array[1]),
+    '-DVER_PATCH=@0@'.format(version_array[2]),
+    language: 'c')
+configuration.set_quoted('VERSION', version)
+configuration.set('VER_MAJOR', version_array[0])
+configuration.set('VER_MINOR', version_array[1])
+configuration.set('VER_PATCH', version_array[2])
+
+# TODO: Define 'RELEASE' when using build type 'release'
+#       Also, compile_args += '-DMBUF_DEBUG=1' in that case
+# TODO: Check if we need to do anything for gcov
+# TODO: Check if we need to do anything for GNU profiling
+# TODO: Port packaging section
+# TODO: Port clang section
+# TODO: Port documentation section
+
+# Includes
+include_dir = include_directories('include')
+subdir('include')
+
+# Sources & Modules
+# TODO: Make which to build configurable
+subdir('src')
+
+# Build library
+re = library(meson.project_name(), sources,
+    dependencies: dependencies,
+    include_directories: include_dir,
+    install: true,
+    version: version)
+
+# Install headers
+install_headers(includes, subdir: 're')
+
+# Declare dependency
+re_dep = declare_dependency(
+    compile_args: compile_args,
+    include_directories: include_dir,
+    link_with: re)
+
+# Generate pkg-config file
+pkg = import('pkgconfig')
+description = '''Generic library for real-time communications with
+                 async IO support'''
+description = ' '.join(description.split())
+pkg.generate(re,
+    name: 'libre',
+    description: description,
+    url: 'http://www.creytiv.com/re.html',
+    extra_cflags: compile_args,  # https://github.com/creytiv/re/issues/167
+    subdirs: 're')
+
+# TODO: Ensure 'install' has the same result as when invoking 'make'
diff --git a/third_party/rawrtc/re/src/aes/meson.build b/third_party/rawrtc/re/src/aes/meson.build
new file mode 100644
index 0000000..6f869ca
--- /dev/null
+++ b/third_party/rawrtc/re/src/aes/meson.build
@@ -0,0 +1,6 @@
+# TODO: What about apple/aes.c?
+if openssl_dep.found()
+    sources += files('openssl/aes.c')
+else
+    sources += files('stub.c')
+endif
diff --git a/third_party/rawrtc/re/src/base64/meson.build b/third_party/rawrtc/re/src/base64/meson.build
new file mode 100644
index 0000000..5b6f4ff
--- /dev/null
+++ b/third_party/rawrtc/re/src/base64/meson.build
@@ -0,0 +1 @@
+sources += files('b64.c')
diff --git a/third_party/rawrtc/re/src/bfcp/meson.build b/third_party/rawrtc/re/src/bfcp/meson.build
new file mode 100644
index 0000000..8e333b0
--- /dev/null
+++ b/third_party/rawrtc/re/src/bfcp/meson.build
@@ -0,0 +1,7 @@
+sources += files([
+    'attr.c',
+    'conn.c',
+    'msg.c',
+    'reply.c',
+    'request.c',
+])
diff --git a/third_party/rawrtc/re/src/conf/meson.build b/third_party/rawrtc/re/src/conf/meson.build
new file mode 100644
index 0000000..ea1c1cfc
--- /dev/null
+++ b/third_party/rawrtc/re/src/conf/meson.build
@@ -0,0 +1 @@
+sources += files('conf.c')
diff --git a/third_party/rawrtc/re/src/crc32/meson.build b/third_party/rawrtc/re/src/crc32/meson.build
new file mode 100644
index 0000000..f073ff7
--- /dev/null
+++ b/third_party/rawrtc/re/src/crc32/meson.build
@@ -0,0 +1,3 @@
+if not zlib_dep.found()
+    sources += files('crc32.c')
+endif
diff --git a/third_party/rawrtc/re/src/dbg/meson.build b/third_party/rawrtc/re/src/dbg/meson.build
new file mode 100644
index 0000000..7c71321
--- /dev/null
+++ b/third_party/rawrtc/re/src/dbg/meson.build
@@ -0,0 +1 @@
+sources += files('dbg.c')
diff --git a/third_party/rawrtc/re/src/dns/meson.build b/third_party/rawrtc/re/src/dns/meson.build
new file mode 100644
index 0000000..cfd6aa0
--- /dev/null
+++ b/third_party/rawrtc/re/src/dns/meson.build
@@ -0,0 +1,23 @@
+sources += files([
+    'client.c',
+    'cstr.c',
+    'dname.c',
+    'hdr.c',
+    'ns.c',
+    'rr.c',
+    'rrlist.c',
+])
+
+if configuration.has('HAVE_RESOLV')
+    sources += files('res.c')
+endif
+
+if system == 'windows'
+    sources += files('win32/srv.c')
+elif system == 'darwin'
+    sources += files('darwin/srv.c')
+    dependencies += dependency(
+        'appleframeworks',
+        modules: ['SystemConfiguration', 'CoreFoundation'],
+        required: true)
+endif
diff --git a/third_party/rawrtc/re/src/fmt/meson.build b/third_party/rawrtc/re/src/fmt/meson.build
new file mode 100644
index 0000000..bcc4750
--- /dev/null
+++ b/third_party/rawrtc/re/src/fmt/meson.build
@@ -0,0 +1,12 @@
+sources += files([
+    'ch.c',
+    'hexdump.c',
+    'pl.c',
+    'print.c',
+    'prm.c',
+    'regex.c',
+    'str.c',
+    'str_error.c',
+    'time.c',
+    'unicode.c',
+])
diff --git a/third_party/rawrtc/re/src/fmt/print.c b/third_party/rawrtc/re/src/fmt/print.c
index 85e9ebf..c0247b4 100644
--- a/third_party/rawrtc/re/src/fmt/print.c
+++ b/third_party/rawrtc/re/src/fmt/print.c
@@ -303,6 +303,11 @@
 			}
 			break;
 
+		case 'h':
+			lenmod = LENMOD_NONE;
+			fm = true;
+			break;
+
 		case 'H':
 			ph     = va_arg(ap, re_printf_h *);
 			ph_arg = va_arg(ap, void *);
diff --git a/third_party/rawrtc/re/src/hash/func.c b/third_party/rawrtc/re/src/hash/func.c
index 3943c94..f379904 100644
--- a/third_party/rawrtc/re/src/hash/func.c
+++ b/third_party/rawrtc/re/src/hash/func.c
@@ -312,20 +312,19 @@
 			k += 12;
 		}
 
-		/* all the case statements fall through */
 		switch (length) {
 
-		case 12: c+=((uint32_t)k[11])<<24;
-		case 11: c+=((uint32_t)k[10])<<16;
-		case 10: c+=((uint32_t)k[9])<<8;
-		case 9 : c+=k[8];
-		case 8 : b+=((uint32_t)k[7])<<24;
-		case 7 : b+=((uint32_t)k[6])<<16;
-		case 6 : b+=((uint32_t)k[5])<<8;
-		case 5 : b+=k[4];
-		case 4 : a+=((uint32_t)k[3])<<24;
-		case 3 : a+=((uint32_t)k[2])<<16;
-		case 2 : a+=((uint32_t)k[1])<<8;
+		case 12: c+=((uint32_t)k[11])<<24; /* fall through */
+		case 11: c+=((uint32_t)k[10])<<16; /* fall through */
+		case 10: c+=((uint32_t)k[9])<<8;   /* fall through */
+		case 9 : c+=k[8];                  /* fall through */
+		case 8 : b+=((uint32_t)k[7])<<24;  /* fall through */
+		case 7 : b+=((uint32_t)k[6])<<16;  /* fall through */
+		case 6 : b+=((uint32_t)k[5])<<8;   /* fall through */
+		case 5 : b+=k[4];                  /* fall through */
+		case 4 : a+=((uint32_t)k[3])<<24;  /* fall through */
+		case 3 : a+=((uint32_t)k[2])<<16;  /* fall through */
+		case 2 : a+=((uint32_t)k[1])<<8;   /* fall through */
 		case 1 : a+=k[0];
 			break;
 		case 0 : return c;
diff --git a/third_party/rawrtc/re/src/hash/meson.build b/third_party/rawrtc/re/src/hash/meson.build
new file mode 100644
index 0000000..57d8c12
--- /dev/null
+++ b/third_party/rawrtc/re/src/hash/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+    'hash.c',
+    'func.c',
+])
diff --git a/third_party/rawrtc/re/src/hmac/meson.build b/third_party/rawrtc/re/src/hmac/meson.build
new file mode 100644
index 0000000..7a7293a
--- /dev/null
+++ b/third_party/rawrtc/re/src/hmac/meson.build
@@ -0,0 +1,8 @@
+sources += files('hmac_sha1.c')
+
+# TODO: What about apple/hmac.c?
+if openssl_dep.found()
+    sources += files('openssl/hmac.c')
+else
+    sources += files('hmac.c')
+endif
diff --git a/third_party/rawrtc/re/src/http/meson.build b/third_party/rawrtc/re/src/http/meson.build
new file mode 100644
index 0000000..3ee5939
--- /dev/null
+++ b/third_party/rawrtc/re/src/http/meson.build
@@ -0,0 +1,7 @@
+sources += files([
+    'auth.c',
+    'chunk.c',
+    'client.c',
+    'msg.c',
+    'server.c',
+])
diff --git a/third_party/rawrtc/re/src/httpauth/meson.build b/third_party/rawrtc/re/src/httpauth/meson.build
new file mode 100644
index 0000000..2ad5862
--- /dev/null
+++ b/third_party/rawrtc/re/src/httpauth/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+    'basic.c',
+    'digest.c',
+])
diff --git a/third_party/rawrtc/re/src/ice/meson.build b/third_party/rawrtc/re/src/ice/meson.build
new file mode 100644
index 0000000..be63f72
--- /dev/null
+++ b/third_party/rawrtc/re/src/ice/meson.build
@@ -0,0 +1,12 @@
+sources += files([
+    'cand.c',
+    'candpair.c',
+    'chklist.c',
+    'comp.c',
+    'connchk.c',
+    'icem.c',
+    'icesdp.c',
+    'icestr.c',
+    'stunsrv.c',
+    'util.c',
+])
diff --git a/third_party/rawrtc/re/src/jbuf/meson.build b/third_party/rawrtc/re/src/jbuf/meson.build
new file mode 100644
index 0000000..1b11bc8
--- /dev/null
+++ b/third_party/rawrtc/re/src/jbuf/meson.build
@@ -0,0 +1 @@
+sources += files('jbuf.c')
diff --git a/third_party/rawrtc/re/src/json/meson.build b/third_party/rawrtc/re/src/json/meson.build
new file mode 100644
index 0000000..f5461df
--- /dev/null
+++ b/third_party/rawrtc/re/src/json/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+    'decode.c',
+    'decode_odict.c',
+    'encode.c',
+])
diff --git a/third_party/rawrtc/re/src/list/meson.build b/third_party/rawrtc/re/src/list/meson.build
new file mode 100644
index 0000000..2027364
--- /dev/null
+++ b/third_party/rawrtc/re/src/list/meson.build
@@ -0,0 +1 @@
+sources += files('list.c')
diff --git a/third_party/rawrtc/re/src/lock/meson.build b/third_party/rawrtc/re/src/lock/meson.build
new file mode 100644
index 0000000..bd400bd
--- /dev/null
+++ b/third_party/rawrtc/re/src/lock/meson.build
@@ -0,0 +1,8 @@
+# TODO: What about lock.c?
+if configuration.has('HAVE_PTHREAD')
+    sources += files('rwlock.c')
+endif
+
+if system == 'windows'
+    sources += files('win32/lock.c')
+endif
diff --git a/third_party/rawrtc/re/src/main/main.c b/third_party/rawrtc/re/src/main/main.c
index b90c139..0243b4b 100644
--- a/third_party/rawrtc/re/src/main/main.c
+++ b/third_party/rawrtc/re/src/main/main.c
@@ -786,6 +786,8 @@
 				flags |= FD_WRITE;
 			if (re->events[i].events & (EPOLLERR|EPOLLHUP))
 				flags |= FD_EXCEPT;
+			if (re->events[i].events & EPOLLHUP)
+				flags |= FD_EXCEPT;
 
 			if (!flags) {
 				DEBUG_WARNING("epoll: no flags fd=%d\n", fd);
diff --git a/third_party/rawrtc/re/src/main/meson.build b/third_party/rawrtc/re/src/main/meson.build
new file mode 100644
index 0000000..f0a5b98
--- /dev/null
+++ b/third_party/rawrtc/re/src/main/meson.build
@@ -0,0 +1,13 @@
+sources += files([
+    'init.c',
+    'main.c',
+    'method.c',
+])
+
+if configuration.has('HAVE_EPOLL')
+    sources += files('epoll.c')
+endif
+
+if openssl_dep.found()
+    sources += files('openssl.c')
+endif
diff --git a/third_party/rawrtc/re/src/mbuf/meson.build b/third_party/rawrtc/re/src/mbuf/meson.build
new file mode 100644
index 0000000..eaec9d1
--- /dev/null
+++ b/third_party/rawrtc/re/src/mbuf/meson.build
@@ -0,0 +1 @@
+sources += files('mbuf.c')
diff --git a/third_party/rawrtc/re/src/md5/meson.build b/third_party/rawrtc/re/src/md5/meson.build
new file mode 100644
index 0000000..547a2fc
--- /dev/null
+++ b/third_party/rawrtc/re/src/md5/meson.build
@@ -0,0 +1,5 @@
+if not openssl_dep.found()
+    sources += files('md5.c')
+endif
+
+sources += files('wrap.c')
diff --git a/third_party/rawrtc/re/src/mem/meson.build b/third_party/rawrtc/re/src/mem/meson.build
new file mode 100644
index 0000000..3e15795
--- /dev/null
+++ b/third_party/rawrtc/re/src/mem/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+    'mem.c',
+    'secure.c',
+])
diff --git a/third_party/rawrtc/re/src/meson.build b/third_party/rawrtc/re/src/meson.build
new file mode 100644
index 0000000..aa5ff49
--- /dev/null
+++ b/third_party/rawrtc/re/src/meson.build
@@ -0,0 +1,50 @@
+sources = []
+
+# Modules
+subdir('aes')
+subdir('base64')
+subdir('bfcp')
+subdir('conf')
+subdir('crc32')
+subdir('dbg')
+subdir('dns')
+subdir('fmt')
+subdir('hash')
+subdir('hmac')
+subdir('http')
+subdir('httpauth')
+subdir('ice')
+subdir('jbuf')
+subdir('json')
+subdir('list')
+subdir('lock')
+subdir('main')
+subdir('mbuf')
+subdir('md5')
+subdir('mem')
+subdir('mod')
+subdir('mqueue')
+subdir('msg')
+subdir('natbd')
+subdir('net')
+subdir('odict')
+subdir('rtmp')
+subdir('rtp')
+subdir('sa')
+subdir('sdp')
+subdir('sha')
+subdir('sip')
+subdir('sipevent')
+subdir('sipreg')
+subdir('sipsess')
+subdir('srtp')
+subdir('stun')
+subdir('sys')
+subdir('tcp')
+subdir('telev')
+subdir('tls')
+subdir('tmr')
+subdir('turn')
+subdir('udp')
+subdir('uri')
+subdir('websock')
diff --git a/third_party/rawrtc/re/src/mod/meson.build b/third_party/rawrtc/re/src/mod/meson.build
new file mode 100644
index 0000000..eb9a306
--- /dev/null
+++ b/third_party/rawrtc/re/src/mod/meson.build
@@ -0,0 +1,9 @@
+sources += files('mod.c')
+
+if configuration.has('HAVE_DLFCN')
+    sources += files('dl.c')
+endif
+
+if system == 'windows'
+    sources += files('win32/dll.c')
+endif
diff --git a/third_party/rawrtc/re/src/mqueue/meson.build b/third_party/rawrtc/re/src/mqueue/meson.build
new file mode 100644
index 0000000..65d6d5b
--- /dev/null
+++ b/third_party/rawrtc/re/src/mqueue/meson.build
@@ -0,0 +1,5 @@
+sources += files('mqueue.c')
+
+if system == 'windows'
+    sources += files('win32/pipe.c')
+endif
diff --git a/third_party/rawrtc/re/src/msg/meson.build b/third_party/rawrtc/re/src/msg/meson.build
new file mode 100644
index 0000000..a70396e
--- /dev/null
+++ b/third_party/rawrtc/re/src/msg/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+    'ctype.c',
+    'param.c',
+])
diff --git a/third_party/rawrtc/re/src/natbd/meson.build b/third_party/rawrtc/re/src/natbd/meson.build
new file mode 100644
index 0000000..523d452
--- /dev/null
+++ b/third_party/rawrtc/re/src/natbd/meson.build
@@ -0,0 +1,8 @@
+sources += files([
+    'filtering.c',
+    'genalg.c',
+    'hairpinning.c',
+    'lifetime.c',
+    'mapping.c',
+    'natstr.c',
+])
diff --git a/third_party/rawrtc/re/src/net/meson.build b/third_party/rawrtc/re/src/net/meson.build
new file mode 100644
index 0000000..1408fed
--- /dev/null
+++ b/third_party/rawrtc/re/src/net/meson.build
@@ -0,0 +1,26 @@
+sources += files([
+    'if.c',
+    'net.c',
+    'netstr.c',
+    'rt.c',
+    'sock.c',
+    'sockopt.c',
+])
+
+if system == 'windows'
+    sources += files('win32/wif.c')
+else
+    sources += files('posix/pif.c')
+endif
+
+if configuration.has('HAVE_ROUTE_LIST')
+    if system == 'linux'
+        sources += files('linux/rt.c')
+    else
+        sources += files('bsd/brt.c')
+    endif
+endif
+
+if configuration.has('HAVE_GETIFADDRS')
+    sources += files('ifaddrs.c')
+endif
diff --git a/third_party/rawrtc/re/src/odict/meson.build b/third_party/rawrtc/re/src/odict/meson.build
new file mode 100644
index 0000000..8ac0cde
--- /dev/null
+++ b/third_party/rawrtc/re/src/odict/meson.build
@@ -0,0 +1,6 @@
+sources += files([
+    'entry.c',
+    'odict.c',
+    'type.c',
+    'get.c',
+])
diff --git a/third_party/rawrtc/re/src/rtmp/meson.build b/third_party/rawrtc/re/src/rtmp/meson.build
new file mode 100644
index 0000000..78a6214
--- /dev/null
+++ b/third_party/rawrtc/re/src/rtmp/meson.build
@@ -0,0 +1,12 @@
+sources += files([
+    'amf.c',
+    'amf_dec.c',
+    'amf_enc.c',
+    'chunk.c',
+    'conn.c',
+    'control.c',
+    'ctrans.c',
+    'dechunk.c',
+    'hdr.c',
+    'stream.c',
+])
diff --git a/third_party/rawrtc/re/src/rtp/meson.build b/third_party/rawrtc/re/src/rtp/meson.build
new file mode 100644
index 0000000..0fc1506
--- /dev/null
+++ b/third_party/rawrtc/re/src/rtp/meson.build
@@ -0,0 +1,12 @@
+sources += files([
+    'fb.c',
+    'member.c',
+    'ntp.c',
+    'pkt.c',
+    'rr.c',
+    'rtcp.c',
+    'rtp.c',
+    'sdes.c',
+    'sess.c',
+    'source.c',
+])
diff --git a/third_party/rawrtc/re/src/sa/meson.build b/third_party/rawrtc/re/src/sa/meson.build
new file mode 100644
index 0000000..92e8cbe
--- /dev/null
+++ b/third_party/rawrtc/re/src/sa/meson.build
@@ -0,0 +1,6 @@
+sources += files([
+    'ntop.c',
+    'printaddr.c',
+    'pton.c',
+    'sa.c',
+])
diff --git a/third_party/rawrtc/re/src/sdp/media.c b/third_party/rawrtc/re/src/sdp/media.c
index 07ce3a2..05e769c 100644
--- a/third_party/rawrtc/re/src/sdp/media.c
+++ b/third_party/rawrtc/re/src/sdp/media.c
@@ -446,6 +446,7 @@
 	if (!m || !laddr)
 		return;
 
+	m->flags |= MEDIA_LADDR_SET;
 	m->laddr = *laddr;
 }
 
@@ -513,6 +514,25 @@
 
 
 /**
+ * Set whether the local direction flag of an SDP media line should be excluded
+ * when encoding. Defaults to false.
+ *
+ * @param m       SDP Media line
+ * @param exclude Exclude direction flag
+ */
+void sdp_media_ldir_exclude(struct sdp_media *m, bool exclude)
+{
+	if (!m)
+		return;
+
+	if (exclude)
+		m->flags |= MEDIA_LDIR_EXCLUDE;
+	else
+		m->flags &= ~MEDIA_LDIR_EXCLUDE;
+}
+
+
+/**
  * Set a local attribute of an SDP Media line
  *
  * @param m       SDP Media line
diff --git a/third_party/rawrtc/re/src/sdp/meson.build b/third_party/rawrtc/re/src/sdp/meson.build
new file mode 100644
index 0000000..03b62dd
--- /dev/null
+++ b/third_party/rawrtc/re/src/sdp/meson.build
@@ -0,0 +1,9 @@
+sources += files([
+    'attr.c',
+    'format.c',
+    'media.c',
+    'msg.c',
+    'session.c',
+    'str.c',
+    'util.c',
+])
diff --git a/third_party/rawrtc/re/src/sdp/msg.c b/third_party/rawrtc/re/src/sdp/msg.c
index c1a8bbc..82b8905 100644
--- a/third_party/rawrtc/re/src/sdp/msg.c
+++ b/third_party/rawrtc/re/src/sdp/msg.c
@@ -398,7 +398,7 @@
 
 	err |= mbuf_write_str(mb, "\r\n");
 
-	if (sa_isset(&m->laddr, SA_ADDR)) {
+	if (m->flags & MEDIA_LADDR_SET) {
 		const int ipver = sa_af(&m->laddr) == AF_INET ? 4 : 6;
 		err |= mbuf_printf(mb, "c=IN IP%d %j\r\n", ipver, &m->laddr);
 	}
@@ -443,8 +443,10 @@
 		err |= mbuf_printf(mb, "a=rtcp:%u\r\n",
 				   sa_port(&m->laddr_rtcp));
 
-	err |= mbuf_printf(mb, "a=%s\r\n",
-			   sdp_dir_name(offer ? m->ldir : m->ldir & m->rdir));
+	if (!(m->flags & MEDIA_LDIR_EXCLUDE))
+		err |= mbuf_printf(mb, "a=%s\r\n",
+				   sdp_dir_name(offer ? m->ldir :
+						m->ldir & m->rdir));
 
 	for (le = m->lattrl.head; le; le = le->next)
 		err |= mbuf_printf(mb, "%H", sdp_attr_print, le->data);
diff --git a/third_party/rawrtc/re/src/sdp/sdp.h b/third_party/rawrtc/re/src/sdp/sdp.h
index f0588d1..604082a 100644
--- a/third_party/rawrtc/re/src/sdp/sdp.h
+++ b/third_party/rawrtc/re/src/sdp/sdp.h
@@ -10,6 +10,11 @@
 	RTP_DYNPT_END   = 127,
 };
 
+enum {
+	MEDIA_LADDR_SET = 1<<0,
+	MEDIA_LDIR_EXCLUDE = 1<<1,
+};
+
 
 struct sdp_session {
 	struct list lmedial;
@@ -27,6 +32,7 @@
 
 struct sdp_media {
 	struct le le;
+	uint8_t flags;
 	struct list lfmtl;
 	struct list rfmtl;
 	struct list lattrl;
diff --git a/third_party/rawrtc/re/src/sha/meson.build b/third_party/rawrtc/re/src/sha/meson.build
new file mode 100644
index 0000000..387b6b5
--- /dev/null
+++ b/third_party/rawrtc/re/src/sha/meson.build
@@ -0,0 +1,3 @@
+if not openssl_dep.found()
+    sources += files('sha1.c')
+endif
diff --git a/third_party/rawrtc/re/src/sip/meson.build b/third_party/rawrtc/re/src/sip/meson.build
new file mode 100644
index 0000000..f80c6e9
--- /dev/null
+++ b/third_party/rawrtc/re/src/sip/meson.build
@@ -0,0 +1,17 @@
+sources += files([
+    'addr.c',
+    'auth.c',
+    'contact.c',
+    'cseq.c',
+    'ctrans.c',
+    'dialog.c',
+    'keepalive.c',
+    'keepalive_udp.c',
+    'msg.c',
+    'reply.c',
+    'request.c',
+    'sip.c',
+    'strans.c',
+    'transp.c',
+    'via.c',
+])
diff --git a/third_party/rawrtc/re/src/sipevent/meson.build b/third_party/rawrtc/re/src/sipevent/meson.build
new file mode 100644
index 0000000..543aa84
--- /dev/null
+++ b/third_party/rawrtc/re/src/sipevent/meson.build
@@ -0,0 +1,6 @@
+sources += files([
+    'listen.c',
+    'msg.c',
+    'notify.c',
+    'subscribe.c',
+])
diff --git a/third_party/rawrtc/re/src/sipreg/meson.build b/third_party/rawrtc/re/src/sipreg/meson.build
new file mode 100644
index 0000000..0e7bb02
--- /dev/null
+++ b/third_party/rawrtc/re/src/sipreg/meson.build
@@ -0,0 +1 @@
+sources += files('reg.c')
diff --git a/third_party/rawrtc/re/src/sipsess/meson.build b/third_party/rawrtc/re/src/sipsess/meson.build
new file mode 100644
index 0000000..4731317
--- /dev/null
+++ b/third_party/rawrtc/re/src/sipsess/meson.build
@@ -0,0 +1,12 @@
+sources += files([
+    'sess.c',
+    'accept.c',
+    'ack.c',
+    'close.c',
+    'connect.c',
+    'info.c',
+    'listen.c',
+    'modify.c',
+    'reply.c',
+    'request.c',
+])
diff --git a/third_party/rawrtc/re/src/srtp/meson.build b/third_party/rawrtc/re/src/srtp/meson.build
new file mode 100644
index 0000000..eb56909
--- /dev/null
+++ b/third_party/rawrtc/re/src/srtp/meson.build
@@ -0,0 +1,7 @@
+sources += files([
+    'misc.c',
+    'replay.c',
+    'srtcp.c',
+    'srtp.c',
+    'stream.c',
+])
diff --git a/third_party/rawrtc/re/src/stun/meson.build b/third_party/rawrtc/re/src/stun/meson.build
new file mode 100644
index 0000000..5f0a018
--- /dev/null
+++ b/third_party/rawrtc/re/src/stun/meson.build
@@ -0,0 +1,14 @@
+sources += files([
+    'addr.c',
+    'attr.c',
+    'ctrans.c',
+    'dnsdisc.c',
+    'hdr.c',
+    'ind.c',
+    'keepalive.c',
+    'msg.c',
+    'rep.c',
+    'req.c',
+    'stun.c',
+    'stunstr.c',
+])
diff --git a/third_party/rawrtc/re/src/sys/meson.build b/third_party/rawrtc/re/src/sys/meson.build
new file mode 100644
index 0000000..7ffb335
--- /dev/null
+++ b/third_party/rawrtc/re/src/sys/meson.build
@@ -0,0 +1,8 @@
+sources += files([
+    'daemon.c',
+    'endian.c',
+    'fs.c',
+    'rand.c',
+    'sleep.c',
+    'sys.c',
+])
diff --git a/third_party/rawrtc/re/src/tcp/meson.build b/third_party/rawrtc/re/src/tcp/meson.build
new file mode 100644
index 0000000..344d6ee
--- /dev/null
+++ b/third_party/rawrtc/re/src/tcp/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+    'tcp.c',
+    'tcp_high.c',
+])
diff --git a/third_party/rawrtc/re/src/telev/meson.build b/third_party/rawrtc/re/src/telev/meson.build
new file mode 100644
index 0000000..e785374
--- /dev/null
+++ b/third_party/rawrtc/re/src/telev/meson.build
@@ -0,0 +1 @@
+sources += files('telev.c')
diff --git a/third_party/rawrtc/re/src/tls/meson.build b/third_party/rawrtc/re/src/tls/meson.build
new file mode 100644
index 0000000..2fc61e8
--- /dev/null
+++ b/third_party/rawrtc/re/src/tls/meson.build
@@ -0,0 +1,7 @@
+if openssl_dep.found()
+    sources += files([
+        'openssl/tls.c',
+        'openssl/tls_tcp.c',
+        'openssl/tls_udp.c',
+    ])
+endif
diff --git a/third_party/rawrtc/re/src/tls/openssl/tls.c b/third_party/rawrtc/re/src/tls/openssl/tls.c
index d84e65f..bc855b1 100644
--- a/third_party/rawrtc/re/src/tls/openssl/tls.c
+++ b/third_party/rawrtc/re/src/tls/openssl/tls.c
@@ -10,6 +10,8 @@
 #include <openssl/bn.h>
 #include <openssl/evp.h>
 #include <openssl/x509.h>
+#include <openssl/dh.h>
+#include <openssl/ec.h>
 #include <re_types.h>
 #include <re_fmt.h>
 #include <re_mem.h>
@@ -853,6 +855,158 @@
 }
 
 
+static int set_dh_params(struct tls *tls, DH *dh)
+{
+	int codes;
+	long r;
+#if OPENSSL_VERSION_NUMBER < 0x1000200fL
+	EC_KEY *ec_key;
+#endif
+
+	if (!DH_check(dh, &codes))
+		return ENOMEM;
+	if (codes) {
+#if defined(DH_CHECK_P_NOT_PRIME)
+		if (codes & DH_CHECK_P_NOT_PRIME)
+			DEBUG_WARNING("set_dh_params: p is not prime\n");
+#endif
+#if defined(DH_CHECK_P_NOT_SAFE_PRIME)
+		if (codes & DH_CHECK_P_NOT_SAFE_PRIME)
+			DEBUG_WARNING("set_dh_params: p is not safe prime\n");
+#endif
+#if defined(DH_UNABLE_TO_CHECK_GENERATOR)
+		if (codes & DH_UNABLE_TO_CHECK_GENERATOR)
+			DEBUG_WARNING("set_dh_params: generator g "
+			              "cannot be checked\n");
+#endif
+#if defined(DH_NOT_SUITABLE_GENERATOR)
+		if (codes & DH_NOT_SUITABLE_GENERATOR)
+			DEBUG_WARNING("set_dh_params: generator g "
+			              "is not suitable\n");
+#endif
+#if defined(DH_CHECK_Q_NOT_PRIME)
+		if (codes & DH_CHECK_Q_NOT_PRIME)
+			DEBUG_WARNING("set_dh_params: q is not prime\n");
+#endif
+#if defined(DH_CHECK_INVALID_Q_VALUE)
+		if (codes & DH_CHECK_INVALID_Q_VALUE)
+			DEBUG_WARNING("set_dh_params: q is invalid\n");
+#endif
+#if defined(DH_CHECK_INVALID_J_VALUE)
+		if (codes & DH_CHECK_INVALID_J_VALUE)
+			DEBUG_WARNING("set_dh_params: j is invalid\n");
+#endif
+		return EINVAL;
+	}
+
+	if (!SSL_CTX_set_tmp_dh(tls->ctx, dh)) {
+		DEBUG_WARNING("set_dh_params: set_tmp_dh failed\n");
+		return ENOMEM;
+	}
+
+#if OPENSSL_VERSION_NUMBER >= 0x1000200fL
+	r = SSL_CTX_set_ecdh_auto(tls->ctx, (long) 1);
+	if (!r) {
+		DEBUG_WARNING("set_dh_params: set_ecdh_auto failed\n");
+		return ENOMEM;
+	}
+#else
+	ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
+	if (!ec_key)
+		return ENOMEM;
+	r = SSL_CTX_set_tmp_ecdh(tls->ctx, ec_key);
+	EC_KEY_free(ec_key);
+	if (!r) {
+		DEBUG_WARNING("set_dh_params: set_tmp_ecdh failed\n");
+		return ENOMEM;
+	}
+#endif
+
+	return 0;
+}
+
+
+/**
+ * Set Diffie-Hellman parameters on a TLS context
+ *
+ * @param tls TLS Context
+ * @param pem Diffie-Hellman parameters in PEM format
+ * @param len Length of PEM string
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_dh_params_pem(struct tls *tls, const char *pem, size_t len)
+{
+	BIO *bio = NULL;
+	DH *dh = NULL;
+	int err = ENOMEM;
+
+	if (!tls || !pem || !len)
+		return EINVAL;
+
+	bio = BIO_new_mem_buf((char *)pem, (int)len);
+	if (!bio)
+		goto out;
+
+	dh = PEM_read_bio_DHparams(bio, NULL, 0, NULL);
+	if (!dh)
+		goto out;
+
+	err = set_dh_params(tls, dh);
+	if (err)
+		goto out;
+
+	err = 0;
+
+ out:
+	if (dh)
+		DH_free(dh);
+	if (bio)
+		BIO_free(bio);
+	if (err)
+		ERR_clear_error();
+
+	return err;
+}
+
+
+/**
+ * Set Diffie-Hellman parameters on a TLS context
+ *
+ * @param tls TLS Context
+ * @param der Diffie-Hellman parameters in DER format
+ * @param len Length of DER bytes
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int tls_set_dh_params_der(struct tls *tls, const uint8_t *der, size_t len)
+{
+	DH *dh = NULL;
+	int err = ENOMEM;
+
+	if (!tls || !der || !len)
+		return EINVAL;
+
+	dh = d2i_DHparams(NULL, &der, len);
+	if (!dh)
+		goto out;
+
+	err = set_dh_params(tls, dh);
+	if (err)
+		goto out;
+
+	err = 0;
+
+ out:
+	if (dh)
+		DH_free(dh);
+	if (err)
+		ERR_clear_error();
+
+	return err;
+}
+
+
 /**
  * Set the server name on a TLS Connection, using TLS SNI extension.
  *
diff --git a/third_party/rawrtc/re/src/tls/openssl/tls.h b/third_party/rawrtc/re/src/tls/openssl/tls.h
index 2c621d5..c0c4d51 100644
--- a/third_party/rawrtc/re/src/tls/openssl/tls.h
+++ b/third_party/rawrtc/re/src/tls/openssl/tls.h
@@ -20,9 +20,11 @@
 
 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && \
 	!defined(LIBRESSL_VERSION_NUMBER)
+#ifndef OPENSSL_IS_BORINGSSL
 #define SSL_state SSL_get_state
 #define SSL_ST_OK TLS_ST_OK
 #endif
+#endif
 
 
 struct tls {
diff --git a/third_party/rawrtc/re/src/tls/openssl/tls_udp.c b/third_party/rawrtc/re/src/tls/openssl/tls_udp.c
index 4ec81a3..669c6cf 100644
--- a/third_party/rawrtc/re/src/tls/openssl/tls_udp.c
+++ b/third_party/rawrtc/re/src/tls/openssl/tls_udp.c
@@ -27,8 +27,9 @@
 
 
 enum {
-	MTU_DEFAULT  = 1400,
-	MTU_FALLBACK = 548,
+	MTU_DEFAULT      = 1400,
+	MTU_FALLBACK     = 548,
+	HEADROOM_DEFAULT = 4,
 };
 
 
@@ -39,8 +40,11 @@
 	struct hash *ht;
 	struct mbuf *mb;
 	dtls_conn_h *connh;
+	dtls_send_h *sendh;
+	dtls_mtu_h *mtuh;
 	void *arg;
 	size_t mtu;
+	size_t headroom;
 };
 
 
@@ -109,18 +113,17 @@
 	struct tls_conn *tc = b->ptr;
 #endif
 	struct mbuf *mb;
-	enum {SPACE = 4};
 	int err;
 
-	mb = mbuf_alloc(SPACE + len);
+	mb = mbuf_alloc(tc->sock->headroom + len);
 	if (!mb)
 		return -1;
 
-	mb->pos = SPACE;
+	mb->pos = tc->sock->headroom;
 	(void)mbuf_write_mem(mb, (void *)buf, len);
-	mb->pos = SPACE;
+	mb->pos = tc->sock->headroom;
 
-	err = udp_send_helper(tc->sock->us, &tc->peer, mb, tc->sock->uh);
+	err = tc->sock->sendh(tc, &tc->peer, mb, tc->arg);
 
 	mem_deref(mb);
 
@@ -146,7 +149,17 @@
 
 #if defined (BIO_CTRL_DGRAM_QUERY_MTU)
 	case BIO_CTRL_DGRAM_QUERY_MTU:
-		return tc ? tc->sock->mtu : MTU_DEFAULT;
+		if (tc) {
+			if (tc->sock->mtuh) {
+				return tc->sock->mtuh(tc, tc->arg);
+			}
+			else {
+				return tc->sock->mtu;
+			}
+		}
+		else {
+			return MTU_DEFAULT;
+		}
 #endif
 
 #if defined (BIO_CTRL_DGRAM_GET_FALLBACK_MTU)
@@ -750,6 +763,13 @@
 }
 
 
+static int send_handler(struct tls_conn *tc, const struct sa *dst,
+		struct mbuf *mb, void *arg) {
+	(void)arg;
+	return udp_send_helper(tc->sock->us, dst, mb, tc->sock->uh);
+}
+
+
 static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg)
 {
 	struct dtls_sock *sock = arg;
@@ -783,6 +803,20 @@
 
 
 /**
+ * Feed data to a DTLS socket
+ *
+ * @param sock DTLS socket
+ * @param src  Source address
+ * @param mb   Buffer to receive
+ *
+ * @return whether the packet has been handled.
+ */
+bool dtls_receive(struct dtls_sock *sock, struct sa *src, struct mbuf *mb) {
+	return recv_handler(src, mb, sock);
+}
+
+
+/**
  * Create DTLS Socket
  *
  * @param sockp  Pointer to returned DTLS Socket
@@ -827,8 +861,57 @@
 	if (err)
 		goto out;
 
+	sock->mtu      = MTU_DEFAULT;
+	sock->headroom = HEADROOM_DEFAULT;
+	sock->connh    = connh;
+	sock->sendh    = send_handler;
+	sock->arg      = arg;
+
+ out:
+	if (err)
+		mem_deref(sock);
+	else
+		*sockp = sock;
+
+	return err;
+}
+
+
+/**
+ * Create a DTLS Socket without using an UDP socket underneath
+ *
+ * @param sockp  Pointer to returned DTLS Socket
+ * @param htsize Connection hash table size. Set to 0 if one DTLS session shall
+ * be used for all peers.
+ * @param connh  Connect handler
+ * @param sendh  Send handler
+ * @param mtuh   MTU handler
+ * @param arg    Handler argument
+ *
+ * @return 0 if success, otherwise errorcode
+ */
+int dtls_socketless(struct dtls_sock **sockp, uint32_t htsize,
+		dtls_conn_h *connh, dtls_send_h *sendh, dtls_mtu_h *mtuh,
+		void *arg)
+{
+	struct dtls_sock *sock;
+	int err;
+
+	if (!sockp || !sendh)
+		return EINVAL;
+
+	sock = mem_zalloc(sizeof(*sock), sock_destructor);
+	if (!sock)
+		return ENOMEM;
+
+	err = hash_alloc(&sock->ht, hash_valid_size(htsize));
+	if (err)
+		goto out;
+
 	sock->mtu   = MTU_DEFAULT;
 	sock->connh = connh;
+	sock->sendh = sendh;
+	sock->mtuh  = mtuh;
 	sock->arg   = arg;
 
  out:
@@ -869,6 +952,34 @@
 }
 
 
+/*
+ * Get headroom of a DTLS Socket
+ *
+ * @param sock DTLS Socket
+ *
+ * @return Headroom value.
+ */
+size_t dtls_headroom(struct dtls_sock *sock)
+{
+	return sock ? sock->headroom : 0;
+}
+
+
+/**
+ * Set headroom on a DTLS Socket
+ *
+ * @param sock     DTLS Socket
+ * @param headroom Headroom value
+ */
+void dtls_set_headroom(struct dtls_sock *sock, size_t headroom)
+{
+	if (!sock)
+		return;
+
+	sock->headroom = headroom;
+}
+
+
 void dtls_recv_packet(struct dtls_sock *sock, const struct sa *src,
 		      struct mbuf *mb)
 {
diff --git a/third_party/rawrtc/re/src/tmr/meson.build b/third_party/rawrtc/re/src/tmr/meson.build
new file mode 100644
index 0000000..9e9fb4a
--- /dev/null
+++ b/third_party/rawrtc/re/src/tmr/meson.build
@@ -0,0 +1 @@
+sources += files('tmr.c')
diff --git a/third_party/rawrtc/re/src/turn/meson.build b/third_party/rawrtc/re/src/turn/meson.build
new file mode 100644
index 0000000..4d0b6c5
--- /dev/null
+++ b/third_party/rawrtc/re/src/turn/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+    'chan.c',
+    'perm.c',
+    'turnc.c',
+])
diff --git a/third_party/rawrtc/re/src/udp/meson.build b/third_party/rawrtc/re/src/udp/meson.build
new file mode 100644
index 0000000..7dabb52
--- /dev/null
+++ b/third_party/rawrtc/re/src/udp/meson.build
@@ -0,0 +1,4 @@
+sources += files([
+    'udp.c',
+    'mcast.c',
+])
diff --git a/third_party/rawrtc/re/src/uri/meson.build b/third_party/rawrtc/re/src/uri/meson.build
new file mode 100644
index 0000000..b0b67fe
--- /dev/null
+++ b/third_party/rawrtc/re/src/uri/meson.build
@@ -0,0 +1,5 @@
+sources += files([
+    'uri.c',
+    'ucmp.c',
+    'uric.c',
+])
diff --git a/third_party/rawrtc/re/src/websock/meson.build b/third_party/rawrtc/re/src/websock/meson.build
new file mode 100644
index 0000000..ca078d3
--- /dev/null
+++ b/third_party/rawrtc/re/src/websock/meson.build
@@ -0,0 +1 @@
+sources += files('websock.c')
diff --git a/third_party/rawrtc/usrsctp/BUILD b/third_party/rawrtc/usrsctp/BUILD
index 7704bb9..654d29b 100644
--- a/third_party/rawrtc/usrsctp/BUILD
+++ b/third_party/rawrtc/usrsctp/BUILD
@@ -1,21 +1,63 @@
-# usrsctp is only actually being used for the CRC function, and getting
-# the entire library building was being obnoxious.
+load("@//tools/build_rules:select.bzl", "compiler_select")
+
 cc_library(
-    name = "usrsctp_crc32",
-    srcs = ["usrsctplib/netinet/sctp_crc32.c"],
+    name = "usrsctp",
+    srcs = [
+        "usrsctplib/netinet/sctp_asconf.c",
+        "usrsctplib/netinet/sctp_auth.c",
+        "usrsctplib/netinet/sctp_bsd_addr.c",
+        "usrsctplib/netinet/sctp_callout.c",
+        "usrsctplib/netinet/sctp_cc_functions.c",
+        "usrsctplib/netinet/sctp_crc32.c",
+        "usrsctplib/netinet/sctp_indata.c",
+        "usrsctplib/netinet/sctp_input.c",
+        "usrsctplib/netinet/sctp_output.c",
+        "usrsctplib/netinet/sctp_pcb.c",
+        "usrsctplib/netinet/sctp_peeloff.c",
+        "usrsctplib/netinet/sctp_sha1.c",
+        "usrsctplib/netinet/sctp_ss_functions.c",
+        "usrsctplib/netinet/sctp_sysctl.c",
+        "usrsctplib/netinet/sctp_timer.c",
+        "usrsctplib/netinet/sctp_userspace.c",
+        "usrsctplib/netinet/sctp_usrreq.c",
+        "usrsctplib/netinet/sctputil.c",
+        "usrsctplib/netinet6/sctp6_usrreq.c",
+        "usrsctplib/user_environment.c",
+        "usrsctplib/user_mbuf.c",
+        "usrsctplib/user_recv_thread.c",
+        "usrsctplib/user_socket.c",
+    ],
     hdrs = glob(["usrsctplib/**/*.h"]),
     copts = [
         "-Wno-cast-qual",
         "-Wno-cast-align",
         "-Wno-unused-parameter",
         "-Wno-incompatible-pointer-types-discards-qualifiers",
+        "-D__Userspace_os_Linux",
+        "-D__Userspace__",
+        "-D_GNU_SOURCE",
+        "-DSCTP_DEBUG",
+        "-DSCTP_SIMPLE_ALLOCATOR",
+        "-DINET",
+        "-DINET6",
+        "-DSCTP_PROCESS_LEVEL_LOCKS",
+        "-DHAVE_SYS_QUEUE_H",
+        "-DHAVE_STDATOMIC_H",
+        "-DHAVE_NETINET_IP_ICMP_H",
+        "-DHAVE_LINUX_RTNETLINK_H",
+        "-DHAVE_LINUX_IF_ADDR_H",
+        "-Wno-address-of-packed-member",
+    ] + compiler_select({
+        "clang": [],
+        "gcc": [
+            "-Wno-discarded-qualifiers",
+        ],
+    }),
+    includes = [
+        "usrsctplib/",
+        "usrsctplib/netinet",
+        "usrsctplib/netinet6",
     ],
-    defines = [
-        "__Userspace_os_Linux",
-        "__Userspace__",
-        "SCTP_SIMPLE_ALLOCATOR",
-    ],
-    includes = ["usrsctplib/"],
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
 )
diff --git a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_constants.h b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_constants.h
index e275dd9..57240cd 100755
--- a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_constants.h
+++ b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_constants.h
@@ -778,7 +778,7 @@
 #define SCTP_DEFAULT_SPLIT_POINT_MIN 2904
 
 /* Maximum length of diagnostic information in error causes */
-#define SCTP_DIAG_INFO_LEN 64
+#define SCTP_DIAG_INFO_LEN 128
 
 /* ABORT CODES and other tell-tale location
  * codes are generated by adding the below
diff --git a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_pcb.c b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_pcb.c
index ec3ff6d..ca84395 100755
--- a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_pcb.c
+++ b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctp_pcb.c
@@ -6845,7 +6845,7 @@
 	TAILQ_INIT(&SCTP_BASE_INFO(callqueue));
 #endif
 #if defined(__Userspace__)
-	mbuf_init(NULL);
+	usrsctpmbuf_init(NULL);
 	atomic_init();
 #if defined(THREAD_SUPPORT) && (defined(INET) || defined(INET6))
 	recv_thread_init();
diff --git a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.c b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.c
index 87e7774..5ac93d5 100755
--- a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.c
+++ b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.c
@@ -5013,7 +5013,7 @@
  */
 
 struct mbuf *
-sctp_generate_cause(uint16_t code, char *info)
+sctp_generate_cause(uint16_t code, const char *info)
 {
 	struct mbuf *m;
 	struct sctp_gen_error_cause *cause;
diff --git a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.h b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.h
index 7746d54..1babe19 100755
--- a/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.h
+++ b/third_party/rawrtc/usrsctp/usrsctplib/netinet/sctputil.h
@@ -281,7 +281,7 @@
 #endif
 );
 
-struct mbuf *sctp_generate_cause(uint16_t, char *);
+struct mbuf *sctp_generate_cause(uint16_t, const char *);
 struct mbuf *sctp_generate_no_user_data_cause(uint32_t);
 
 void sctp_bindx_add_address(struct socket *so, struct sctp_inpcb *inp,
diff --git a/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.c b/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.c
index 9a82746..232d74e 100755
--- a/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.c
+++ b/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.c
@@ -121,7 +121,7 @@
 	mbuf_mb_args.type = type;
 #endif
 	/* Mbuf master zone, zone_mbuf, has already been
-	 * created in mbuf_init() */
+	 * created in usrsctpmbuf_init() */
 	mret = SCTP_ZONE_GET(zone_mbuf, struct mbuf);
 #if defined(SCTP_SIMPLE_ALLOCATOR)
 	mb_ctor_mbuf(mret, &mbuf_mb_args, 0);
@@ -328,11 +328,11 @@
 /************ End functions to substitute umem_cache_alloc and umem_cache_free **************/
 
 /* __Userspace__
- * TODO: mbuf_init must be called in the initialization routines
+ * TODO: usrsctpmbuf_init must be called in the initialization routines
  * of userspace stack.
  */
 void
-mbuf_init(void *dummy)
+usrsctpmbuf_init(void *dummy)
 {
 
 	/*
diff --git a/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.h b/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.h
index 7893ea9..64a0ac3 100755
--- a/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.h
+++ b/third_party/rawrtc/usrsctp/usrsctplib/user_mbuf.h
@@ -58,7 +58,7 @@
 
 
 /* mbuf initialization function */
-void mbuf_init(void *);
+void usrsctpmbuf_init(void *);
 
 #define	M_MOVE_PKTHDR(to, from)	m_move_pkthdr((to), (from))
 #define	MGET(m, how, type)	((m) = m_get((how), (type)))
diff --git a/y2020/actors/autonomous_actor.cc b/y2020/actors/autonomous_actor.cc
index c78a33f..da64408 100644
--- a/y2020/actors/autonomous_actor.cc
+++ b/y2020/actors/autonomous_actor.cc
@@ -12,12 +12,12 @@
 #include "y2020/control_loops/drivetrain/drivetrain_base.h"
 
 DEFINE_bool(spline_auto, false, "If true, define a spline autonomous mode");
-DEFINE_bool(ignore_vision, true, "If true, ignore vision");
-DEFINE_bool(galactic_search, true,
+DEFINE_bool(ignore_vision, false, "If true, ignore vision");
+DEFINE_bool(galactic_search, false,
             "If true, do the galactic search autonomous");
 DEFINE_bool(bounce, false, "If true, run the AutoNav Bounce autonomous");
 DEFINE_bool(barrel, false, "If true, run the AutoNav Barrel autonomous");
-DEFINE_bool(slalom, false, "If true, run the AutoNav Slalom autonomous");
+DEFINE_bool(slalom, true, "If true, run the AutoNav Slalom autonomous");
 
 namespace y2020 {
 namespace actors {
diff --git a/y2020/actors/splines/autonav_barrel.json b/y2020/actors/splines/autonav_barrel.json
index 2649661..467420b 100644
--- a/y2020/actors/splines/autonav_barrel.json
+++ b/y2020/actors/splines/autonav_barrel.json
@@ -1 +1 @@
-{"spline_count": 4, "spline_x": [-3.439527264404297, -1.4521381072998047, -1.9621627899169916, 2.3140273590087896, -0.15155005187988285, -1.2099180084228516, -2.2682859649658202, -1.9194444671630853, 2.2364890686035164, 3.0541333465576175, 2.1377636993408204, 1.2213940521240234, -1.428989520263672, 1.0701125885009766, 3.340432150268555, 3.8391586853027344, 4.337885220336914, 3.065018728637696, 2.9864502685546874, 0.33045889892578123, -3.72018571472168], "spline_y": [0.0026085617065429684, -0.004877609252929687, -0.094301220703125, 0.4880167236328125, -1.993748071289062, -1.4883794998168944, -0.9830109283447268, 2.509491009521483, -2.4357923103332517, 0.09796495056152343, 1.1720165428161622, 2.246068135070801, 1.8604140586853029, -2.8892405044555662, -2.3678153549194336, -1.3137169052124023, -0.2596184555053709, 1.3271532943725592, -0.6553675552368162, -0.01534266815185547, -0.04329328765869141], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 7.499999999999998}, {"constraint_type": "LATERAL_ACCELERATION", "value": 8.499999999999995}, {"constraint_type": "VOLTAGE", "value": 11.999999999999993}]}
\ No newline at end of file
+{"spline_count": 4, "spline_x": [-3.439527264404297, -1.4521381072998047, -1.9621627899169916, 2.661975888061524, 0.06282058410644531, -1.1731946685791015, -2.4092099212646483, -2.2820851226806633, 2.9662840576171883, 4.035525769042969, 2.715648193359375, 1.3957706176757811, -2.313226245117187, 1.953077297973632, 4.110928143310546, 4.481738067626953, 4.85254799194336, 3.436316995239258, 1.6429130905151372, -1.0130782791137694, -4.985386976623535], "spline_y": [0.0026085617065429684, -0.004877609252929687, -0.094301220703125, 1.5597702003479004, -2.059494058227539, -1.5799808853149415, -1.1004677124023439, 3.477822891998291, -3.3979725334167483, 0.032006476593017474, 1.223588104248047, 2.4151697319030765, 1.3683539772033693, -3.5761337516784657, -2.7649265808105468, -1.4880899826049805, -0.2112533843994142, 1.531212641143799, -0.560890182495117, 0.07913470458984374, -0.04733257598876953], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 7.499999999999998}, {"constraint_type": "LATERAL_ACCELERATION", "value": 8.499999999999995}, {"constraint_type": "VOLTAGE", "value": 11.999999999999993}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/autonav_bounce_1.json b/y2020/actors/splines/autonav_bounce_1.json
index 233040c..53989c5 100644
--- a/y2020/actors/splines/autonav_bounce_1.json
+++ b/y2020/actors/splines/autonav_bounce_1.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [-3.854933303833008, -3.1908587219238282, -2.8742285649343122, -2.3949857243581434, -2.3116481497990145, -2.304892561983471], "spline_y": [0.04259449310302734, 0.04866970367431641, 0.12185038589540864, 0.3335849649896266, 0.9762657982284103, 1.5617851239669422], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 6.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 6.0}, {"constraint_type": "VOLTAGE", "value": 9.999999999999993}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [-3.6514654907226562, -2.933625585937499, -2.8310597534179687, -2.3949857243581434, -2.3116481497990145, -2.304892561983471], "spline_y": [-0.0021888198852539065, -0.012177745056152347, 0.24826333465576164, 0.3335849649896266, 0.9762657982284103, 1.5617851239669422], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 6.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 6.0}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/autonav_bounce_2.json b/y2020/actors/splines/autonav_bounce_2.json
index 365f721..de04a24 100644
--- a/y2020/actors/splines/autonav_bounce_2.json
+++ b/y2020/actors/splines/autonav_bounce_2.json
@@ -1 +1 @@
-{"spline_count": 2, "spline_x": [-2.300972622575431, -2.276803448275862, -2.2197493927001952, -2.0912932800292934, -1.8907473815917966, -1.6507643554687499, -1.4107813293457032, -1.1313611755371065, -0.17296153564453126, -0.002871917724609375, -0.0033345198006461863], "spline_y": [1.52789615352236, 0.9051463547279094, 0.5131617004394531, 0.6903293746948247, 0.10570575714111341, -0.4938134628295899, -1.0933326828002932, -1.7077475051879882, -2.25593497467041, -1.0112510650634765, 1.5787289769665949], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 6.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 5.0}, {"constraint_type": "VOLTAGE", "value": 9.999999999999993}]}
\ No newline at end of file
+{"spline_count": 2, "spline_x": [-2.300972622575431, -2.2976492889404296, -2.2449048339843745, -2.0912932800292934, -1.8907473815917966, -1.6507643554687499, -1.4107813293457032, -1.1313611755371067, 0.10809223937988281, -0.002871917724609375, -0.0033345198006461863], "spline_y": [1.52789615352236, 0.8841274291992188, 0.5057435485839843, 0.6903293746948247, 0.10570575714111341, -0.4938134628295899, -1.0933326828002932, -1.7077475051879885, -2.2848872772216797, -1.0112510650634765, 1.5787289769665949], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 6.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 6.0}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/autonav_bounce_3.json b/y2020/actors/splines/autonav_bounce_3.json
index 96a3e83..9ef5ae2 100644
--- a/y2020/actors/splines/autonav_bounce_3.json
+++ b/y2020/actors/splines/autonav_bounce_3.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [0.012595041322314211, 0.035615267944335935, -1.0700335235595704, 3.6841669830322266, 2.4339549224853516, 2.4026312530517577], "spline_y": [1.5869752066115703, -0.6927920150756836, -2.8280890045166016, -2.13918454284668, -0.8389284439086915, 1.5275657707214354], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 5.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 5.5}, {"constraint_type": "VOLTAGE", "value": 9.999999999999993}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [0.012595041322314211, 0.035615267944335935, -1.0700335235595704, 3.6841669830322266, 2.4339549224853516, 2.4026312530517577], "spline_y": [1.5869752066115703, -0.6927920150756836, -2.8280890045166016, -2.13918454284668, -0.8389284439086915, 1.5275657707214354], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 5.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 5.5}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/autonav_bounce_4.json b/y2020/actors/splines/autonav_bounce_4.json
index 042b800..ee65a95 100644
--- a/y2020/actors/splines/autonav_bounce_4.json
+++ b/y2020/actors/splines/autonav_bounce_4.json
@@ -1 +1 @@
-{"spline_count": 1, "spline_x": [2.420588296508789, 2.4232765045166014, 2.5394848846055655, 2.5435332334653036, 2.696887710571289, 3.661613708496094], "spline_y": [1.5176544570922852, 0.9584327774047853, 0.7181724172979964, 0.2998157461902411, 0.1163405731201172, 0.12068274993896484], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 5.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 5.5}, {"constraint_type": "VOLTAGE", "value": 9.999999999999993}]}
\ No newline at end of file
+{"spline_count": 1, "spline_x": [2.420588296508789, 2.4232765045166014, 2.411430715942383, 3.181959380926238, 3.284892517089844, 3.989023956298828], "spline_y": [1.5176544570922852, 0.9584327774047853, 0.49865735321044924, 0.1483674322864324, -0.07779583282470703, -0.5024309600830078], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 5.5}, {"constraint_type": "LATERAL_ACCELERATION", "value": 5.5}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/autonav_slalom.json b/y2020/actors/splines/autonav_slalom.json
index 6936d80..664b929 100644
--- a/y2020/actors/splines/autonav_slalom.json
+++ b/y2020/actors/splines/autonav_slalom.json
@@ -1 +1 @@
-{"spline_count": 8, "spline_x": [-3.4749518463134765, -3.1365934295654294, -2.9590075597612686, -2.760017578125, -2.5225692810058593, -2.273429890106328, -2.0242904992067965, -1.7634600145268742, -1.4530334014892579, -1.1285066986083985, -0.68847890625, -0.24845111389160157, 0.30707776794433594, 1.871516759595437, 2.184855541688278, 2.417896609990923, 2.650937678293568, 2.8036810328060167, 3.5928316545035397, 3.8742948612577215, 3.853217129516602, 3.832139397775482, 3.5085207275390626, 2.5905665683350616, 2.4835600727761933, 2.3169760114107887, 2.150391950045384, 1.9242303228734432, 1.4789411224365234, 1.0076788024902341, 0.21033948669433594, -0.5869998291015622, -1.7104161407470695, -1.6109939147949213, -2.028241729609699, -2.2553543746758296, -2.4824670197419603, -2.519444495059444, -3.4609283874195165, -3.690070111083984, -4.450609153747559], "spline_y": [-1.6666796630859375, -1.5026007247924806, -1.3657751963192004, -1.3422337692260742, -1.0754058700561524, -0.758464560101141, -0.4415232501461297, -0.07446852940602877, 0.20931454925537119, 0.2820978973388672, 0.3545516143798828, 0.4270053314208984, 0.49912941741943356, 0.32144404823651485, -0.1969548795707987, -0.7699543538073782, -1.3429538280439577, -1.970553848709803, -1.7554159149993005, -1.2416998867240683, -0.7131605392456054, -0.18462119176714253, 0.3587414749145508, 0.459862695717713, -0.20117116182572614, -0.7732763550311204, -1.3453815482365146, -1.8285580771038639, -1.8799707138061523, -2.1832105377197277, -2.2168379425048825, -2.2504653472900373, -2.0144803329467713, -1.7455779479980467, -1.3180884153753891, -0.7902526419865145, -0.2624168685976398, 0.365765145557452, 1.047112901958292, 1.014591268157959, 1.6061016746520995], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 6.999999999999993}, {"constraint_type": "LATERAL_ACCELERATION", "value": 6.999999999999993}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
+{"spline_count": 8, "spline_x": [-3.635843479156494, -3.2806723095703125, -3.1868360023498545, -2.858219025878906, -2.5751525830078124, -2.3197756127929687, -2.064398642578125, -1.8367111450195317, -1.7695840795898432, -0.7814239306640623, -0.18952982666015625, 0.40236427734374985, 0.5979923364257812, 1.9385774853515625, 2.5234608544921873, 2.8941172998046873, 3.2647737451171874, 3.4212032666015624, 4.386921788780882, 4.586409534109282, 4.4665292285156255, 4.346648922921969, 3.907400566406256, 2.758564643554687, 2.5834497895730655, 2.3319990527343752, 2.080548315895685, 1.7527616961999257, 1.7609669311523446, 0.8905572692871097, 0.13155197021484377, -0.6274533288574221, -1.2750542651367192, -1.6109939147949213, -2.028241729609699, -2.2553543746758296, -2.4824670197419603, -2.519444495059444, -3.4609283874195165, -3.690070111083984, -4.450609153747559], "spline_y": [-1.7265489738464355, -1.5607546752929686, -1.4611474120330816, -1.326837150878906, -1.023809600830078, -0.7159144409179687, -0.4080192810058594, -0.09525651123046863, -0.04151700073242237, 0.44292831298828117, 0.5805910729980469, 0.7182538330078126, 0.5091340393066406, 1.3885468725585939, 0.09532766967773454, -0.7626050793457031, -1.6205378283691407, -2.0431841235351564, -1.6678404465544734, -1.0808934231010208, -0.5693647814941407, -0.05783613988726066, 0.3782741198730468, 0.4133138415527349, -0.31849562837846057, -0.8944612170410157, -1.4704268057035708, -1.8905485130974853, -1.8685408300781232, -2.0658371594238267, -2.0819412927246095, -2.0980454260253922, -1.932957363281254, -1.7455779479980467, -1.3180884153753891, -0.7902526419865145, -0.2624168685976398, 0.365765145557452, 1.047112901958292, 1.014591268157959, 1.6061016746520995], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 6.999999999999993}, {"constraint_type": "LATERAL_ACCELERATION", "value": 6.999999999999993}, {"constraint_type": "VOLTAGE", "value": 12.0}]}
\ No newline at end of file
diff --git a/y2020/vision/tools/python_code/README.md b/y2020/vision/tools/python_code/README.md
new file mode 100644
index 0000000..09e0b01
--- /dev/null
+++ b/y2020/vision/tools/python_code/README.md
@@ -0,0 +1,42 @@
+# Notes on running the SIFT matching code
+
+Here are some of the key modules:
+
+## Camera Definition
+  * Class defined in camera_definition.py
+    * Includes:
+      * Camera Intrinsic
+      * Camera Extrinsic
+      * Team number & node name
+    * We create a separate camera definition for each camera (on each robot)
+    * For now, they're all identical, but we need a mechanism for having each be unique, esp. for the extrinsics
+
+## Target Definition
+  * Class defined in target_definition.py
+    * For each target (e.g., Power Port Red, Power Port Blue, Loading Station Red, etc), we define both an ideal target (e.g., the CAD / graphic version, with known 3D point locations) and a training target (an image from our cameras in the environment we're expecting).
+      * KEY IDEA: We use the ideal target info (including 3D locations of points) to determine the 3D locations of the keypoints in the training target
+
+    * The following are parts of the TargetData class:
+      * image
+      * List of polygons which define flat surfaces on the ideal target
+        * List of corresponding 3D points for the polygons, so we can automatically extract 3D locations of the keypoints (via interpolation)
+      * Keypoint List of extracted keypoints from the target image (ideal & training)
+        * Descriptor list (which is used to match features)
+      * (Ideal) Target point position and rotation
+      * (Ideal) Target point 2D-- location of a special point in the target used to help visualize accuracy of matches
+      * Target point radius (used to help size things to check on distance)
+      * Keypoint List 3d of the 3D locations of each of the keypoints
+        * These are computed based on the 3D polygon definition
+        * These are 3D with respect to our global origin
+        * This is used to determine our pose as we do th ematching
+
+## Header definition of target data
+
+  * Creating the config file
+    * The output of all this is a sift_training_data.h file that gets used in other parts of the code (e.g., //y2020/vision:camera_reader)
+    * By building "run_load_training_data", you should get this file generated
+
+## Testing / debugging
+  * There's a couple tests that can be run through bazel
+  * image_match_test.py is a good way to see what's being matched
+    * You have to specify and select the correct query_image to test on
diff --git a/y2020/vision/tools/python_code/image_match_test.py b/y2020/vision/tools/python_code/image_match_test.py
index 8d16104..b085166 100644
--- a/y2020/vision/tools/python_code/image_match_test.py
+++ b/y2020/vision/tools/python_code/image_match_test.py
@@ -32,7 +32,7 @@
     'test_images/train_loading_bay_blue.png',  #9
     'test_images/train_loading_bay_red.png',  #10
     'test_images/pi-7971-3_test_image.png',  #11
-    'sample_images/capture-2020-02-13-16-40-07.png',
+    'test_images/test_taped_dusk-2021-04-03-19-30-00.png',  #12
 ]
 
 training_image_index = 0
@@ -126,6 +126,7 @@
     # If we're querying static images, show full results
     if (query_image_index is not -1):
         print("Showing results for static query image")
+        cv2.imshow('test', query_images[0]), cv2.waitKey(0)
         homography_list, matches_mask_list = tam.show_results(
             training_images, train_keypoint_lists, query_images,
             query_keypoint_lists, target_pt_list, good_matches_list)
@@ -162,18 +163,24 @@
         #   -- This causes a lot of mapping around of points, but overall OK
 
         src_pts_3d = []
+        query_image_matches = query_images[0].copy()
         for m in good_matches:
             src_pts_3d.append(target_list[i].keypoint_list_3d[m.trainIdx])
             pt = query_keypoint_lists[0][m.queryIdx].pt
-            query_images[0] = cv2.circle(
-                query_images[0], (int(pt[0]), int(pt[1])), 5, (0, 255, 0), 3)
+            query_image_matches = cv2.circle(query_image_matches,
+                                             (int(pt[0]), int(pt[1])), 5,
+                                             (0, 255, 0), 3)
 
-        cv2.imshow('DEBUG', query_images[0]), cv2.waitKey(0)
+        cv2.imshow('DEBUG matches', query_image_matches), cv2.waitKey(0)
         # Reshape 3d point list to work with computations to follow
         src_pts_3d_array = np.asarray(np.float32(src_pts_3d)).reshape(-1, 3, 1)
 
         # Load from camera parameters
         cam_mat = camera_params.camera_int.camera_matrix
+        cam_mat[0][0] = 390
+        cam_mat[1][1] = 390
+        # TODO<Jim>: Would be good to read this from a config file
+        print("FIXING CAM MAT to pi camera: ", cam_mat)
         dist_coeffs = camera_params.camera_int.dist_coeffs
 
         # Create list of matching query point locations
@@ -227,9 +234,17 @@
         img_ret = field_display.plot_line_on_field(img_ret, (255, 255, 0),
                                                    T_w_ci_estj, T_w_target_pt)
         # THESE ARE OUR ESTIMATES OF HEADING, DISTANCE, AND SKEW TO TARGET
-        log_file.write('%lf, %lf, %lf\n' %
-                       (heading_est, distance_to_target_ground,
-                        angle_to_target_robot))
+        log_file.write(
+            '%lf, %lf, %lf\n' %
+            (heading_est, distance_to_target_ground, angle_to_target_robot))
+        print("Estimates: \n  Heading on field: ",
+              "{:.2f}".format(heading_est), " radians; ",
+              "{:.2f}".format(heading_est / math.pi * 180.0),
+              " degrees\n  Distance to target: ",
+              "{:.2f}".format(distance_to_target_ground),
+              "m\n  Angle to target: ", "{:.2f}".format(angle_to_target_robot),
+              " radians; ", "{:.2f}".format(angle_to_target_robot / math.pi *
+                                            180.0), " degrees\n")
 
         # A bunch of code to visualize things...
         #
@@ -241,9 +256,9 @@
             target_pt_list[i].reshape(-1, 1, 2),
             homography_list[i]).astype(int)
         # Ballpark the size of the circle so it looks right on image
-        radius = int(
-            32 * abs(homography_list[i][0][0] + homography_list[i][1][1]) /
-            2)  # Average of scale factors
+        radius = int(12 *
+                     abs(homography_list[i][0][0] + homography_list[i][1][1]) /
+                     2)  # Average of scale factors
         query_image_copy = cv2.circle(query_image_copy,
                                       (target_point_2d_trans.flatten()[0],
                                        target_point_2d_trans.flatten()[1]),
diff --git a/y2020/vision/tools/python_code/image_stream.py b/y2020/vision/tools/python_code/image_stream.py
deleted file mode 100644
index 2cd5ac7..0000000
--- a/y2020/vision/tools/python_code/image_stream.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import cv2
-import datetime
-# Open the device at the ID X for /dev/videoX
-CAMERA_INDEX = 0
-cap = cv2.VideoCapture(CAMERA_INDEX)
-
-#Check whether user selected camera is opened successfully.
-if not (cap.isOpened()):
-    print("Could not open video device /dev/video%d" % CAMERA_INDEX)
-    quit()
-
-while True:
-    # Capture frame-by-frame
-    ret, frame = cap.read()
-
-    exp = cap.get(cv2.CAP_PROP_EXPOSURE)
-    #print("Exposure:", exp)
-    # Display the resulting frame
-    cv2.imshow('preview', frame)
-
-    #Waits for a user input to capture image or quit the application
-    keystroke = cv2.waitKey(1)
-
-    if keystroke & 0xFF == ord('q'):
-        break
-    elif keystroke & 0xFF == ord('c'):
-        image_name = datetime.datetime.today().strftime(
-            "capture-%b-%d-%Y-%H-%M-%S.png")
-        print("Capturing image as %s" % image_name)
-        cv2.imwrite(image_name, frame)
-
-# When everything's done, release the capture
-cap.release()
-cv2.destroyAllWindows()
diff --git a/y2020/vision/tools/python_code/target_definition.py b/y2020/vision/tools/python_code/target_definition.py
index 6080ac2..be7e223 100644
--- a/y2020/vision/tools/python_code/target_definition.py
+++ b/y2020/vision/tools/python_code/target_definition.py
@@ -14,8 +14,8 @@
 global VISUALIZE_KEYPOINTS
 VISUALIZE_KEYPOINTS = False
 
-# For now, just have a 32 pixel radius, based on original training image
-target_radius_default = 32.
+# For now, just have a 12 pixel radius, based on captured (taped) training image
+target_radius_default = 12.
 
 
 class TargetData:
@@ -55,8 +55,8 @@
 
     def project_keypoint_to_3d_by_polygon(self, keypoint_list):
         # Create dummy array of correct size that we can put values in
-        point_list_3d = np.asarray(
-            [(0., 0., 0.) for kp in keypoint_list]).reshape(-1, 3)
+        point_list_3d = np.asarray([(0., 0., 0.)
+                                    for kp in keypoint_list]).reshape(-1, 3)
         # Iterate through our polygons
         for poly_ind in range(len(self.polygon_list)):
             # Filter and project points for each polygon in the list
@@ -64,9 +64,10 @@
                 keypoint_list, None, [self.polygon_list[poly_ind]])
             glog.info("Filtering kept %d of %d features" %
                       (len(keep_list), len(keypoint_list)))
-            filtered_point_array = np.asarray(
-                [(keypoint.pt[0], keypoint.pt[1])
-                 for keypoint in filtered_keypoints]).reshape(-1, 2)
+            filtered_point_array = np.asarray([
+                (keypoint.pt[0], keypoint.pt[1])
+                for keypoint in filtered_keypoints
+            ]).reshape(-1, 2)
             filtered_point_list_3d = dtd.compute_3d_points(
                 filtered_point_array, self.reprojection_map_list[poly_ind])
             for i in range(len(keep_list)):
@@ -100,13 +101,16 @@
     loading_bay_edge_y = 0.784
     loading_bay_width = 1.524
     loading_bay_height = 0.933
-    wing_angle = 20. * math.pi / 180.
+    # Wing angle on actual field target
+    # wing_angle = 20. * math.pi / 180.
+    ### NOTE: Setting wing angle to zero to match current FRC971 target
+    wing_angle = 0
 
     # Pick the target center location at halfway between top and bottom of the top panel
-    power_port_target_height = (
-        power_port_total_height + power_port_bottom_wing_height) / 2.
+    power_port_target_height = (power_port_total_height +
+                                power_port_bottom_wing_height) / 2.
 
-    ### Cafeteria target definition
+    ### Taped up FRC target definition
     inch_to_meter = 0.0254
     c_power_port_total_height = (79.5 + 39.5) * inch_to_meter
     c_power_port_edge_y = 1.089
@@ -116,65 +120,81 @@
     c_power_port_white_marker_z = (79.5 - 19.5) * inch_to_meter
 
     # Pick the target center location at halfway between top and bottom of the top panel
-    c_power_port_target_height = (
-        power_port_total_height + power_port_bottom_wing_height) / 2.
+    c_power_port_target_height = (power_port_total_height +
+                                  power_port_bottom_wing_height) / 2.
 
     ###
-    ### Cafe power port
+    ### Taped power port
     ###
 
-    # Create the reference "ideal" image
-    ideal_power_port_cafe = TargetData(
-        'test_images/train_cafeteria-2020-02-13-16-27-25.png')
+    # Create the reference "ideal" image.
+    # NOTE: Since we don't have an "ideal" (e.g., graphic) version, we're
+    # just using the same image as the training image.
+    ideal_power_port_taped = TargetData(
+        'test_images/train_power_port_taped-2020-08-20-13-27-50.png')
 
     # Start at lower left corner, and work around clockwise
     # These are taken by manually finding the points in gimp for this image
-    power_port_cafe_main_panel_polygon_points_2d = [(271, 456), (278, 394),
-                                                    (135, 382), (286, 294),
-                                                    (389, 311), (397,
-                                                                 403), (401,
-                                                                        458)]
+    power_port_taped_main_panel_polygon_points_2d = [(181, 423), (186, 192),
+                                                     (45, 191), (188, 93),
+                                                     (310, 99), (314, 196),
+                                                     (321, 414)]
 
     # These are "virtual" 3D points based on the expected geometry
-    power_port_cafe_main_panel_polygon_points_3d = [
+    power_port_taped_main_panel_polygon_points_3d = [
+        (field_length / 2., -c_power_port_edge_y, 0.),
         (field_length / 2., -c_power_port_edge_y,
-         c_power_port_white_marker_z), (field_length / 2.,
-                                        -c_power_port_edge_y,
-                                        c_power_port_bottom_wing_height),
+         c_power_port_bottom_wing_height),
         (field_length / 2., -c_power_port_edge_y + c_power_port_wing_width,
-         c_power_port_bottom_wing_height), (field_length / 2.,
-                                            -c_power_port_edge_y,
-                                            c_power_port_total_height),
+         c_power_port_bottom_wing_height),
+        (field_length / 2., -c_power_port_edge_y, c_power_port_total_height),
         (field_length / 2., -c_power_port_edge_y - c_power_port_width,
          c_power_port_total_height),
         (field_length / 2., -c_power_port_edge_y - c_power_port_width,
          c_power_port_bottom_wing_height),
-        (field_length / 2., -c_power_port_edge_y - c_power_port_width,
-         c_power_port_white_marker_z)
+        (field_length / 2., -c_power_port_edge_y - c_power_port_width, 0.)
     ]
 
-    # Populate the cafe power port
-    ideal_power_port_cafe.polygon_list.append(
-        power_port_cafe_main_panel_polygon_points_2d)
-    ideal_power_port_cafe.polygon_list_3d.append(
-        power_port_cafe_main_panel_polygon_points_3d)
+    power_port_taped_wing_panel_polygon_points_2d = [(312, 99), (438, 191),
+                                                     (315, 195)]
+
+    # These are "virtual" 3D points based on the expected geometry
+    power_port_taped_wing_panel_polygon_points_3d = [
+        (field_length / 2., -power_port_edge_y - power_port_width,
+         power_port_total_height),
+        (field_length / 2. - power_port_wing_width * math.sin(wing_angle),
+         -power_port_edge_y - power_port_width -
+         power_port_wing_width * math.cos(wing_angle),
+         power_port_bottom_wing_height),
+        (field_length / 2., -power_port_edge_y - power_port_width,
+         power_port_bottom_wing_height)
+    ]
+
+    # Populate the taped power port
+    ideal_power_port_taped.polygon_list.append(
+        power_port_taped_main_panel_polygon_points_2d)
+    ideal_power_port_taped.polygon_list_3d.append(
+        power_port_taped_main_panel_polygon_points_3d)
+    # Including the wing panel
+    ideal_power_port_taped.polygon_list.append(
+        power_port_taped_wing_panel_polygon_points_2d)
+    ideal_power_port_taped.polygon_list_3d.append(
+        power_port_taped_wing_panel_polygon_points_3d)
 
     # Location of target.  Rotation is pointing in -x direction
-    ideal_power_port_cafe.target_rotation = np.identity(3, np.double)
-    ideal_power_port_cafe.target_position = np.array([
+    ideal_power_port_taped.target_rotation = np.identity(3, np.double)
+    ideal_power_port_taped.target_position = np.array([
         field_length / 2., -c_power_port_edge_y - c_power_port_width / 2.,
         c_power_port_target_height
     ])
-    ideal_power_port_cafe.target_point_2d = np.float32([[340, 350]]).reshape(
-        -1, 1, 2)  # train_cafeteria-2020-02-13-16-27-25.png
+    ideal_power_port_taped.target_point_2d = np.float32([[250, 146]]).reshape(
+        -1, 1, 2)  # train_power_port_taped-2020-08-20-13-27-50.png
 
-    ideal_target_list.append(ideal_power_port_cafe)
-    training_target_power_port_cafe = TargetData(
-        'test_images/train_cafeteria-2020-02-13-16-27-25.png')
-    training_target_power_port_cafe.target_rotation = ideal_power_port_cafe.target_rotation
-    training_target_power_port_cafe.target_position = ideal_power_port_cafe.target_position
-    training_target_power_port_cafe.target_radius = target_radius_default
-    training_target_list.append(training_target_power_port_cafe)
+    training_target_power_port_taped = TargetData(
+        'test_images/train_power_port_taped-2020-08-20-13-27-50.png')
+    training_target_power_port_taped.target_rotation = ideal_power_port_taped.target_rotation
+    training_target_power_port_taped.target_position = ideal_power_port_taped.target_position
+    training_target_power_port_taped.target_radius = target_radius_default
 
     ###
     ### Red Power Port
@@ -186,23 +206,21 @@
     # Start at lower left corner, and work around clockwise
     # These are taken by manually finding the points in gimp for this image
     power_port_red_main_panel_polygon_points_2d = [(451, 679), (451, 304),
-                                                   (100, 302), (451, 74), (689,
-                                                                           74),
-                                                   (689, 302), (689, 679)]
+                                                   (100, 302), (451, 74),
+                                                   (689, 74), (689, 302),
+                                                   (689, 679)]
 
     # These are "virtual" 3D points based on the expected geometry
     power_port_red_main_panel_polygon_points_3d = [
-        (-field_length / 2., power_port_edge_y,
-         0.), (-field_length / 2., power_port_edge_y,
-               power_port_bottom_wing_height),
+        (-field_length / 2., power_port_edge_y, 0.),
+        (-field_length / 2., power_port_edge_y, power_port_bottom_wing_height),
         (-field_length / 2., power_port_edge_y - power_port_wing_width,
-         power_port_bottom_wing_height), (-field_length / 2.,
-                                          power_port_edge_y,
-                                          power_port_total_height),
+         power_port_bottom_wing_height),
+        (-field_length / 2., power_port_edge_y, power_port_total_height),
         (-field_length / 2., power_port_edge_y + power_port_width,
-         power_port_total_height), (-field_length / 2.,
-                                    power_port_edge_y + power_port_width,
-                                    power_port_bottom_wing_height),
+         power_port_total_height),
+        (-field_length / 2., power_port_edge_y + power_port_width,
+         power_port_bottom_wing_height),
         (-field_length / 2., power_port_edge_y + power_port_width, 0.)
     ]
 
@@ -215,9 +233,9 @@
         (-field_length / 2. + power_port_wing_width * math.sin(wing_angle),
          power_port_edge_y + power_port_width +
          power_port_wing_width * math.cos(wing_angle),
-         power_port_bottom_wing_height), (-field_length / 2.,
-                                          power_port_edge_y + power_port_width,
-                                          power_port_bottom_wing_height)
+         power_port_bottom_wing_height),
+        (-field_length / 2., power_port_edge_y + power_port_width,
+         power_port_bottom_wing_height)
     ]
 
     # Populate the red power port
@@ -246,8 +264,6 @@
         -1, 1, 2)  # ideal_power_port_red.png
     # np.float32([[305, 97]]).reshape(-1, 1, 2),  #train_power_port_red_webcam.png
 
-    # Add the ideal 3D target to our list
-    ideal_target_list.append(ideal_power_port_red)
     # And add the training image we'll actually use to the training list
     training_target_power_port_red = TargetData(
         'test_images/train_power_port_red_webcam.png')
@@ -256,8 +272,6 @@
     training_target_power_port_red.target_position = ideal_power_port_red.target_position
     training_target_power_port_red.target_radius = target_radius_default
 
-    training_target_list.append(training_target_power_port_red)
-
     ###
     ### Red Loading Bay
     ###
@@ -266,16 +280,16 @@
 
     # Start at lower left corner, and work around clockwise
     # These are taken by manually finding the points in gimp for this image
-    loading_bay_red_polygon_points_2d = [(42, 406), (42, 35), (651, 34), (651,
-                                                                          406)]
+    loading_bay_red_polygon_points_2d = [(42, 406), (42, 35), (651, 34),
+                                         (651, 406)]
 
     # These are "virtual" 3D points based on the expected geometry
     loading_bay_red_polygon_points_3d = [
         (field_length / 2., loading_bay_edge_y + loading_bay_width, 0.),
         (field_length / 2., loading_bay_edge_y + loading_bay_width,
-         loading_bay_height), (field_length / 2., loading_bay_edge_y,
-                               loading_bay_height), (field_length / 2.,
-                                                     loading_bay_edge_y, 0.)
+         loading_bay_height),
+        (field_length / 2., loading_bay_edge_y, loading_bay_height),
+        (field_length / 2., loading_bay_edge_y, 0.)
     ]
 
     ideal_loading_bay_red.polygon_list.append(
@@ -291,13 +305,11 @@
     ideal_loading_bay_red.target_point_2d = np.float32([[366, 236]]).reshape(
         -1, 1, 2)  # ideal_loading_bay_red.png
 
-    ideal_target_list.append(ideal_loading_bay_red)
     training_target_loading_bay_red = TargetData(
         'test_images/train_loading_bay_red.png')
     training_target_loading_bay_red.target_rotation = ideal_loading_bay_red.target_rotation
     training_target_loading_bay_red.target_position = ideal_loading_bay_red.target_position
     training_target_loading_bay_red.target_radius = target_radius_default
-    training_target_list.append(training_target_loading_bay_red)
 
     ###
     ### Blue Power Port
@@ -308,23 +320,21 @@
     # Start at lower left corner, and work around clockwise
     # These are taken by manually finding the points in gimp for this image
     power_port_blue_main_panel_polygon_points_2d = [(438, 693), (438, 285),
-                                                    (93, 285), (440, 50), (692,
-                                                                           50),
-                                                    (692, 285), (692, 693)]
+                                                    (93, 285), (440, 50),
+                                                    (692, 50), (692, 285),
+                                                    (692, 693)]
 
     # These are "virtual" 3D points based on the expected geometry
     power_port_blue_main_panel_polygon_points_3d = [
-        (field_length / 2., -power_port_edge_y,
-         0.), (field_length / 2., -power_port_edge_y,
-               power_port_bottom_wing_height),
+        (field_length / 2., -power_port_edge_y, 0.),
+        (field_length / 2., -power_port_edge_y, power_port_bottom_wing_height),
         (field_length / 2., -power_port_edge_y + power_port_wing_width,
-         power_port_bottom_wing_height), (field_length / 2.,
-                                          -power_port_edge_y,
-                                          power_port_total_height),
+         power_port_bottom_wing_height),
+        (field_length / 2., -power_port_edge_y, power_port_total_height),
         (field_length / 2., -power_port_edge_y - power_port_width,
-         power_port_total_height), (field_length / 2.,
-                                    -power_port_edge_y - power_port_width,
-                                    power_port_bottom_wing_height),
+         power_port_total_height),
+        (field_length / 2., -power_port_edge_y - power_port_width,
+         power_port_bottom_wing_height),
         (field_length / 2., -power_port_edge_y - power_port_width, 0.)
     ]
 
@@ -343,10 +353,15 @@
     ]
 
     # Populate the blue power port
+    #    ideal_power_port_blue.polygon_list.append(
+    #        power_port_blue_main_panel_polygon_points_2d)
+    #    ideal_power_port_blue.polygon_list_3d.append(
+    #        power_port_blue_main_panel_polygon_points_3d)
+    # Including the wing panel
     ideal_power_port_blue.polygon_list.append(
-        power_port_blue_main_panel_polygon_points_2d)
+        power_port_blue_wing_panel_polygon_points_2d)
     ideal_power_port_blue.polygon_list_3d.append(
-        power_port_blue_main_panel_polygon_points_3d)
+        power_port_blue_wing_panel_polygon_points_3d)
 
     # Location of target.  Rotation is pointing in -x direction
     ideal_power_port_blue.target_rotation = np.identity(3, np.double)
@@ -357,15 +372,12 @@
     ideal_power_port_blue.target_point_2d = np.float32([[567, 180]]).reshape(
         -1, 1, 2)  # ideal_power_port_blue.png
 
-    #### TEMPORARILY DISABLING the BLUE POWER PORT target
-    #ideal_target_list.append(ideal_power_port_blue)
     training_target_power_port_blue = TargetData(
         'test_images/train_power_port_blue.png')
+    #        'test_images/image_from_ios-scaled.jpg')
     training_target_power_port_blue.target_rotation = ideal_power_port_blue.target_rotation
     training_target_power_port_blue.target_position = ideal_power_port_blue.target_position
     training_target_power_port_blue.target_radius = target_radius_default
-    #### TEMPORARILY DISABLING the BLUE POWER PORT target
-    #training_target_list.append(training_target_power_port_blue)
 
     ###
     ### Blue Loading Bay
@@ -376,16 +388,16 @@
 
     # Start at lower left corner, and work around clockwise
     # These are taken by manually finding the points in gimp for this image
-    loading_bay_blue_polygon_points_2d = [(7, 434), (7, 1), (729, 1), (729,
-                                                                       434)]
+    loading_bay_blue_polygon_points_2d = [(7, 434), (7, 1), (729, 1),
+                                          (729, 434)]
 
     # These are "virtual" 3D points based on the expected geometry
     loading_bay_blue_polygon_points_3d = [
         (-field_length / 2., -loading_bay_edge_y - loading_bay_width, 0.),
         (-field_length / 2., -loading_bay_edge_y - loading_bay_width,
-         loading_bay_height), (-field_length / 2., -loading_bay_edge_y,
-                               loading_bay_height), (-field_length / 2.,
-                                                     -loading_bay_edge_y, 0.)
+         loading_bay_height),
+        (-field_length / 2., -loading_bay_edge_y, loading_bay_height),
+        (-field_length / 2., -loading_bay_edge_y, 0.)
     ]
 
     ideal_loading_bay_blue.polygon_list.append(
@@ -403,12 +415,36 @@
     ideal_loading_bay_blue.target_point_2d = np.float32([[366, 236]]).reshape(
         -1, 1, 2)  # ideal_loading_bay_blue.png
 
-    ideal_target_list.append(ideal_loading_bay_blue)
     training_target_loading_bay_blue = TargetData(
         'test_images/train_loading_bay_blue.png')
     training_target_loading_bay_blue.target_rotation = ideal_loading_bay_blue.target_rotation
     training_target_loading_bay_blue.target_position = ideal_loading_bay_blue.target_position
     training_target_loading_bay_blue.target_radius = target_radius_default
+
+    ######################################################################
+    # Generate lists of ideal and training targets based on all the
+    # definitions above
+    ######################################################################
+
+    ### Taped power port
+    ideal_target_list.append(ideal_power_port_taped)
+    training_target_list.append(training_target_power_port_taped)
+
+    ### Red Power Port
+    ### NOTE: Temporarily taking this out of the list
+    #ideal_target_list.append(ideal_power_port_red)
+    #training_target_list.append(training_target_power_port_red)
+
+    ### Red Loading Bay
+    ideal_target_list.append(ideal_loading_bay_red)
+    training_target_list.append(training_target_loading_bay_red)
+
+    ### Blue Power Port
+    #ideal_target_list.append(ideal_power_port_blue)
+    #training_target_list.append(training_target_power_port_blue)
+
+    ### Blue Loading Bay
+    ideal_target_list.append(ideal_loading_bay_blue)
     training_target_list.append(training_target_loading_bay_blue)
 
     return ideal_target_list, training_target_list
@@ -424,8 +460,8 @@
     camera_params = camera_definition.load_camera_definitions()[0]
 
     for ideal_target in ideal_target_list:
-        glog.info(
-            "\nPreparing target for image %s" % ideal_target.image_filename)
+        glog.info("\nPreparing target for image %s" %
+                  ideal_target.image_filename)
         ideal_target.extract_features(feature_extractor)
         ideal_target.filter_keypoints_by_polygons()
         ideal_target.compute_reprojection_maps()
@@ -463,8 +499,8 @@
                 dtd.visualize_reprojections(
                     img_copy,
                     np.asarray(kp_in_poly2d).reshape(-1, 2),
-                    np.asarray(kp_in_poly3d).reshape(
-                        -1, 3), camera_params.camera_int.camera_matrix,
+                    np.asarray(kp_in_poly3d).reshape(-1, 3),
+                    camera_params.camera_int.camera_matrix,
                     camera_params.camera_int.dist_coeffs)
 
     ###############
@@ -521,14 +557,14 @@
             training_target.target_point_2d = training_target_point_2d.reshape(
                 -1, 1, 2)
 
-            glog.info("Started with %d keypoints" % len(
-                training_target.keypoint_list))
+            glog.info("Started with %d keypoints" %
+                      len(training_target.keypoint_list))
 
             training_target.keypoint_list, training_target.descriptor_list, rejected_keypoint_list, rejected_descriptor_list, _ = dtd.filter_keypoints_by_polygons(
                 training_target.keypoint_list, training_target.descriptor_list,
                 training_target.polygon_list)
-            glog.info("After filtering by polygons, had %d keypoints" % len(
-                training_target.keypoint_list))
+            glog.info("After filtering by polygons, had %d keypoints" %
+                      len(training_target.keypoint_list))
             if VISUALIZE_KEYPOINTS:
                 tam.show_keypoints(training_target.image,
                                    training_target.keypoint_list)
@@ -568,9 +604,9 @@
                     pts = polygon.astype(int).reshape(-1, 2)
                     img_copy = dtd.draw_polygon(img_copy, pts, (255, 0, 0),
                                                 True)
-                kp_tmp = np.asarray(
-                    [(kp.pt[0], kp.pt[1])
-                     for kp in training_target.keypoint_list]).reshape(-1, 2)
+                kp_tmp = np.asarray([(kp.pt[0], kp.pt[1])
+                                     for kp in training_target.keypoint_list
+                                     ]).reshape(-1, 2)
                 dtd.visualize_reprojections(
                     img_copy, kp_tmp, training_target.keypoint_list_3d,
                     camera_params.camera_int.camera_matrix,
@@ -582,11 +618,10 @@
 
 if __name__ == '__main__':
     ap = argparse.ArgumentParser()
-    ap.add_argument(
-        "--visualize",
-        help="Whether to visualize the results",
-        default=False,
-        action='store_true')
+    ap.add_argument("--visualize",
+                    help="Whether to visualize the results",
+                    default=False,
+                    action='store_true')
     args = vars(ap.parse_args())
 
     VISUALIZE_KEYPOINTS = args["visualize"]
diff --git a/y2020/vision/tools/python_code/test_images/test_taped_dusk-2021-04-03-19-30-00.png b/y2020/vision/tools/python_code/test_images/test_taped_dusk-2021-04-03-19-30-00.png
new file mode 100644
index 0000000..fb4952e
--- /dev/null
+++ b/y2020/vision/tools/python_code/test_images/test_taped_dusk-2021-04-03-19-30-00.png
Binary files differ
diff --git a/y2020/vision/tools/python_code/test_images/train_power_port_taped-2020-08-20-13-27-50.png b/y2020/vision/tools/python_code/test_images/train_power_port_taped-2020-08-20-13-27-50.png
new file mode 100644
index 0000000..b635a12
--- /dev/null
+++ b/y2020/vision/tools/python_code/test_images/train_power_port_taped-2020-08-20-13-27-50.png
Binary files differ
diff --git a/y2020/vision/tools/python_code/train_and_match.py b/y2020/vision/tools/python_code/train_and_match.py
index 7a9d9bf..ad1bbde 100644
--- a/y2020/vision/tools/python_code/train_and_match.py
+++ b/y2020/vision/tools/python_code/train_and_match.py
@@ -240,8 +240,8 @@
         transformed_target = cv2.perspectiveTransform(
             target_point_list[i].reshape(-1, 1, 2), H)
         # Ballpark the size of the circle so it looks right on image
-        radius = int(
-            32 * abs(H[0][0] + H[1][1]) / 2)  # Average of scale factors
+        radius = int(12 * abs(H[0][0] + H[1][1]) /
+                     2)  # Average of scale factors
         # We're only using one query image at this point
         query_image = query_images[QUERY_INDEX].copy()