Commit a34214c4 authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

Support various operations on empty IPAddress and SocketAddress

Summary: [Folly] Support various operations on empty `IPAddress` and `SocketAddress`, i.e. default-constructed instances, such as `operator==` and `hash`. V.s. throwing exceptions.

Reviewed By: simpkins

Differential Revision: D15750100

fbshipit-source-id: 89853635f6aab3c295f6fca4e66e6435b0839357
parent 3fe63e48
...@@ -373,6 +373,9 @@ uint8_t IPAddress::getNthMSByte(size_t byteIndex) const { ...@@ -373,6 +373,9 @@ uint8_t IPAddress::getNthMSByte(size_t byteIndex) const {
// public // public
bool operator==(const IPAddress& addr1, const IPAddress& addr2) { bool operator==(const IPAddress& addr1, const IPAddress& addr2) {
if (addr1.empty() || addr2.empty()) {
return addr1.empty() == addr2.empty();
}
if (addr1.family() == addr2.family()) { if (addr1.family() == addr2.family()) {
if (addr1.isV6()) { if (addr1.isV6()) {
return (addr1.asV6() == addr2.asV6()); return (addr1.asV6() == addr2.asV6());
...@@ -403,6 +406,9 @@ bool operator==(const IPAddress& addr1, const IPAddress& addr2) { ...@@ -403,6 +406,9 @@ bool operator==(const IPAddress& addr1, const IPAddress& addr2) {
} }
bool operator<(const IPAddress& addr1, const IPAddress& addr2) { bool operator<(const IPAddress& addr1, const IPAddress& addr2) {
if (addr1.empty() || addr2.empty()) {
return addr1.empty() < addr2.empty();
}
if (addr1.family() == addr2.family()) { if (addr1.family() == addr2.family()) {
if (addr1.isV6()) { if (addr1.isV6()) {
return (addr1.asV6() < addr2.asV6()); return (addr1.asV6() < addr2.asV6());
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
#include <folly/IPAddressV6.h> #include <folly/IPAddressV6.h>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/detail/IPAddress.h> #include <folly/detail/IPAddress.h>
#include <folly/lang/Exception.h>
namespace folly { namespace folly {
...@@ -70,7 +71,68 @@ class IPAddress { ...@@ -70,7 +71,68 @@ class IPAddress {
private: private:
template <typename F> template <typename F>
auto pick(F f) const { auto pick(F f) const {
return isV4() ? f(asV4()) : f(asV6()); return isV4() ? f(asV4()) : isV6() ? f(asV6()) : f(asNone());
}
class IPAddressNone {
public:
bool isZero() const {
return true;
}
size_t bitCount() const {
return 0;
}
std::string toJson() const {
return "{family:'AF_UNSPEC', addr:'', hash:0}";
}
std::size_t hash() const {
return std::hash<uint64_t>{}(0);
}
bool isLoopback() const {
throw_exception<InvalidAddressFamilyException>("empty address");
}
bool isLinkLocal() const {
throw_exception<InvalidAddressFamilyException>("empty address");
}
bool isLinkLocalBroadcast() const {
throw_exception<InvalidAddressFamilyException>("empty address");
}
bool isNonroutable() const {
throw_exception<InvalidAddressFamilyException>("empty address");
}
bool isPrivate() const {
throw_exception<InvalidAddressFamilyException>("empty address");
}
bool isMulticast() const {
throw_exception<InvalidAddressFamilyException>("empty address");
}
IPAddress mask(uint8_t numBits) const {
(void)numBits;
return IPAddress();
}
std::string str() const {
return "";
}
std::string toFullyQualified() const {
return "";
}
void toFullyQualifiedAppend(std::string& out) const {
(void)out;
return;
}
uint8_t version() const {
return 0;
}
const unsigned char* bytes() const {
return nullptr;
}
};
IPAddressNone const& asNone() const {
if (!empty()) {
throw_exception<InvalidAddressFamilyException>("not empty");
}
return addr_.ipNoneAddr;
} }
public: public:
...@@ -427,7 +489,7 @@ class IPAddress { ...@@ -427,7 +489,7 @@ class IPAddress {
return pick([&](auto& _) { return _.toFullyQualifiedAppend(out); }); return pick([&](auto& _) { return _.toFullyQualifiedAppend(out); });
} }
// Address version (4 or 6) // Address version (0 if empty, or 4 or 6 if nonempty)
uint8_t version() const { uint8_t version() const {
return pick([&](auto& _) { return _.version(); }); return pick([&](auto& _) { return _.version(); });
} }
...@@ -444,14 +506,10 @@ class IPAddress { ...@@ -444,14 +506,10 @@ class IPAddress {
[[noreturn]] void asV6Throw() const; [[noreturn]] void asV6Throw() const;
typedef union IPAddressV46 { typedef union IPAddressV46 {
std::aligned_storage< IPAddressNone ipNoneAddr;
constexpr_max(sizeof(IPAddressV4), sizeof(IPAddressV6)),
constexpr_max(alignof(IPAddressV4), alignof(IPAddressV6))>::type
storage;
IPAddressV4 ipV4Addr; IPAddressV4 ipV4Addr;
IPAddressV6 ipV6Addr; IPAddressV6 ipV6Addr;
// default constructor IPAddressV46() noexcept : ipNoneAddr() {}
IPAddressV46() noexcept : storage() {}
explicit IPAddressV46(const IPAddressV4& addr) noexcept : ipV4Addr(addr) {} explicit IPAddressV46(const IPAddressV4& addr) noexcept : ipV4Addr(addr) {}
explicit IPAddressV46(const IPAddressV6& addr) noexcept : ipV6Addr(addr) {} explicit IPAddressV46(const IPAddressV6& addr) noexcept : ipV6Addr(addr) {}
} IPAddressV46; } IPAddressV46;
......
...@@ -537,10 +537,11 @@ bool SocketAddress::operator==(const SocketAddress& other) const { ...@@ -537,10 +537,11 @@ bool SocketAddress::operator==(const SocketAddress& other) const {
case AF_INET: case AF_INET:
case AF_INET6: case AF_INET6:
return (other.storage_.addr == storage_.addr) && (other.port_ == port_); return (other.storage_.addr == storage_.addr) && (other.port_ == port_);
case AF_UNSPEC:
return other.storage_.addr.empty();
default: default:
throw std::invalid_argument( throw_exception<std::invalid_argument>(
"SocketAddress: unsupported address family " "SocketAddress: unsupported address family for comparison");
"for comparison");
} }
} }
...@@ -589,10 +590,12 @@ size_t SocketAddress::hash() const { ...@@ -589,10 +590,12 @@ size_t SocketAddress::hash() const {
assert(external_); assert(external_);
break; break;
case AF_UNSPEC: case AF_UNSPEC:
assert(storage_.addr.empty());
boost::hash_combine(seed, storage_.addr.hash());
break;
default: default:
throw std::invalid_argument( throw_exception<std::invalid_argument>(
"SocketAddress: unsupported address family " "SocketAddress: unsupported address family for comparison");
"for hashing");
} }
return seed; return seed;
......
...@@ -419,6 +419,11 @@ TEST(IPAddress, CtorDefault) { ...@@ -419,6 +419,11 @@ TEST(IPAddress, CtorDefault) {
EXPECT_EQ(IPAddressV4("0.0.0.0"), v4); EXPECT_EQ(IPAddressV4("0.0.0.0"), v4);
IPAddressV6 v6; IPAddressV6 v6;
EXPECT_EQ(IPAddressV6("::0"), v6); EXPECT_EQ(IPAddressV6("::0"), v6);
IPAddress v0;
EXPECT_EQ(IPAddress(), v0);
EXPECT_NE(v0, v4);
EXPECT_NE(v0, v6);
EXPECT_NE(v4, v6);
} }
TEST(IPAddressV4, validate) { TEST(IPAddressV4, validate) {
...@@ -543,6 +548,14 @@ TEST(IPAddress, CtorSockaddr) { ...@@ -543,6 +548,14 @@ TEST(IPAddress, CtorSockaddr) {
EXPECT_THROW(IPAddress((sockaddr*)&addr), IPAddressFormatException); EXPECT_THROW(IPAddress((sockaddr*)&addr), IPAddressFormatException);
} }
// test none address
{
IPAddress ipAddr;
EXPECT_TRUE(ipAddr.empty());
EXPECT_FALSE(ipAddr.isV4());
EXPECT_FALSE(ipAddr.isV6());
EXPECT_EQ("", ipAddr.str());
}
} }
TEST(IPAddress, ToSockaddrStorage) { TEST(IPAddress, ToSockaddrStorage) {
......
...@@ -197,6 +197,11 @@ TEST(SocketAddress, SetFromStrings) { ...@@ -197,6 +197,11 @@ TEST(SocketAddress, SetFromStrings) {
} }
TEST(SocketAddress, EqualityAndHash) { TEST(SocketAddress, EqualityAndHash) {
SocketAddress empty1;
SocketAddress empty2;
EXPECT_EQ(empty1, empty2);
EXPECT_EQ(empty1.hash(), empty2.hash());
// IPv4 // IPv4
SocketAddress local1("127.0.0.1", 1234); SocketAddress local1("127.0.0.1", 1234);
EXPECT_EQ(local1, local1); EXPECT_EQ(local1, local1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment