[weave] Add Thread WARM support

Add functionality to support addressing and routing for Thread networks
to WARM.

Bug: 57904
Test: fx test weavestack-adaptation-unittests
Change-Id: I87ac2514eb11240e35358f5ef87a6cda2fc670e6
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/414744
Commit-Queue: Jason Graffius <jgraff@google.com>
Testability-Review: Jiaming (Charlie) Wang <jiamingw@google.com>
Reviewed-by: Prashanth Swaminathan <prashanthsw@google.com>
diff --git a/src/connectivity/weave/adaptation/tests/BUILD.gn b/src/connectivity/weave/adaptation/tests/BUILD.gn
index 61cd961..4e05285 100644
--- a/src/connectivity/weave/adaptation/tests/BUILD.gn
+++ b/src/connectivity/weave/adaptation/tests/BUILD.gn
@@ -49,6 +49,12 @@
     {
       name = "weave_platform_manager_unittests"
     },
+    {
+      log_settings = {
+        max_severity = "ERROR"
+      }
+      name = "weave_warm_platform_support_unittests"
+    },
   ]
   deps = [
     ":config",
@@ -60,6 +66,7 @@
     ":weave_connectivity_manager_unittests",
     ":weave_platform_manager_unittests",
     ":weave_thread_stack_manager_unittests",
+    ":weave_warm_platform_support_unittests",
   ]
   resources = [
     {
@@ -159,6 +166,13 @@
   deps = [ ":common_test_deps" ]
 }
 
+executable("weave_warm_platform_support_unittests") {
+  testonly = true
+  output_name = "weave_warm_platform_support_unittests"
+  sources = [ "warm_unittests.cpp" ]
+  deps = [ ":common_test_deps" ]
+}
+
 group("common_test_deps") {
   testonly = true
   public_deps = [
diff --git a/src/connectivity/weave/adaptation/tests/meta/weave_warm_platform_support_unittests.cmx b/src/connectivity/weave/adaptation/tests/meta/weave_warm_platform_support_unittests.cmx
new file mode 100644
index 0000000..1c2e268
--- /dev/null
+++ b/src/connectivity/weave/adaptation/tests/meta/weave_warm_platform_support_unittests.cmx
@@ -0,0 +1,10 @@
+{
+    "program": {
+        "binary": "test/weave_warm_platform_support_unittests"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.logger.LogSink"
+        ]
+    }
+}
diff --git a/src/connectivity/weave/adaptation/tests/warm_unittests.cpp b/src/connectivity/weave/adaptation/tests/warm_unittests.cpp
new file mode 100644
index 0000000..b1c445f
--- /dev/null
+++ b/src/connectivity/weave/adaptation/tests/warm_unittests.cpp
@@ -0,0 +1,658 @@
+
+// Copyright 2020 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <fuchsia/net/cpp/fidl.h>
+#include <fuchsia/net/stack/cpp/fidl_test_base.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include <lib/fit/function.h>
+#include <lib/sys/cpp/testing/component_context_provider.h>
+#include <lib/syslog/cpp/macros.h>
+
+#include <algorithm>
+#include <memory>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+// clang-format off
+#include <Weave/DeviceLayer/internal/WeaveDeviceLayerInternal.h>
+#include <Weave/DeviceLayer/ConnectivityManager.h>
+#include <Weave/DeviceLayer/ThreadStackManager.h>
+#include <Warm/Warm.h>
+// clang-format on
+
+#include "src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.h"
+#include "weave_test_fixture.h"
+
+namespace nl {
+namespace Weave {
+namespace Warm {
+namespace Platform {
+namespace testing {
+
+namespace {
+using DeviceLayer::PlatformMgrImpl;
+using DeviceLayer::ThreadStackMgrImpl;
+using DeviceLayer::Internal::testing::WeaveTestFixture;
+
+using fuchsia::net::Subnet;
+using fuchsia::net::stack::AdministrativeStatus;
+using fuchsia::net::stack::ForwardingEntry;
+using fuchsia::net::stack::InterfaceInfo;
+using fuchsia::net::stack::PhysicalStatus;
+using fuchsia::net::stack::Stack_AddForwardingEntry_Response;
+using fuchsia::net::stack::Stack_AddForwardingEntry_Result;
+using fuchsia::net::stack::Stack_AddInterfaceAddress_Response;
+using fuchsia::net::stack::Stack_AddInterfaceAddress_Result;
+using fuchsia::net::stack::Stack_DelForwardingEntry_Response;
+using fuchsia::net::stack::Stack_DelForwardingEntry_Result;
+using fuchsia::net::stack::Stack_DelInterfaceAddress_Response;
+using fuchsia::net::stack::Stack_DelInterfaceAddress_Result;
+
+// This is the OpenWeave IP address, not to be confused with the similar IpAddress from Fuchsia.
+using Inet::IPAddress;
+using Inet::IPPrefix;
+
+constexpr char kTunInterfaceName[] = "weav-tun0";
+constexpr char kThreadInterfaceName[] = "thread0";
+constexpr size_t kIpAddressSize = 16;
+
+// Copies the IP address bytes in network order from a Weave Inet::IPAddress to a std::array.
+std::array<uint8_t, kIpAddressSize> WeaveIpAddressToArray(const IPAddress& addr) {
+  std::array<uint8_t, kIpAddressSize> array;
+  const uint8_t* data = reinterpret_cast<const uint8_t*>(addr.Addr);
+  std::copy(data, data + kIpAddressSize, array.begin());
+  return array;
+}
+}  // namespace
+
+// Fake implementation of TSM delegate, only provides an interface name.
+class FakeThreadStackManagerDelegate : public DeviceLayer::ThreadStackManagerDelegateImpl {
+ public:
+  WEAVE_ERROR InitThreadStack() override {
+    interface_name_ = kThreadInterfaceName;
+    return WEAVE_NO_ERROR;
+  }
+
+  const std::string& GetInterfaceName() const override { return interface_name_; }
+
+ private:
+  std::string interface_name_;
+};
+
+// Fake implementation of fuchsia::net::stack::Stack that only provides the fake functionality
+// needed for WARM.
+class FakeNetStack : public fuchsia::net::stack::testing::Stack_TestBase {
+ private:
+  void NotImplemented_(const std::string& name) override { FAIL() << "Not implemented: " << name; }
+
+  // FIDL interface definitions
+
+  void ListInterfaces(ListInterfacesCallback callback) override {
+    std::vector<InterfaceInfo> result{interfaces_.size()};
+
+    for (size_t i = 0; i < result.size(); ++i) {
+      if (interfaces_[i].Clone(&result[i]) != ZX_OK) {
+        ADD_FAILURE() << "InterfaceInfo::Clone() failed.";
+      };
+    }
+
+    callback(std::move(result));
+  }
+
+  void AddInterfaceAddress(uint64_t interface_id, Subnet ifaddr,
+                           AddInterfaceAddressCallback callback) override {
+    Stack_AddInterfaceAddress_Result result;
+    Stack_AddInterfaceAddress_Response response;
+
+    // Interface ID 0 is always invalid.
+    if (interface_id == 0) {
+      result.set_err(fuchsia::net::stack::Error::INVALID_ARGS);
+      callback(std::move(result));
+      return;
+    }
+
+    // Find the interface with the specified ID.
+    for (auto& interface : interfaces_) {
+      if (interface_id == interface.id) {
+        interface.properties.addresses.push_back(std::move(ifaddr));
+        result.set_response(std::move(response));
+        callback(std::move(result));
+        return;
+      }
+    }
+
+    // No interface was found with the given ID.
+    result.set_err(fuchsia::net::stack::Error::NOT_FOUND);
+    callback(std::move(result));
+  }
+
+  void DelInterfaceAddress(uint64_t interface_id, Subnet ifaddr,
+                           DelInterfaceAddressCallback callback) override {
+    Stack_DelInterfaceAddress_Result result;
+    Stack_DelInterfaceAddress_Response response;
+
+    // Interface ID 0 is always invalid.
+    if (interface_id == 0) {
+      result.set_err(fuchsia::net::stack::Error::INVALID_ARGS);
+      callback(std::move(result));
+      return;
+    }
+
+    // Search the interfaces for the specified ID.
+    for (auto& interface : interfaces_) {
+      auto& addrs = interface.properties.addresses;
+      if (interface_id == interface.id) {
+        // Find the specified address.
+        auto it = std::find_if(addrs.cbegin(), addrs.cend(),
+                               [&](const Subnet& addr) { return fidl::Equals(ifaddr, addr); });
+
+        // No matching address was found.
+        if (it == addrs.cend()) {
+          result.set_err(fuchsia::net::stack::Error::NOT_FOUND);
+          callback(std::move(result));
+          return;
+        }
+
+        // Remove the address.
+        addrs.erase(it);
+        result.set_response(std::move(response));
+        callback(std::move(result));
+        return;
+      }
+    }
+
+    // No interface was found with the given ID.
+    result.set_err(fuchsia::net::stack::Error::NOT_FOUND);
+    callback(std::move(result));
+  }
+
+  void AddForwardingEntry(ForwardingEntry entry, AddForwardingEntryCallback callback) override {
+    Stack_AddForwardingEntry_Result result;
+    Stack_AddForwardingEntry_Response response;
+
+    // Interface ID 0 is always invalid.
+    if (entry.destination.is_device_id() && entry.destination.device_id() == 0) {
+      result.set_err(fuchsia::net::stack::Error::INVALID_ARGS);
+      callback(std::move(result));
+      return;
+    }
+
+    // Check if there's an existing entry for this subnet.
+    auto it =
+        std::find_if(fwd_table_.cbegin(), fwd_table_.cend(), [&](const ForwardingEntry& existing) {
+          return fidl::Equals(existing.subnet, entry.subnet);
+        });
+    if (it != fwd_table_.cend()) {
+      result.set_err(fuchsia::net::stack::Error::ALREADY_EXISTS);
+      callback(std::move(result));
+      return;
+    }
+
+    // Add to the forwarding table.
+    fwd_table_.push_back(std::move(entry));
+    result.set_response(std::move(response));
+    callback(std::move(result));
+  }
+
+  void DelForwardingEntry(Subnet subnet, DelForwardingEntryCallback callback) override {
+    Stack_DelForwardingEntry_Result result;
+    Stack_DelForwardingEntry_Response response;
+
+    // Search for the entry with the given subnet.
+    auto it = std::find_if(
+        fwd_table_.cbegin(), fwd_table_.cend(),
+        [&](const ForwardingEntry& existing) { return fidl::Equals(existing.subnet, subnet); });
+    if (it == fwd_table_.cend()) {
+      result.set_err(fuchsia::net::stack::Error::NOT_FOUND);
+      callback(std::move(result));
+      return;
+    }
+
+    // Delete the entry.
+    fwd_table_.erase(it);
+    result.set_response(std::move(response));
+    callback(std::move(result));
+  }
+
+ public:
+  // Mutators, accessors, and helpers for tests.
+
+  // Add a fake interface with the given name. Does not check for duplicates.
+  FakeNetStack& AddFakeInterface(std::string name) {
+    if (name == "") {
+      ADD_FAILURE() << "Invalid name supplied in test data.";
+    }
+    interfaces_.push_back(InterfaceInfo{
+        .id = ++last_id_assigned,
+        .properties =
+            {
+                .name = std::move(name),
+                .administrative_status = AdministrativeStatus::DISABLED,
+                .physical_status = PhysicalStatus::DOWN,
+            },
+    });
+    return *this;
+  }
+
+  // Remove the fake interface with the given name. If it is not present, no change occurs.
+  FakeNetStack& RemoveFakeInterface(std::string name) {
+    auto it = std::find_if(
+        interfaces_.cbegin(), interfaces_.cend(),
+        [&](const InterfaceInfo& interface) { return interface.properties.name == name; });
+    if (it != interfaces_.cend()) {
+      interfaces_.erase(it);
+    }
+    return *this;
+  }
+
+  // Access the current interfaces.
+  const std::vector<InterfaceInfo>& interfaces() const { return interfaces_; }
+
+  // Access the current forwarding table.
+  const std::vector<ForwardingEntry>& fwd_table() const { return fwd_table_; }
+
+  // Get a pointer to an interface by name. Returns nullptr if not found.
+  const InterfaceInfo* GetInterface(const std::string& name) const {
+    auto it = std::find_if(
+        interfaces_.cbegin(), interfaces_.cend(),
+        [&](const InterfaceInfo& interface) { return interface.properties.name == name; });
+
+    if (it != interfaces_.cend()) {
+      return &(*it);
+    } else {
+      return nullptr;
+    }
+  }
+
+  // Get a pointer to the first forwarding entry that meets the given predicate. Returns nullptr if
+  // no entries met the predicate.
+  const ForwardingEntry* FindForwardingEntry(fit::function<bool(const ForwardingEntry&)> pred) {
+    auto it = std::find_if(fwd_table_.cbegin(), fwd_table_.cend(), std::move(pred));
+    if (it != fwd_table_.cend()) {
+      return &(*it);
+    } else {
+      return nullptr;
+    }
+  }
+
+  fidl::InterfaceRequestHandler<fuchsia::net::stack::Stack> GetHandler(
+      async_dispatcher_t* dispatcher) {
+    dispatcher_ = dispatcher;
+    return [this](fidl::InterfaceRequest<fuchsia::net::stack::Stack> request) {
+      binding_.Bind(std::move(request), dispatcher_);
+    };
+  }
+
+ private:
+  fidl::Binding<fuchsia::net::stack::Stack> binding_{this};
+  async_dispatcher_t* dispatcher_;
+  std::vector<InterfaceInfo> interfaces_;
+  std::vector<ForwardingEntry> fwd_table_;
+  uint64_t last_id_assigned = 0;
+};
+
+class WarmTest : public WeaveTestFixture {
+ public:
+  void SetUp() override {
+    WeaveTestFixture::SetUp();
+
+    // Initialize everything needed for the test.
+    context_provider_.service_directory_provider()->AddService(
+        fake_net_stack_.GetHandler(dispatcher()));
+    PlatformMgrImpl().SetComponentContextForProcess(context_provider_.TakeContext());
+    ThreadStackMgrImpl().SetDelegate(std::make_unique<FakeThreadStackManagerDelegate>());
+    ThreadStackMgrImpl().InitThreadStack();
+    Warm::Platform::Init(nullptr);
+
+    // Populate initial fake interfaces
+    fake_net_stack().AddFakeInterface(kTunInterfaceName);
+    fake_net_stack().AddFakeInterface(ThreadStackMgrImpl().GetInterfaceName());
+
+    RunFixtureLoop();
+  }
+
+  void TearDown() override {
+    StopFixtureLoop();
+    ThreadStackMgrImpl().SetDelegate(nullptr);
+    WeaveTestFixture::TearDown();
+  }
+
+ protected:
+  FakeNetStack& fake_net_stack() { return fake_net_stack_; }
+
+  const InterfaceInfo* GetThreadInterface() {
+    return fake_net_stack().GetInterface(ThreadStackMgrImpl().GetInterfaceName());
+  }
+
+  uint64_t GetThreadInterfaceId() {
+    const InterfaceInfo* iface = GetThreadInterface();
+    if (iface != nullptr) {
+      return iface->id;
+    } else {
+      // ID 0 is sentinel value for an invalid ID.
+      return 0;
+    }
+  }
+
+  const InterfaceInfo* GetTunnelInterface() {
+    return fake_net_stack().GetInterface(kTunInterfaceName);
+  }
+
+  uint64_t GetTunnelInterfaceId() {
+    const InterfaceInfo* iface = GetTunnelInterface();
+    if (iface != nullptr) {
+      return iface->id;
+    } else {
+      // ID 0 is sentinel value for an invalid ID.
+      return 0;
+    }
+  }
+
+ private:
+  FakeNetStack fake_net_stack_;
+  sys::testing::ComponentContextProvider context_provider_;
+};
+
+TEST_F(WarmTest, AddRemoveAddressThread) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  // Sanity check - no addresses assigned.
+  const InterfaceInfo* iface = GetThreadInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+
+  // Attempt to add the address.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeThread, addr, kPrefixLength, /*add*/ true);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that it worked.
+  iface = GetThreadInterface();
+  ASSERT_NE(iface, nullptr);
+  ASSERT_EQ(iface->properties.addresses.size(), 1u);
+  ASSERT_TRUE(iface->properties.addresses[0].addr.is_ipv6());
+  EXPECT_EQ(WeaveIpAddressToArray(addr), iface->properties.addresses[0].addr.ipv6().addr);
+
+  // Attempt to remove the address.
+  result = AddRemoveHostAddress(kInterfaceTypeThread, addr, kPrefixLength, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that it worked.
+  iface = GetThreadInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+}
+
+TEST_F(WarmTest, AddRemoveAddressTunnel) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  // Sanity check - no addresses assigned.
+  const InterfaceInfo* iface = GetTunnelInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+
+  // Attempt to add the address.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeTunnel, addr, kPrefixLength, /*add*/ true);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that it worked.
+  iface = GetTunnelInterface();
+  ASSERT_NE(iface, nullptr);
+  ASSERT_EQ(iface->properties.addresses.size(), 1u);
+  ASSERT_TRUE(iface->properties.addresses[0].addr.is_ipv6());
+  EXPECT_EQ(WeaveIpAddressToArray(addr), iface->properties.addresses[0].addr.ipv6().addr);
+
+  // Attempt to remove the address.
+  result = AddRemoveHostAddress(kInterfaceTypeTunnel, addr, kPrefixLength, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that it worked.
+  iface = GetTunnelInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+}
+
+TEST_F(WarmTest, RemoveAddressThreadNotFound) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  // Sanity check - no addresses assigned.
+  const InterfaceInfo* iface = GetThreadInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+
+  // Attempt to remove the address, expecting failure.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeThread, addr, kPrefixLength, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultFailure);
+
+  // Sanity check - still no addresses assigned.
+  iface = GetThreadInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+}
+
+TEST_F(WarmTest, RemoveAddressTunnelNotFound) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  // Sanity check - no addresses assigned.
+  const InterfaceInfo* iface = GetTunnelInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+
+  // Attempt to remove the address, expecting failure.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeTunnel, addr, kPrefixLength, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultFailure);
+
+  // Sanity check - still no addresses assigned.
+  iface = GetTunnelInterface();
+  ASSERT_NE(iface, nullptr);
+  EXPECT_EQ(iface->properties.addresses.size(), 0u);
+}
+
+TEST_F(WarmTest, AddAddressThreadNoInterface) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  fake_net_stack().RemoveFakeInterface(kThreadInterfaceName);
+
+  // Attempt to add to the inteface when there's no Thread interface. Expect failure.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeThread, addr, kPrefixLength, /*add*/ true);
+  EXPECT_EQ(result, kPlatformResultFailure);
+}
+
+TEST_F(WarmTest, RemoveAddressThreadNoInterface) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  fake_net_stack().RemoveFakeInterface(kThreadInterfaceName);
+
+  // Attempt to remove from the inteface when there's no Thread interface. Expect failure.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeThread, addr, kPrefixLength, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultFailure);
+}
+
+TEST_F(WarmTest, AddAddressTunnelNoInterface) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  fake_net_stack().RemoveFakeInterface(kTunInterfaceName);
+
+  // Attempt to add to the inteface when there's no Tunnel interface. Expect failure.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeTunnel, addr, kPrefixLength, /*add*/ true);
+  EXPECT_EQ(result, kPlatformResultFailure);
+}
+
+TEST_F(WarmTest, RemoveAddressTunnelNoInterface) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPAddress addr;
+
+  fake_net_stack().RemoveFakeInterface(kTunInterfaceName);
+
+  // Attempt to remove from the inteface when there's no Tunnel interface. Expect failure.
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, addr));
+  auto result = AddRemoveHostAddress(kInterfaceTypeTunnel, addr, kPrefixLength, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultFailure);
+}
+
+TEST_F(WarmTest, AddRemoveHostRouteThread) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPPrefix prefix;
+
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, prefix.IPAddr));
+  prefix.Length = kPrefixLength;
+
+  // Sanity check - confirm no routes to the Thread interface exist.
+  uint64_t thread_iface_id = GetThreadInterfaceId();
+  ASSERT_NE(thread_iface_id, 0u);
+  const ForwardingEntry* route =
+      fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+        return entry.destination.is_device_id() && entry.destination.device_id() == thread_iface_id;
+      });
+  ASSERT_EQ(route, nullptr);
+
+  // Attempt to add a route to the Thread interface.
+  auto result = AddRemoveHostRoute(kInterfaceTypeThread, prefix, kRoutePriorityLow, /*add*/ true);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that a route exists to the Thread interface with the given IP.
+  route = fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+    return entry.destination.is_device_id() && entry.destination.device_id() == thread_iface_id;
+  });
+  ASSERT_NE(route, nullptr);
+  ASSERT_TRUE(route->subnet.addr.is_ipv6());
+  // Yo dawg, we heard you liked addrs, so we put an addr in your addr.
+  EXPECT_EQ(WeaveIpAddressToArray(prefix.IPAddr), route->subnet.addr.ipv6().addr);
+
+  // Remove the route to the Thread interface.
+  result = AddRemoveHostRoute(kInterfaceTypeThread, prefix, kRoutePriorityLow, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that the removal worked.
+  route = fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+    return entry.destination.is_device_id() && entry.destination.device_id() == thread_iface_id;
+  });
+  ASSERT_EQ(route, nullptr);
+}
+
+TEST_F(WarmTest, AddRemoveHostRouteTunnel) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPPrefix prefix;
+
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, prefix.IPAddr));
+  prefix.Length = kPrefixLength;
+
+  // Sanity check - confirm no routes to the Tunnel interface exist.
+  uint64_t tunnel_iface_id = GetTunnelInterfaceId();
+  ASSERT_NE(tunnel_iface_id, 0u);
+  const ForwardingEntry* route =
+      fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+        return entry.destination.is_device_id() && entry.destination.device_id() == tunnel_iface_id;
+      });
+  ASSERT_EQ(route, nullptr);
+
+  // Attempt to add a route to the Tunnel interface.
+  auto result = AddRemoveHostRoute(kInterfaceTypeTunnel, prefix, kRoutePriorityLow, /*add*/ true);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that a route exists to the Tunnel interface with the given IP.
+  route = fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+    return entry.destination.is_device_id() && entry.destination.device_id() == tunnel_iface_id;
+  });
+  ASSERT_NE(route, nullptr);
+  ASSERT_TRUE(route->subnet.addr.is_ipv6());
+  EXPECT_EQ(WeaveIpAddressToArray(prefix.IPAddr), route->subnet.addr.ipv6().addr);
+
+  // Remove the route to the Tunnel interface.
+  result = AddRemoveHostRoute(kInterfaceTypeTunnel, prefix, kRoutePriorityLow, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultSuccess);
+
+  // Confirm that the removal worked.
+  route = fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+    return entry.destination.is_device_id() && entry.destination.device_id() == tunnel_iface_id;
+  });
+  ASSERT_EQ(route, nullptr);
+}
+
+TEST_F(WarmTest, RemoveHostRouteThreadNotFound) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPPrefix prefix;
+
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, prefix.IPAddr));
+  prefix.Length = kPrefixLength;
+
+  // Sanity check - confirm no routes to the Thread interface exist.
+  uint64_t thread_iface_id = GetThreadInterfaceId();
+  ASSERT_NE(thread_iface_id, 0u);
+  const ForwardingEntry* route =
+      fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+        return entry.destination.is_device_id() && entry.destination.device_id() == thread_iface_id;
+      });
+  ASSERT_EQ(route, nullptr);
+
+  // Remove the non-existent route to the Thread interface, expect failure.
+  auto result = AddRemoveHostRoute(kInterfaceTypeThread, prefix, kRoutePriorityLow, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultFailure);
+
+  // Sanity check - confirm still no routes to the Thread interface exist.
+  route = fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+    return entry.destination.is_device_id() && entry.destination.device_id() == thread_iface_id;
+  });
+  ASSERT_EQ(route, nullptr);
+}
+
+TEST_F(WarmTest, RemoveHostRouteTunnelNotFound) {
+  constexpr char kSubnetIp[] = "2001:0DB8:0042::";
+  constexpr uint8_t kPrefixLength = 48;
+  IPPrefix prefix;
+
+  ASSERT_TRUE(IPAddress::FromString(kSubnetIp, prefix.IPAddr));
+  prefix.Length = kPrefixLength;
+
+  // Sanity check - confirm no routes to the Tunnel interface exist.
+  uint64_t tunnel_iface_id = GetTunnelInterfaceId();
+  ASSERT_NE(tunnel_iface_id, 0u);
+  const ForwardingEntry* route =
+      fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+        return entry.destination.is_device_id() && entry.destination.device_id() == tunnel_iface_id;
+      });
+  ASSERT_EQ(route, nullptr);
+
+  // Remove the non-existent route to the Tunnel interface, expect failure.
+  auto result = AddRemoveHostRoute(kInterfaceTypeTunnel, prefix, kRoutePriorityLow, /*add*/ false);
+  EXPECT_EQ(result, kPlatformResultFailure);
+
+  // Sanity check - confirm still no routes to the Tunnel interface exist.
+  route = fake_net_stack().FindForwardingEntry([=](const ForwardingEntry& entry) {
+    return entry.destination.is_device_id() && entry.destination.device_id() == tunnel_iface_id;
+  });
+  ASSERT_EQ(route, nullptr);
+}
+
+}  // namespace testing
+}  // namespace Platform
+}  // namespace Warm
+}  // namespace Weave
+}  // namespace nl
diff --git a/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.cpp b/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.cpp
index 309ffe4..593bbe5 100644
--- a/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.cpp
+++ b/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.cpp
@@ -418,6 +418,10 @@
   return ZX_OK;
 }
 
+const std::string& ThreadStackManagerDelegateImpl::GetInterfaceName() const {
+  return interface_name_;
+}
+
 }  // namespace DeviceLayer
 }  // namespace Weave
 }  // namespace nl
diff --git a/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.h b/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.h
index 000708a..b0e78d0 100644
--- a/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.h
+++ b/src/connectivity/weave/adaptation/thread_stack_manager_delegate_impl.h
@@ -38,6 +38,7 @@
   WEAVE_ERROR GetAndLogThreadStatsCounters() override;
   WEAVE_ERROR GetAndLogThreadTopologyMinimal() override;
   WEAVE_ERROR GetAndLogThreadTopologyFull() override;
+  const std::string& GetInterfaceName() const override;
 
  private:
   std::string interface_name_;
diff --git a/src/connectivity/weave/adaptation/thread_stack_manager_impl.cpp b/src/connectivity/weave/adaptation/thread_stack_manager_impl.cpp
index 1020eca..9f7efe9 100644
--- a/src/connectivity/weave/adaptation/thread_stack_manager_impl.cpp
+++ b/src/connectivity/weave/adaptation/thread_stack_manager_impl.cpp
@@ -81,6 +81,10 @@
   return delegate_->GetAndLogThreadTopologyFull();
 }
 
+const std::string& ThreadStackManagerImpl::GetInterfaceName() const {
+  return delegate_->GetInterfaceName();
+}
+
 }  // namespace DeviceLayer
 }  // namespace Weave
 }  // namespace nl
