base::Value (de)serialization for some basic net types

Working on some HostCache::Entry replacement work, which requires going
to/from base::Value for these types. This essentially duplicates some of
the (de)serialization logic, which will hopefully eventually be deleted,
in host_cache.cc.

Change-Id: Ib28b4d4b5b1da29824f64fae3bc7d83f602269a3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3964882
Commit-Queue: Eric Orth <[email protected]>
Reviewed-by: Tsuyoshi Horo <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1062892}
diff --git a/net/base/host_port_pair.cc b/net/base/host_port_pair.cc
index 1abe514..7bc4f26 100644
--- a/net/base/host_port_pair.cc
+++ b/net/base/host_port_pair.cc
@@ -12,13 +12,23 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/trace_event/memory_usage_estimator.h"
+#include "base/values.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/url_util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 #include "url/scheme_host_port.h"
 
 namespace net {
 
+namespace {
+
+// Value dictionary keys
+constexpr base::StringPiece kValueHostKey = "host";
+constexpr base::StringPiece kValuePortKey = "port";
+
+}  // namespace
+
 HostPortPair::HostPortPair() : port_(0) {}
 HostPortPair::HostPortPair(base::StringPiece in_host, uint16_t in_port)
     : host_(in_host), port_(in_port) {}
@@ -76,6 +86,23 @@
   return HostPortPair(host, port);
 }
 
+// static
+absl::optional<HostPortPair> HostPortPair::FromValue(const base::Value& value) {
+  const base::Value::Dict* dict = value.GetIfDict();
+  if (!dict)
+    return absl::nullopt;
+
+  const std::string* host = dict->FindString(kValueHostKey);
+  absl::optional<int> port = dict->FindInt(kValuePortKey);
+
+  if (host == nullptr || !port.has_value() ||
+      !base::IsValueInRangeForNumericType<uint16_t>(port.value())) {
+    return absl::nullopt;
+  }
+
+  return HostPortPair(*host, base::checked_cast<uint16_t>(port.value()));
+}
+
 std::string HostPortPair::ToString() const {
   std::string ret(HostForURL());
   ret += ':';
@@ -102,4 +129,12 @@
   return host_;
 }
 
+base::Value HostPortPair::ToValue() const {
+  base::Value::Dict dict;
+  dict.Set(kValueHostKey, host_);
+  dict.Set(kValuePortKey, port_);
+
+  return base::Value(std::move(dict));
+}
+
 }  // namespace net
diff --git a/net/base/host_port_pair.h b/net/base/host_port_pair.h
index b86d3b2c..d694af2 100644
--- a/net/base/host_port_pair.h
+++ b/net/base/host_port_pair.h
@@ -11,7 +11,9 @@
 #include <tuple>
 
 #include "base/strings/string_piece.h"
+#include "base/values.h"
 #include "net/base/net_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 class GURL;
 
@@ -42,6 +44,9 @@
   // ToString().
   static HostPortPair FromString(base::StringPiece str);
 
