Add initial utilities to base/

This change adds IPv4Address and IPv6Address.

Change-Id: Id107842422c32b8c9232c887668504a16feb0a90
Reviewed-on: https://chromium-review.googlesource.com/1080730
Reviewed-by: mark a. foltz <mfoltz@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index ba4b065..a4ec72a 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -3,7 +3,12 @@
 # found in the LICENSE file.
 
 group("gn_all") {
-  deps = [ "//platform" ]
+  testonly = true
+
+  deps = [
+    "//base:base_unittests",
+    "//platform",
+  ]
 }
 
 executable("hello") {
diff --git a/base/BUILD.gn b/base/BUILD.gn
new file mode 100644
index 0000000..3dcd197
--- /dev/null
+++ b/base/BUILD.gn
@@ -0,0 +1,23 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("base") {
+  sources = [
+    "ip_address.cc",
+    "ip_address.h",
+  ]
+}
+
+executable("base_unittests") {
+  testonly = true
+
+  sources = [ "ip_address_unittest.cc" ]
+
+  deps = [
+    ":base",
+    "//third_party/googletest:gmock",
+    "//third_party/googletest:gtest",
+    "//third_party/googletest:gtest_main",
+  ]
+}
diff --git a/base/ip_address.cc b/base/ip_address.cc
new file mode 100644
index 0000000..e6f29f2
--- /dev/null
+++ b/base/ip_address.cc
@@ -0,0 +1,170 @@
+// Copyright 2018 The Chromium 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 "base/ip_address.h"
+
+namespace openscreen {
+
+// static
+bool IPv4Address::Parse(const std::string& s, IPv4Address* address) {
+  if (s.size() > 0 && s[0] == '.') {
+    return false;
+  }
+  uint16_t next_octet = 0;
+  int i = 0;
+  bool previous_dot = false;
+  for (auto c : s) {
+    if (c == '.') {
+      if (previous_dot) {
+        return false;
+      }
+      address->bytes[i++] = static_cast<uint8_t>(next_octet);
+      next_octet = 0;
+      previous_dot = true;
+      if (i > 3) {
+        return false;
+      }
+      continue;
+    }
+    previous_dot = false;
+    if (!std::isdigit(c)) {
+      return false;
+    }
+    next_octet = next_octet * 10 + (c - '0');
+    if (next_octet > 255) {
+      return false;
+    }
+  }
+  if (previous_dot) {
+    return false;
+  }
+  if (i != 3) {
+    return false;
+  }
+  address->bytes[i] = static_cast<uint8_t>(next_octet);
+  return true;
+}
+
+IPv4Address::IPv4Address() = default;
+IPv4Address::IPv4Address(const std::array<uint8_t, 4>& bytes) : bytes(bytes) {}
+IPv4Address::IPv4Address(const uint8_t (&b)[4])
+    : bytes{{b[0], b[1], b[2], b[3]}} {}
+IPv4Address::IPv4Address(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4)
+    : bytes{{b1, b2, b3, b4}} {}
+IPv4Address::IPv4Address(const IPv4Address& o) = default;
+
+IPv4Address& IPv4Address::operator=(const IPv4Address& o) = default;
+
+bool IPv4Address::operator==(const IPv4Address& o) const {
+  return bytes == o.bytes;
+}
+
+bool IPv4Address::operator!=(const IPv4Address& o) const {
+  return !(*this == o);
+}
+
+// static
+bool IPv6Address::Parse(const std::string& s, IPv6Address* address) {
+  if (s.size() > 1 && s[0] == ':' && s[1] != ':') {
+    return false;
+  }
+  uint16_t next_value = 0;
+  uint8_t values[16];
+  int i = 0;
+  int num_previous_colons = 0;
+  int double_colon_index = -1;
+  for (auto c : s) {
+    if (c == ':') {
+      ++num_previous_colons;
+      if (num_previous_colons == 2) {
+        if (double_colon_index != -1) {
+          return false;
+        }
+        double_colon_index = i;
+      } else if (i >= 15 || num_previous_colons > 2) {
+        return false;
+      } else {
+        values[i++] = static_cast<uint8_t>(next_value >> 8);
+        values[i++] = static_cast<uint8_t>(next_value & 0xff);
+        next_value = 0;
+      }
+    } else {
+      num_previous_colons = 0;
+      uint8_t x = 0;
+      if (c >= '0' && c <= '9') {
+        x = c - '0';
+      } else if (c >= 'a' && c <= 'f') {
+        x = c - 'a' + 10;
+      } else if (c >= 'A' && c <= 'F') {
+        x = c - 'A' + 10;
+      } else {
+        return false;
+      }
+      if (next_value & 0xf000) {
+        return false;
+      } else {
+        next_value = static_cast<uint16_t>(next_value * 16 + x);
+      }
+    }
+  }
+  if (num_previous_colons == 1) {
+    return false;
+  }
+  if (i >= 15) {
+    return false;
+  }
+  values[i++] = static_cast<uint8_t>(next_value >> 8);
+  values[i] = static_cast<uint8_t>(next_value & 0xff);
+  if (!((i == 15 && double_colon_index == -1) ||
+        (i < 14 && double_colon_index != -1))) {
+    return false;
+  }
+  for (int j = 15; j >= 0;) {
+    if (i == double_colon_index) {
+      address->bytes[j--] = values[i--];
+      while (j > i)
+        address->bytes[j--] = 0;
+    } else {
+      address->bytes[j--] = values[i--];
+    }
+  }
+  return true;
+}
+
+IPv6Address::IPv6Address() = default;
+IPv6Address::IPv6Address(const std::array<uint8_t, 16>& bytes) : bytes(bytes) {}
+IPv6Address::IPv6Address(const uint8_t (&b)[16])
+    : bytes{{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10],
+             b[11], b[12], b[13], b[14], b[15]}} {}
+IPv6Address::IPv6Address(uint8_t b1,
+                         uint8_t b2,
+                         uint8_t b3,
+                         uint8_t b4,
+                         uint8_t b5,
+                         uint8_t b6,
+                         uint8_t b7,
+                         uint8_t b8,
+                         uint8_t b9,
+                         uint8_t b10,
+                         uint8_t b11,
+                         uint8_t b12,
+                         uint8_t b13,
+                         uint8_t b14,
+                         uint8_t b15,
+                         uint8_t b16)
+    : bytes{{b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15,
+             b16}} {}
+IPv6Address::IPv6Address(const IPv6Address& o) = default;
+
+IPv6Address& IPv6Address::operator=(const IPv6Address& o) = default;
+
+bool IPv6Address::operator==(const IPv6Address& o) const {
+  return bytes == o.bytes;
+}
+
+bool IPv6Address::operator!=(const IPv6Address& o) const {
+  return !(*this == o);
+}
+
+}  // namespace openscreen
diff --git a/base/ip_address.h b/base/ip_address.h
new file mode 100644
index 0000000..8613fbb
--- /dev/null
+++ b/base/ip_address.h
@@ -0,0 +1,102 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_IP_ADDRESS_H_
+#define BASE_IP_ADDRESS_H_
+
+#include <array>
+#include <cstdint>
+#include <string>
+#include <type_traits>
+
+namespace openscreen {
+
+struct IPv4Address {
+ public:
+  // Parses a text representation of an IPv4 address (e.g. "192.168.0.1") and
+  // puts the result into |address|.  Returns true if the parsing was successful
+  // and |address| was set, false otherwise.
+  static bool Parse(const std::string& s, IPv4Address* address);
+
+  IPv4Address();
+  explicit IPv4Address(const std::array<uint8_t, 4>& bytes);
+  explicit IPv4Address(const uint8_t (&b)[4]);
+  // IPv4Address(const uint8_t*) disambiguated with
+  // IPv4Address(const uint8_t (&)[4]).
+  template <typename T,
+            typename = typename std::enable_if<
+                std::is_convertible<T, const uint8_t*>::value &&
+                !std::is_convertible<T, const uint8_t (&)[4]>::value>::type>
+  explicit IPv4Address(T b) : bytes{{b[0], b[1], b[2], b[3]}} {}
+  IPv4Address(uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4);
+  IPv4Address(const IPv4Address& o);
+  ~IPv4Address() = default;
+
+  IPv4Address& operator=(const IPv4Address& o);
+
+  bool operator==(const IPv4Address& o) const;
+  bool operator!=(const IPv4Address& o) const;
+
+  std::array<uint8_t, 4> bytes;
+};
+
+struct IPv6Address {
+ public:
+  // Parses a text representation of an IPv6 address (e.g. "abcd::1234") and
+  // puts the result into |address|.  Returns true if the parsing was successful
+  // and |address| was set, false otherwise.
+  static bool Parse(const std::string& s, IPv6Address* address);
+
+  IPv6Address();
+  explicit IPv6Address(const std::array<uint8_t, 16>& bytes);
+  explicit IPv6Address(const uint8_t (&b)[16]);
+  template <typename T,
+            typename = typename std::enable_if<
+                std::is_convertible<T, const uint8_t*>::value &&
+                !std::is_convertible<T, const uint8_t (&)[16]>::value>::type>
+  explicit IPv6Address(T b)
+      : bytes{{b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9],
+               b[10], b[11], b[12], b[13], b[14], b[15]}} {}
+  IPv6Address(uint8_t b1,
+              uint8_t b2,
+              uint8_t b3,
+              uint8_t b4,
+              uint8_t b5,
+              uint8_t b6,
+              uint8_t b7,
+              uint8_t b8,
+              uint8_t b9,
+              uint8_t b10,
+              uint8_t b11,
+              uint8_t b12,
+              uint8_t b13,
+              uint8_t b14,
+              uint8_t b15,
+              uint8_t b16);
+  IPv6Address(const IPv6Address& o);
+  ~IPv6Address() = default;
+
+  IPv6Address& operator=(const IPv6Address& o);
+
+  bool operator==(const IPv6Address& o) const;
+  bool operator!=(const IPv6Address& o) const;
+
+  std::array<uint8_t, 16> bytes;
+};
+
+struct IPv4Endpoint {
+ public:
+  IPv4Address address;
+  uint16_t port;
+};
+
+struct IPv6Endpoint {
+ public:
+  IPv6Address address;
+  uint16_t port;
+};
+
+}  // namespace openscreen
+
+#endif
diff --git a/base/ip_address_unittest.cc b/base/ip_address_unittest.cc
new file mode 100644
index 0000000..7a2306e
--- /dev/null
+++ b/base/ip_address_unittest.cc
@@ -0,0 +1,220 @@
+// Copyright 2018 The Chromium 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 "base/ip_address.h"
+#include "third_party/googletest/src/googlemock/include/gmock/gmock.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+
+namespace openscreen {
+
+using ::testing::ElementsAreArray;
+
+TEST(IPv4AddressTest, Constructors) {
+  IPv4Address address1(std::array<uint8_t, 4>{{1, 2, 3, 4}});
+  EXPECT_THAT(address1.bytes, ElementsAreArray({1, 2, 3, 4}));
+
+  uint8_t x[] = {4, 3, 2, 1};
+  IPv4Address address2(x);
+  EXPECT_THAT(address2.bytes, ElementsAreArray(x));
+
+  IPv4Address address3(&x[0]);
+  EXPECT_THAT(address3.bytes, ElementsAreArray(x));
+
+  IPv4Address address4(6, 5, 7, 9);
+  EXPECT_THAT(address4.bytes, ElementsAreArray({6, 5, 7, 9}));
+
+  IPv4Address address5(address4);
+  EXPECT_THAT(address5.bytes, ElementsAreArray({6, 5, 7, 9}));
+
+  address5 = address1;
+  EXPECT_THAT(address5.bytes, ElementsAreArray({1, 2, 3, 4}));
+}
+
+TEST(IPv4AddressTest, Comparison) {
+  IPv4Address address1;
+  EXPECT_EQ(address1, address1);
+
+  IPv4Address address2({4, 3, 2, 1});
+  EXPECT_NE(address1, address2);
+
+  IPv4Address address3({4, 3, 2, 1});
+  EXPECT_EQ(address2, address3);
+
+  address2 = address1;
+  EXPECT_EQ(address1, address2);
+}
+
+TEST(IPv4AddressTest, Parse) {
+  IPv4Address address;
+  ASSERT_TRUE(IPv4Address::Parse("192.168.0.1", &address));
+  EXPECT_THAT(address.bytes, ElementsAreArray({192, 168, 0, 1}));
+}
+
+TEST(IPv4AddressTest, ParseFailures) {
+  IPv4Address address;
+
+  EXPECT_FALSE(IPv4Address::Parse("192..0.1", &address))
+      << "empty value should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse(".192.168.0.1", &address))
+      << "leading dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse(".192.168.1", &address))
+      << "leading dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("..192.168.0.1", &address))
+      << "leading dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("..192.1", &address))
+      << "leading dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.168.0.1.", &address))
+      << "trailing dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.168.1.", &address))
+      << "trailing dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.168.1..", &address))
+      << "trailing dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.168..", &address))
+      << "trailing dot should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.x3.0.1", &address))
+      << "non-digit character should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.3.1", &address))
+      << "too few values should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("192.3.2.0.1", &address))
+      << "too many values should fail to parse";
+  EXPECT_FALSE(IPv4Address::Parse("1920.3.2.1", &address))
+      << "value > 255 should fail to parse";
+}
+
+TEST(IPv6AddressTest, Constructors) {
+  IPv6Address address1(std::array<uint8_t, 16>{
+      {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}});
+  EXPECT_THAT(address1.bytes, ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
+                                                11, 12, 13, 14, 15, 16}));
+
+  const uint8_t x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+  IPv6Address address2(x);
+  EXPECT_THAT(address2.bytes, ElementsAreArray(x));
+
+  IPv6Address address3(&x[0]);
+  EXPECT_THAT(address3.bytes, ElementsAreArray(x));
+
+  IPv6Address address4(16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
+  EXPECT_THAT(address4.bytes, ElementsAreArray({16, 15, 14, 13, 12, 11, 10, 9,
+                                                8, 7, 6, 5, 4, 3, 2, 1}));
+
+  IPv6Address address5(address4);
+  EXPECT_THAT(address5.bytes, ElementsAreArray({16, 15, 14, 13, 12, 11, 10, 9,
+                                                8, 7, 6, 5, 4, 3, 2, 1}));
+}
+
+TEST(IPv6AddressTest, Comparison) {
+  IPv6Address address1;
+  EXPECT_EQ(address1, address1);
+
+  IPv6Address address2({16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1});
+  EXPECT_NE(address1, address2);
+
+  IPv6Address address3({16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1});
+  EXPECT_EQ(address2, address3);
+
+  address2 = address1;
+  EXPECT_EQ(address1, address2);
+}
+
+TEST(IPv6AddressTest, ParseBasic) {
+  IPv6Address address;
+  ASSERT_TRUE(
+      IPv6Address::Parse("abcd:ef01:2345:6789:9876:5432:10FE:DBCA", &address));
+  EXPECT_THAT(
+      address.bytes,
+      ElementsAreArray({0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0x98,
+                        0x76, 0x54, 0x32, 0x10, 0xfe, 0xdb, 0xca}));
+}
+
+TEST(IPv6AddressTest, ParseDoubleColon) {
+  IPv6Address address1;
+  ASSERT_TRUE(
+      IPv6Address::Parse("abcd:ef01:2345:6789:9876:5432::dbca", &address1));
+  EXPECT_THAT(
+      address1.bytes,
+      ElementsAreArray({0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0x98,
+                        0x76, 0x54, 0x32, 0x00, 0x00, 0xdb, 0xca}));
+  IPv6Address address2;
+  ASSERT_TRUE(IPv6Address::Parse("abcd::10fe:dbca", &address2));
+  EXPECT_THAT(
+      address2.bytes,
+      ElementsAreArray({0xab, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x10, 0xfe, 0xdb, 0xca}));
+
+  IPv6Address address3;
+  ASSERT_TRUE(IPv6Address::Parse("::10fe:dbca", &address3));
+  EXPECT_THAT(
+      address3.bytes,
+      ElementsAreArray({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x10, 0xfe, 0xdb, 0xca}));
+
+  IPv6Address address4;
+  ASSERT_TRUE(IPv6Address::Parse("10fe:dbca::", &address4));
+  EXPECT_THAT(
+      address4.bytes,
+      ElementsAreArray({0x10, 0xfe, 0xdb, 0xca, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
+}
+
+TEST(IPv6AddressTest, SmallValues) {
+  IPv6Address address1;
+  ASSERT_TRUE(IPv6Address::Parse("::", &address1));
+  EXPECT_THAT(
+      address1.bytes,
+      ElementsAreArray({0x0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}));
+
+  IPv6Address address2;
+  ASSERT_TRUE(IPv6Address::Parse("::1", &address2));
+  EXPECT_THAT(
+      address2.bytes,
+      ElementsAreArray({0x0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}));
+
+  IPv6Address address3;
+  ASSERT_TRUE(IPv6Address::Parse("::2:1", &address3));
+  EXPECT_THAT(
+      address3.bytes,
+      ElementsAreArray({0x0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01}));
+}
+
+TEST(IPv6AddressTest, ParseFailures) {
+  IPv6Address address;
+
+  EXPECT_FALSE(IPv6Address::Parse(":abcd::dbca", &address))
+      << "leading colon should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("abcd::dbca:", &address))
+      << "trailing colon should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("abxd::1234", &address))
+      << "non-hex digit should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("abcd:1234", &address))
+      << "too few values should fail to parse";
+  EXPECT_FALSE(
+      IPv6Address::Parse("a:b:c:d:e:f:0:1:2:3:4:5:6:7:8:9:a", &address))
+      << "too many values should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("abcd1::dbca", &address))
+      << "value > 0xffff should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("::abcd::dbca", &address))
+      << "multiple double colon should fail to parse";
+
+  EXPECT_FALSE(IPv6Address::Parse(":::abcd::dbca", &address))
+      << "leading triple colon should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("abcd:::dbca", &address))
+      << "triple colon should fail to parse";
+  EXPECT_FALSE(IPv6Address::Parse("abcd:dbca:::", &address))
+      << "trailing triple colon should fail to parse";
+}
+
+TEST(IPv6AddressTest, ParseThreeDigitValue) {
+  IPv6Address address;
+  ASSERT_TRUE(IPv6Address::Parse("::123", &address));
+  EXPECT_THAT(
+      address.bytes,
+      ElementsAreArray({0x0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                        0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x23}));
+}
+
+}  // namespace openscreen