diff --git a/src/connectivity/weave/adaptation/thread_stack_manager_impl.h b/src/connectivity/weave/adaptation/thread_stack_manager_impl.h
index 6761187..62bce5e 100644
--- a/src/connectivity/weave/adaptation/thread_stack_manager_impl.h
+++ b/src/connectivity/weave/adaptation/thread_stack_manager_impl.h
@@ -64,6 +64,8 @@
     virtual WEAVE_ERROR GetAndLogThreadTopologyMinimal() = 0;
     // Log a Weave event for a full Thread topology.
     virtual WEAVE_ERROR GetAndLogThreadTopologyFull() = 0;
+    // Get the name of the thread interface.
+    virtual const std::string& GetInterfaceName() const = 0;
   };
 
   // Sets the delegate containing the platform-specific implementation. It is
@@ -129,6 +131,9 @@
   WEAVE_ERROR _GetAndLogThreadTopologyMinimal();
   WEAVE_ERROR _GetAndLogThreadTopologyFull();
 
+  // ThreadStackManagerImpl-specific functionality.
+  const std::string& GetInterfaceName() const;
+
  private:
   static ThreadStackManagerImpl sInstance;
   std::unique_ptr<Delegate> delegate_;
diff --git a/src/connectivity/weave/adaptation/warm_platform_config.h b/src/connectivity/weave/adaptation/warm_platform_config.h
index 7a8d444..1620326 100644
--- a/src/connectivity/weave/adaptation/warm_platform_config.h
+++ b/src/connectivity/weave/adaptation/warm_platform_config.h
@@ -6,13 +6,13 @@
 
 // ==================== Platform Adaptations ====================
 
-#define WARM_CONFIG_SUPPORT_THREAD 0
-#define WARM_CONFIG_SUPPORT_THREAD_ROUTING 0
+#define WARM_CONFIG_SUPPORT_THREAD 1
+#define WARM_CONFIG_SUPPORT_THREAD_ROUTING 1
 #define WARM_CONFIG_SUPPORT_LEGACY6LOWPAN_NETWORK 0
 #define WARM_CONFIG_SUPPORT_WIFI 1
 #define WARM_CONFIG_SUPPORT_CELLULAR 0
 #define WARM_CONFIG_SUPPORT_WEAVE_TUNNEL 1
-#define WARM_CONFIG_SUPPORT_BORDER_ROUTING 0
+#define WARM_CONFIG_SUPPORT_BORDER_ROUTING 1
 
 // ========== Platform-specific Configuration Overrides =========
 
diff --git a/src/connectivity/weave/adaptation/warm_support.cpp b/src/connectivity/weave/adaptation/warm_support.cpp
index 47f42ee..8c2b93e 100644
--- a/src/connectivity/weave/adaptation/warm_support.cpp
+++ b/src/connectivity/weave/adaptation/warm_support.cpp
@@ -5,6 +5,7 @@
 // clang-format off
 #include <Weave/DeviceLayer/internal/WeaveDeviceLayerInternal.h>
 #include <Weave/DeviceLayer/ConnectivityManager.h>