+  // Nullopt if `value` is malformed to be deserialized to HostPortPair.
+  static absl::optional<HostPortPair> FromValue(const base::Value& value);
+
   // TODO(willchan): Define a functor instead.
   // Comparator function so this can be placed in a std::map.
   bool operator<(const HostPortPair& other) const {
@@ -78,6 +83,8 @@
   // Returns |host_|, adding IPv6 brackets if needed.
   std::string HostForURL() const;
 
+  base::Value ToValue() const;
+
  private:
   // If |host_| represents an IPv6 address, this string will not contain
   // brackets around the address.
diff --git a/net/base/host_port_pair_unittest.cc b/net/base/host_port_pair_unittest.cc
index 89907a77..0ee593b9 100644
--- a/net/base/host_port_pair_unittest.cc
+++ b/net/base/host_port_pair_unittest.cc
@@ -4,12 +4,16 @@
 
 #include "net/base/host_port_pair.h"
 
+#include "base/values.h"
 #include "net/test/gtest_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 #include "url/scheme_host_port.h"
 
 using std::string;
+using testing::Optional;
 
 namespace net {
 
@@ -155,6 +159,39 @@
   EXPECT_EQ(parsed, expected);
 }
 
+TEST(HostPortPairTest, RoundtripThroughValue) {
+  HostPortPair pair("foo.test", 1456);
+  base::Value value = pair.ToValue();
+
+  EXPECT_THAT(HostPortPair::FromValue(value), Optional(pair));
+}
+
+TEST(HostPortPairTest, DeserializeGarbageValue) {
+  base::Value value(43);
+  EXPECT_FALSE(HostPortPair::FromValue(value).has_value());
+}
+
+TEST(HostPortPairTest, DeserializeMalformedValues) {
+  base::Value valid_value = HostPortPair("foo.test", 123).ToValue();
+  ASSERT_TRUE(HostPortPair::FromValue(valid_value).has_value());
+
+  base::Value missing_host = valid_value.Clone();
+  ASSERT_TRUE(missing_host.GetDict().Remove("host"));
+  EXPECT_FALSE(HostPortPair::FromValue(missing_host).has_value());
+
+  base::Value missing_port = valid_value.Clone();
+  ASSERT_TRUE(missing_port.GetDict().Remove("port"));
+  EXPECT_FALSE(HostPortPair::FromValue(missing_port).has_value());
+
+  base::Value negative_port = valid_value.Clone();
+  *negative_port.GetDict().Find("port") = base::Value(-1);
+  EXPECT_FALSE(HostPortPair::FromValue(negative_port).has_value());
+
+  base::Value large_port = valid_value.Clone();
+  *large_port.GetDict().Find("port") = base::Value(66000);
+  EXPECT_FALSE(HostPortPair::FromValue(large_port).has_value());
+}
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/base/ip_address.cc b/net/base/ip_address.cc
index 28e210ef..9f33182 100644
--- a/net/base/ip_address.cc
+++ b/net/base/ip_address.cc
@@ -14,7 +14,9 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/stringprintf.h"
+#include "base/values.h"
 #include "net/base/parse_number.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 #include "url/url_canon_ip.h"
 
@@ -168,6 +170,19 @@
   return !(*this == other);
 }
 
+// static
+absl::optional<IPAddress> IPAddress::FromValue(const base::Value& value) {
+  if (!value.is_string())
+    return absl::nullopt;
+
+  IPAddress address;
+  bool success = address.AssignFromIPLiteral(value.GetString());
+  if (!success || !address.IsValid())
+    return absl::nullopt;
+
+  return address;
+}
+
 IPAddress::IPAddress() = default;
 
 IPAddress::IPAddress(const IPAddress& other) = default;
@@ -361,6 +376,11 @@
   return str;
 }
 
+base::Value IPAddress::ToValue() const {
+  DCHECK(IsValid());
+  return base::Value(ToString());
+}
+
 std::string IPAddressToStringWithPort(const IPAddress& address, uint16_t port) {
   std::string address_str = address.ToString();
   if (address_str.empty())
diff --git a/net/base/ip_address.h b/net/base/ip_address.h
index a9782d8..5f0412b1 100644
--- a/net/base/ip_address.h
+++ b/net/base/ip_address.h
@@ -14,7 +14,9 @@
 
 #include "base/check_op.h"
 #include "base/strings/string_piece.h"
+#include "base/values.h"
 #include "net/base/net_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace net {
 
@@ -99,6 +101,9 @@
  public:
   enum : size_t { kIPv4AddressSize = 4, kIPv6AddressSize = 16 };
 
+  // Nullopt if `value` is malformed to be deserialized to IPAddress.
+  static absl::optional<IPAddress> FromValue(const base::Value& value);
+
   // Creates a zero-sized, invalid address.
   IPAddress();
 
@@ -215,6 +220,9 @@
   bool operator!=(const IPAddress& that) const;
   bool operator<(const IPAddress& that) const;
 
+  // Must be a valid address (per IsValid()).
+  base::Value ToValue() const;
+
  private:
   IPAddressBytes ip_address_;
 
diff --git a/net/base/ip_address_unittest.cc b/net/base/ip_address_unittest.cc
index 1345bcd..560c6cd 100644
--- a/net/base/ip_address_unittest.cc
+++ b/net/base/ip_address_unittest.cc
@@ -9,7 +9,11 @@
 #include "base/format_macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+using testing::Optional;
 
 namespace net {
 
@@ -831,6 +835,24 @@
   EXPECT_EQ("2001:db8:c000:221::", converted_ipv6_address_32.ToString());
 }
 
+TEST(IPAddressTest, RoundtripAddressThroughValue) {
+  IPAddress address(1, 2, 3, 4);
+  ASSERT_TRUE(address.IsValid());
+
+  base::Value value = address.ToValue();
+  EXPECT_THAT(IPAddress::FromValue(value), Optional(address));
+}
+
+TEST(IPAddressTest, FromGarbageValue) {
+  base::Value value(123);
+  EXPECT_FALSE(IPAddress::FromValue(value).has_value());
+}
+
+TEST(IPAddressTest, FromInvalidValue) {
+  base::Value value("1.2.3.4.5");
+  EXPECT_FALSE(IPAddress::FromValue(value).has_value());
+}
+
 }  // anonymous namespace
 
 }  // namespace net
