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