+#include <Weave/DeviceLayer/ThreadStackManager.h>
 #include <Warm/Warm.h>
 // clang-format on
 
@@ -27,25 +28,31 @@
 using namespace ::nl::Weave;
 using namespace ::nl::Weave::Warm;
 
-constexpr char kInterfaceName[] = "weav-tun0";
+constexpr char kTunInterfaceName[] = "weav-tun0";
 constexpr uint8_t kSubnetPrefixLen = 48;
-}  // namespace
 
-WEAVE_ERROR Init(WarmFabricStateDelegate *inFabricStateDelegate) { return WEAVE_NO_ERROR; }
+// Get the interface name associated with the interface type. Returns true on success or false if
+// the type is not yet supported.
+bool GetInterfaceName(InterfaceType interface_type, std::string *interface_name) {
+  switch (interface_type) {
+    case kInterfaceTypeThread:
+      *interface_name = ThreadStackMgrImpl().GetInterfaceName();
+      return true;
+    case kInterfaceTypeTunnel:
+      *interface_name = kTunInterfaceName;
+      return true;
+    default:
+      return false;
+  }
+}
 
-void CriticalSectionEnter(void) {}
-
-void CriticalSectionExit(void) {}
-
-void RequestInvokeActions(void) { ::nl::Weave::Warm::InvokeActions(); }
-
-// Get tunnel interface id.
+// Get network interface id.
 zx_status_t GetInterface(fuchsia::net::stack::StackSyncPtr &stack_sync_ptr,
-                         uint64_t *interface_id) {
+                         std::string interface_name, uint64_t *interface_id) {
   std::vector<fuchsia::net::stack::InterfaceInfo> ifs;
 
   if (interface_id == NULL) {
-    FX_LOGS(ERROR) << "interface_id is NULL";
+    FX_LOGS(ERROR) << "interface_id is NULL.";
     return ZX_ERR_INVALID_ARGS;
   }
 
@@ -56,13 +63,12 @@
   }
 
   std::vector<fuchsia::net::stack::InterfaceInfo>::iterator it;