diff --git a/net/base/ip_endpoint.cc b/net/base/ip_endpoint.cc
index 05466120..7947b58 100644
--- a/net/base/ip_endpoint.cc
+++ b/net/base/ip_endpoint.cc
@@ -8,15 +8,19 @@
 
 #include <string.h>
 #include <tuple>
+#include <utility>
 
 #include "base/check.h"
 #include "base/check_op.h"
 #include "base/notreached.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/sys_byteorder.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "net/base/ip_address.h"
 #include "net/base/sys_addrinfo.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_WIN)
 #include <winsock2.h>
@@ -27,6 +31,39 @@
 
 namespace net {
 
+namespace {
+
+// Value dictionary keys
+constexpr base::StringPiece kValueAddressKey = "address";
+constexpr base::StringPiece kValuePortKey = "port";
+
+}  // namespace
+
+// static
+absl::optional<IPEndPoint> IPEndPoint::FromValue(const base::Value& value) {
+  const base::Value::Dict* dict = value.GetIfDict();
+  if (!dict)
+    return absl::nullopt;
+
+  const base::Value* address_value = dict->Find(kValueAddressKey);
+  if (!address_value)
+    return absl::nullopt;
+  absl::optional<IPAddress> address = IPAddress::FromValue(*address_value);
+  if (!address.has_value())
+    return absl::nullopt;
+  // Expect IPAddress to only allow deserializing valid addresses.
+  DCHECK(address.value().IsValid());
+
+  absl::optional<int> port = dict->FindInt(kValuePortKey);
+  if (!port.has_value() ||
+      !base::IsValueInRangeForNumericType<uint16_t>(port.value())) {
+    return absl::nullopt;
+  }
+
+  return IPEndPoint(address.value(),
+                    base::checked_cast<uint16_t>(port.value()));
+}
+
 IPEndPoint::IPEndPoint() = default;
 
 IPEndPoint::~IPEndPoint() = default;
@@ -182,6 +219,16 @@
   return !(*this == that);
 }
 
+base::Value IPEndPoint::ToValue() const {
+  base::Value::Dict dict;
+
+  DCHECK(address_.IsValid());
+  dict.Set(kValueAddressKey, address_.ToValue());
+  dict.Set(kValuePortKey, port_);
+
+  return base::Value(std::move(dict));
+}
+
 std::ostream& operator<<(std::ostream& os, const IPEndPoint& ip_endpoint) {
   return os << ip_endpoint.ToString();
 }
diff --git a/net/base/ip_endpoint.h b/net/base/ip_endpoint.h
index 8e0f8a5..fa77f35 100644
--- a/net/base/ip_endpoint.h
+++ b/net/base/ip_endpoint.h
@@ -10,10 +10,12 @@
 #include <ostream>
 #include <string>
 
+#include "base/values.h"
 #include "build/build_config.h"
 #include "net/base/address_family.h"
 #include "net/base/ip_address.h"
 #include "net/base/net_export.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 // Replicate these from Windows headers to avoid pulling net/sys_addrinfo.h.
 // Doing that transitively brings in windows.h. Including windows.h pollutes the