-
-  it = std::find_if(ifs.begin(), ifs.end(), [](const fuchsia::net::stack::InterfaceInfo &info) {
-    return info.properties.name.compare(kInterfaceName) == 0;
+  it = std::find_if(ifs.begin(), ifs.end(), [&](const fuchsia::net::stack::InterfaceInfo &info) {
+    return info.properties.name == interface_name;
   });
 
   if (it == ifs.end()) {
-    FX_LOGS(ERROR) << "Unable to find the tun interface";
+    FX_LOGS(ERROR) << "Unable to find interface \"" << interface_name << "\".";
     return ZX_ERR_NOT_FOUND;
   }
 
@@ -70,30 +76,52 @@
   return ZX_OK;
 }
 
+}  // namespace
+
+WEAVE_ERROR Init(WarmFabricStateDelegate *inFabricStateDelegate) { return WEAVE_NO_ERROR; }
+
+void CriticalSectionEnter(void) {}
+
+void CriticalSectionExit(void) {}
+
+void RequestInvokeActions(void) { ::nl::Weave::Warm::InvokeActions(); }
+
 // Add or remove address on tunnel interface.
-PlatformResult AddRemoveHostAddress(InterfaceType in_interface_type,
-                                    const Inet::IPAddress &in_address, uint8_t in_prefix_length,
-                                    bool in_add) {
+PlatformResult AddRemoveHostAddress(InterfaceType interface_type, const Inet::IPAddress &address,
+                                    uint8_t prefix_length, bool add) {
   fuchsia::net::stack::StackSyncPtr stack_sync_ptr;
   fuchsia::net::IpAddress addr;
   fuchsia::net::Ipv6Address v6;
   uint64_t interface_id = 0;
   fuchsia::net::Subnet ifaddr;
   auto svc = nl::Weave::DeviceLayer::PlatformMgrImpl().GetComponentContextForProcess()->svc();
+
+  // Determine interface name to add to/remove from.
+  std::string interface_name;
+  if (!GetInterfaceName(interface_type, &interface_name)) {
+    FX_LOGS(ERROR) << "Cannot handle interface type \"" << interface_type << "\".";
+    return kPlatformResultFailure;
+  }
+
+  // Connect to the net Stack and grab the interface ID requested.
   zx_status_t status = svc->Connect(stack_sync_ptr.NewRequest());
   if (status != ZX_OK) {
     FX_LOGS(ERROR) << "Connect to netstack failed: " << status;
     return kPlatformResultFailure;
   }
-  status = GetInterface(stack_sync_ptr, &interface_id);
+  status = GetInterface(stack_sync_ptr, interface_name, &interface_id);
   if (status != ZX_OK) {
     return kPlatformResultFailure;
   }
-  std::memcpy(v6.addr.data(), (uint8_t *)(in_address.Addr), v6.addr.size());
+
+  // Set up the ip address and prefix.
+  std::memcpy(v6.addr.data(), (uint8_t *)(address.Addr), v6.addr.size());
   addr.set_ipv6(v6);
   ifaddr.addr = std::move(addr);
-  ifaddr.prefix_len = in_prefix_length;
-  if (in_add) {
+  ifaddr.prefix_len = prefix_length;
+
+  if (add) {
+    // Add the address to the interface.
     fuchsia::net::stack::Stack_AddInterfaceAddress_Result result;
     status = stack_sync_ptr->AddInterfaceAddress(interface_id, std::move(ifaddr), &result);
     if (status != ZX_OK || result.is_err()) {
@@ -102,6 +130,7 @@
       return kPlatformResultFailure;
     }
   } else {
+    // Remove the address from the interface.
     fuchsia::net::stack::Stack_DelInterfaceAddress_Result result;
     status = stack_sync_ptr->DelInterfaceAddress(interface_id, std::move(ifaddr), &result);
     if (status != ZX_OK || result.is_err()) {
@@ -111,31 +140,41 @@
     }
   }
 
-  FX_LOGS(INFO) << "AddRemoveHostAddress successful";
+  FX_LOGS(INFO) << "AddRemoveHostAddress successful.";
 
   return kPlatformResultSuccess;
 }
 
 // Add or remove route to/from forwarding table.
-PlatformResult AddRemoveHostRoute(InterfaceType in_interface_type, const Inet::IPPrefix &in_prefix,
-                                  RoutePriority in_priority, bool in_add) {
+PlatformResult AddRemoveHostRoute(InterfaceType interface_type, const Inet::IPPrefix &prefix,
+                                  RoutePriority priority, bool add) {
   uint64_t interface_id = 0;
   fuchsia::net::stack::StackSyncPtr stack_sync_ptr;
   fuchsia::net::Ipv6Address v6;
   fuchsia::net::stack::ForwardingEntry entry;
   auto svc = nl::Weave::DeviceLayer::PlatformMgrImpl().GetComponentContextForProcess()->svc();
+
+  // Determine interface name to add to/remove from.
+  std::string interface_name;
+  if (!GetInterfaceName(interface_type, &interface_name)) {
+    FX_LOGS(ERROR) << "Cannot handle interface type \"" << interface_type << "\".";
+    return kPlatformResultFailure;
+  }
+
+  // Connect to the net Stack and grab the interface ID requested.
   zx_status_t status = svc->Connect(stack_sync_ptr.NewRequest());
   if (status != ZX_OK) {
     FX_LOGS(ERROR) << "Connect to netstack failed: " << status;
     return kPlatformResultFailure;
   }
-  status = GetInterface(stack_sync_ptr, &interface_id);
+  status = GetInterface(stack_sync_ptr, interface_name, &interface_id);
   if (status != ZX_OK) {
     return kPlatformResultSuccess;
   }
 
-  std::memcpy(v6.addr.data(), (uint8_t *)(in_prefix.IPAddr.Addr), v6.addr.size());
-  if (in_add) {
+  std::memcpy(v6.addr.data(), (uint8_t *)(prefix.IPAddr.Addr), v6.addr.size());
+  if (add) {
+    // Add the forwarding entry for the specified interface.
     fuchsia::net::stack::Stack_AddForwardingEntry_Result result;
     entry.subnet.addr.set_ipv6(v6);
     entry.subnet.prefix_len = kSubnetPrefixLen;
@@ -151,9 +190,10 @@
       return kPlatformResultFailure;
     }
   } else {
+    // Remove the forwarding entry for the specified interface.
     fuchsia::net::Subnet subnet;
     subnet.addr.set_ipv6(v6);
-    subnet.prefix_len = in_prefix.Length;
+    subnet.prefix_len = prefix.Length;
     fuchsia::net::stack::Stack_DelForwardingEntry_Result result;
     status = stack_sync_ptr->DelForwardingEntry(std::move(subnet), &result);
     if (status != ZX_OK) {
@@ -161,49 +201,43 @@
       return kPlatformResultFailure;
     }
     if (result.is_err()) {
-      FX_LOGS(ERROR) << "DelForwardingEntry failed result";
+      FX_LOGS(ERROR) << "DelForwardingEntry failed result.";
       return kPlatformResultFailure;
     }
   }
 
-  FX_LOGS(ERROR) << "AddRemoveHostRoute successful";
+  FX_LOGS(INFO) << "AddRemoveHostRoute successful.";
   return kPlatformResultSuccess;
 }
 
 #if WARM_CONFIG_SUPPORT_THREAD
-
 PlatformResult AddRemoveThreadAddress(InterfaceType inInterfaceType,
                                       const Inet::IPAddress &inAddress, bool inAdd) {
+  // This will be handled during the subsequent AddRemoveHostAddress from WARM.
   return kPlatformResultSuccess;
 }
-
 #endif  // WARM_CONFIG_SUPPORT_THREAD
 
 #if WARM_CONFIG_SUPPORT_THREAD_ROUTING
-
-#error "Weave Thread router support not implemented"
-
 PlatformResult StartStopThreadAdvertisement(InterfaceType inInterfaceType,
                                             const Inet::IPPrefix &inPrefix, bool inStart) {
-  // TODO: implement me
+  // This is handled by the LoWPAN service, nothing to do here.
+  return kPlatformResultSuccess;
 }
-
 #endif  // WARM_CONFIG_SUPPORT_THREAD_ROUTING
 
 #if WARM_CONFIG_SUPPORT_BORDER_ROUTING
-
-#error "Weave border router support not implemented"
-
 PlatformResult AddRemoveThreadRoute(InterfaceType inInterfaceType, const Inet::IPPrefix &inPrefix,
                                     RoutePriority inPriority, bool inAdd) {
-  // TODO: implement me
+  // This will be handled during the subsequent AddRemoveHostAddress from WARM.
+  return kPlatformResultSuccess;
 }
 
 PlatformResult SetThreadRoutePriority(InterfaceType inInterfaceType, const Inet::IPPrefix &inPrefix,
                                       RoutePriority inPriority) {
-  // TODO: implement me
+  // Route priority not supported.
+  return kPlatformResultSuccess;
 }
-
 #endif  // WARM_CONFIG_SUPPORT_BORDER_ROUTING
 
 }  // namespace Platform