@@ -36,6 +38,9 @@
 //  * Port
 class NET_EXPORT IPEndPoint {
  public:
+  // Nullopt if `value` is malformed to be serialized to IPEndPoint.
+  static absl::optional<IPEndPoint> FromValue(const base::Value& value);
+
   IPEndPoint();
   ~IPEndPoint();
   IPEndPoint(const IPAddress& address, uint16_t port);
@@ -88,6 +93,8 @@
   bool operator==(const IPEndPoint& that) const;
   bool operator!=(const IPEndPoint& that) const;
 
+  base::Value ToValue() const;
+
  private:
   IPAddress address_;
   uint16_t port_ = 0;
diff --git a/net/base/ip_endpoint_unittest.cc b/net/base/ip_endpoint_unittest.cc
index fc100b2..355f882c 100644
--- a/net/base/ip_endpoint_unittest.cc
+++ b/net/base/ip_endpoint_unittest.cc
@@ -14,12 +14,15 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/sys_byteorder.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "net/base/ip_address.h"
 #include "net/base/sockaddr_storage.h"
 #include "net/base/sys_addrinfo.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if BUILDFLAG(IS_WIN)
 #include <winsock2.h>
@@ -31,6 +34,8 @@
 #include <netinet/in.h>
 #endif
 
+using testing::Optional;
+
 namespace net {
 
 namespace {
@@ -68,10 +73,10 @@
   bool ipv6;
   IPAddress ip_address;
 } tests[] = {
-  { "127.0.00.1", "127.0.0.1", false},
-  { "192.168.1.1", "192.168.1.1", false },
-  { "::1", "[::1]", true },
-  { "2001:db8:0::42", "[2001:db8::42]", true },
+    {"127.0.00.1", "127.0.0.1", false},
+    {"192.168.1.1", "192.168.1.1", false},
+    {"::1", "[::1]", true},
+    {"2001:db8:0::42", "[2001:db8::42]", true},
 };
 
 class IPEndPointTest : public PlatformTest {
@@ -132,8 +137,8 @@
     socklen_t expected_size =
         test.ipv6 ? sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in);
     EXPECT_EQ(expected_size, storage.addr_len);
-    EXPECT_EQ(ip_endpoint.port(), GetPortFromSockaddr(storage.addr,
-                                                      storage.addr_len));
+    EXPECT_EQ(ip_endpoint.port(),
+              GetPortFromSockaddr(storage.addr, storage.addr_len));
     // And convert back to an IPEndPoint.
     IPEndPoint ip_endpoint2;
     EXPECT_TRUE(ip_endpoint2.FromSockAddr(storage.addr, storage.addr_len));
@@ -376,6 +381,47 @@
   EXPECT_EQ("", invalid_endpoint.ToStringWithoutPort());
 }
 
+TEST_F(IPEndPointTest, RoundtripThroughValue) {
+  for (const auto& test : tests) {
+    IPEndPoint endpoint(test.ip_address, 1645);
+    base::Value value = endpoint.ToValue();
+
+    EXPECT_THAT(IPEndPoint::FromValue(value), Optional(endpoint));
+  }
+}
+
+TEST_F(IPEndPointTest, FromGarbageValue) {
+  base::Value value(123);
+  EXPECT_FALSE(IPEndPoint::FromValue(value).has_value());
+}
+
+TEST_F(IPEndPointTest, FromMalformedValues) {
+  for (const auto& test : tests) {
+    base::Value valid_value = IPEndPoint(test.ip_address, 1111).ToValue();
+    ASSERT_TRUE(IPEndPoint::FromValue(valid_value).has_value());
+
+    base::Value missing_address = valid_value.Clone();
+    ASSERT_TRUE(missing_address.GetDict().Remove("address"));
+    EXPECT_FALSE(IPEndPoint::FromValue(missing_address).has_value());
+
+    base::Value missing_port = valid_value.Clone();
+    ASSERT_TRUE(missing_port.GetDict().Remove("port"));
+    EXPECT_FALSE(IPEndPoint::FromValue(missing_port).has_value());
+
+    base::Value invalid_address = valid_value.Clone();
+    *invalid_address.GetDict().Find("address") = base::Value("1.2.3.4.5");
+    EXPECT_FALSE(IPEndPoint::FromValue(invalid_address).has_value());
+
+    base::Value negative_port = valid_value.Clone();
+    *negative_port.GetDict().Find("port") = base::Value(-1);
+    EXPECT_FALSE(IPEndPoint::FromValue(negative_port).has_value());
+
+    base::Value large_port = valid_value.Clone();
+    *large_port.GetDict().Find("port") = base::Value(66000);
+    EXPECT_FALSE(IPEndPoint::FromValue(large_port).has_value());
+  }
+}
+
 }  // namespace
 
 }  // namespace net