add tryCreateNetwork()
[folly.git] / folly / IPAddress.h
index 46c096578dee6ae2b2ac47c09daa88d36b019a92..943c5cec28a5425517a54d08968433862b912a1d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Facebook, Inc.
+ * Copyright 2014-present Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 #pragma once
 
 #include <functional>
-#include <iostream>
+#include <iosfwd>
 #include <memory>
 #include <string>
 #include <utility> // std::pair
 
-#include <boost/operators.hpp>
-
-#include <folly/Format.h>
-#include <folly/Range.h>
 #include <folly/IPAddressException.h>
 #include <folly/IPAddressV4.h>
 #include <folly/IPAddressV6.h>
+#include <folly/Range.h>
 #include <folly/detail/IPAddress.h>
 
 namespace folly {
@@ -67,8 +64,17 @@ typedef std::pair<IPAddress, uint8_t> CIDRNetwork;
  *   CHECK(IPAddress::createIPv6(v4addr) == v6map.asV6());
  * @encode
  */
-class IPAddress : boost::totally_ordered<IPAddress> {
+class IPAddress {
+ private:
+  template <typename F>
+  auto pick(F f) const {
+    return isV4() ? f(asV4()) : f(asV6());
+  }
+
  public:
+  // returns true iff the input string can be parsed as an ip-address
+  static bool validate(StringPiece ip) noexcept;
+
   // return the V4 representation of the address, converting it from V6 to V4 if
   // needed. Note that this will throw an IPAddressFormatException if the V6
   // address is not IPv4Mapped.
@@ -85,11 +91,26 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    *             is -1, will use /32 for IPv4 and /128 for IPv6)
    * @param [in] mask apply mask on the address or not,
    *             e.g. 192.168.13.46/24 => 192.168.13.0/24
+   * @return either pair with IPAddress network and uint8_t mask or
+   *         CIDRNetworkError
+   */
+  static Expected<CIDRNetwork, CIDRNetworkError> tryCreateNetwork(
+      StringPiece ipSlashCidr,
+      int defaultCidr = -1,
+      bool mask = true);
+
+  /**
+   * Create a network and mask from a CIDR formatted address string.
+   * Same as tryCreateNetwork() but throws IPAddressFormatException on error.
+   * The implementation calls tryCreateNetwork(...) underneath
+   *
    * @throws IPAddressFormatException if invalid address
    * @return pair with IPAddress network and uint8_t mask
    */
   static CIDRNetwork createNetwork(
-    StringPiece ipSlashCidr, int defaultCidr = -1, bool mask = true);
+      StringPiece ipSlashCidr,
+      int defaultCidr = -1,
+      bool mask = true);
 
   /**
    * Return a string representation of a CIDR block created with createNetwork.
@@ -97,9 +118,7 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    *
    * @return string representing the netblock
    */
-  static std::string networkToString(const CIDRNetwork& network) {
-    return network.first.str() + "/" + std::to_string(network.second);
-  }
+  static std::string networkToString(const CIDRNetwork& network);
 
   /**
    * Create a new IPAddress instance from the provided binary data
@@ -108,6 +127,20 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    */
   static IPAddress fromBinary(ByteRange bytes);
 
+  /**
+   * Non-throwing version of fromBinary().
+   * On failure returns IPAddressFormatError.
+   */
+  static Expected<IPAddress, IPAddressFormatError> tryFromBinary(
+      ByteRange bytes) noexcept;
+
+  /**
+   * Tries to create a new IPAddress instance from provided string and
+   * returns it on success. Returns IPAddressFormatError on failure.
+   */
+  static Expected<IPAddress, IPAddressFormatError> tryFromString(
+      StringPiece str) noexcept;
+
   /**
    * Create an IPAddress from a 32bit long (network byte order).
    * @throws IPAddressFormatException
@@ -118,8 +151,9 @@ class IPAddress : boost::totally_ordered<IPAddress> {
 
   // Given 2 IPAddress,mask pairs extract the longest common IPAddress,
   // mask pair
-  static CIDRNetwork longestCommonPrefix(const CIDRNetwork& one,
-                                         const CIDRNetwork& two);
+  static CIDRNetwork longestCommonPrefix(
+      const CIDRNetwork& one,
+      const CIDRNetwork& two);
 
   /**
    * Constructs an uninitialized IPAddress.
@@ -145,31 +179,29 @@ class IPAddress : boost::totally_ordered<IPAddress> {
   explicit IPAddress(const sockaddr* addr);
 
   // Create an IPAddress from a V4 address
-  /* implicit */ IPAddress(const IPAddressV4 ipV4Addr);
-  /* implicit */ IPAddress(const in_addr addr);
+  /* implicit */ IPAddress(const IPAddressV4 ipV4Addr) noexcept;
+  /* implicit */ IPAddress(const in_addr addr) noexcept;
 
   // Create an IPAddress from a V6 address
-  /* implicit */ IPAddress(const IPAddressV6& ipV6Addr);
-  /* implicit */ IPAddress(const in6_addr& addr);
+  /* implicit */ IPAddress(const IPAddressV6& ipV6Addr) noexcept;
+  /* implicit */ IPAddress(const in6_addr& addr) noexcept;
 
   // Assign from V4 address
-  IPAddress& operator=(const IPAddressV4& ipV4Addr);
+  IPAddress& operator=(const IPAddressV4& ipV4Addr) noexcept;
 
   // Assign from V6 address
-  IPAddress& operator=(const IPAddressV6& ipV6Addr);
+  IPAddress& operator=(const IPAddressV6& ipV6Addr) noexcept;
 
   /**
    * Converts an IPAddress to an IPAddressV4 instance.
    * @note This is not some handy convenience wrapper to convert an IPv4 address
    *       to a mapped IPv6 address. If you want that use
    *       IPAddress::createIPv6(addr)
-   * @throws IPAddressFormatException is not a V4 instance
+   * @throws InvalidAddressFamilyException is not a V4 instance
    */
   const IPAddressV4& asV4() const {
-    if (!isV4()) {
-      auto familyName = detail::familyNameStr(family());
-      throw InvalidAddressFamilyException("Can't convert address with family ",
-                                          familyName, " to AF_INET address");
+    if (UNLIKELY(!isV4())) {
+      asV4Throw();
     }
     return addr_.ipV4Addr;
   }
@@ -179,33 +211,41 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * @throws InvalidAddressFamilyException is not a V6 instance
    */
   const IPAddressV6& asV6() const {
-    if (!isV6()) {
-      auto familyName = detail::familyNameStr(family());
-      throw InvalidAddressFamilyException("Can't convert address with family ",
-                                          familyName, " to AF_INET6 address");
+    if (UNLIKELY(!isV6())) {
+      asV6Throw();
     }
     return addr_.ipV6Addr;
   }
 
   // Return sa_family_t of IPAddress
-  sa_family_t family() const { return family_; }
+  sa_family_t family() const {
+    return family_;
+  }
 
   // Populate sockaddr_storage with an appropriate value
-  int toSockaddrStorage(sockaddr_storage *dest, uint16_t port = 0) const {
+  int toSockaddrStorage(sockaddr_storagedest, uint16_t port = 0) const {
     if (dest == nullptr) {
       throw IPAddressFormatException("dest must not be null");
     }
     memset(dest, 0, sizeof(sockaddr_storage));
     dest->ss_family = family();
+
     if (isV4()) {
-      sockaddr_in *sin = reinterpret_cast<sockaddr_in*>(dest);
+      sockaddr_insin = reinterpret_cast<sockaddr_in*>(dest);
       sin->sin_addr = asV4().toAddr();
       sin->sin_port = port;
+#if defined(__APPLE__)
+      sin->sin_len = sizeof(*sin);
+#endif
       return sizeof(*sin);
     } else if (isV6()) {
-      sockaddr_in6 *sin = reinterpret_cast<sockaddr_in6*>(dest);
+      sockaddr_in6sin = reinterpret_cast<sockaddr_in6*>(dest);
       sin->sin6_addr = asV6().toAddr();
       sin->sin6_port = port;
+      sin->sin6_scope_id = asV6().getScopeId();
+#if defined(__APPLE__)
+      sin->sin6_len = sizeof(*sin);
+#endif
       return sizeof(*sin);
     } else {
       throw InvalidAddressFamilyException(family());
@@ -251,38 +291,50 @@ class IPAddress : boost::totally_ordered<IPAddress> {
     return isV6() && asV6().isIPv4Mapped();
   }
 
+  // @return true if address is uninitialized
+  bool empty() const {
+    return family_ == AF_UNSPEC;
+  }
+
+  // @return true if address is initialized
+  explicit operator bool() const {
+    return !empty();
+  }
+
   // @return true if this is an IPAddressV4 instance
-  bool isV4() const { return (family_ == AF_INET); }
+  bool isV4() const {
+    return family_ == AF_INET;
+  }
 
   // @return true if this is an IPAddressV6 instance
-  bool isV6() const { return (family_ == AF_INET6); }
+  bool isV6() const {
+    return family_ == AF_INET6;
+  }
 
   // @return true if this address is all zeros
   bool isZero() const {
-    return isV4() ? asV4().isZero()
-                  : asV6().isZero();
+    return pick([&](auto& _) { return _.isZero(); });
   }
 
   // Number of bits in the address representation.
   size_t bitCount() const {
-    return isV4() ? IPAddressV4::bitCount()
-                  : IPAddressV6::bitCount();
+    return pick([&](auto& _) { return _.bitCount(); });
   }
   // Number of bytes in the address representation.
   size_t byteCount() const {
     return bitCount() / 8;
   }
-  //get nth most significant bit - 0 indexed
+  // get nth most significant bit - 0 indexed
   bool getNthMSBit(size_t bitIndex) const {
     return detail::getNthMSBitImpl(*this, bitIndex, family());
   }
-  //get nth most significant byte - 0 indexed
+  // get nth most significant byte - 0 indexed
   uint8_t getNthMSByte(size_t byteIndex) const;
-  //get nth bit - 0 indexed
+  // get nth bit - 0 indexed
   bool getNthLSBit(size_t bitIndex) const {
     return getNthMSBit(bitCount() - bitIndex - 1);
   }
-  //get nth byte - 0 indexed
+  // get nth byte - 0 indexed
   uint8_t getNthLSByte(size_t byteIndex) const {
     return getNthMSByte(byteCount() - byteIndex - 1);
   }
@@ -294,26 +346,27 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * {family:'AF_INET|AF_INET6', addr:'address', hash:long}.
    */
   std::string toJson() const {
-    return isV4() ? asV4().toJson()
-                  : asV6().toJson();
+    return pick([&](auto& _) { return _.toJson(); });
   }
 
   // Hash of address
   std::size_t hash() const {
-    return isV4() ? asV4().hash()
-                  : asV6().hash();
+    return pick([&](auto& _) { return _.hash(); });
   }
 
   // Return true if the address qualifies as localhost.
   bool isLoopback() const {
-    return isV4() ? asV4().isLoopback()
-                  : asV6().isLoopback();
+    return pick([&](auto& _) { return _.isLoopback(); });
+  }
+
+  // Return true if the address qualifies as link local
+  bool isLinkLocal() const {
+    return pick([&](auto& _) { return _.isLinkLocal(); });
   }
 
   // Return true if the address qualifies as broadcast.
   bool isLinkLocalBroadcast() const {
-    return isV4() ? asV4().isLinkLocalBroadcast()
-                  : asV6().isLinkLocalBroadcast();
+    return pick([&](auto& _) { return _.isLinkLocalBroadcast(); });
   }
 
   /**
@@ -323,8 +376,7 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * 2000::/3, ffxe::/16.
    */
   bool isNonroutable() const {
-    return isV4() ? asV4().isNonroutable()
-                  : asV6().isNonroutable();
+    return pick([&](auto& _) { return _.isNonroutable(); });
   }
 
   /**
@@ -332,14 +384,12 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * (for example, 192.168.xxx.xxx or fc00::/7 addresses)
    */
   bool isPrivate() const {
-    return isV4() ? asV4().isPrivate()
-                  : asV6().isPrivate();
+    return pick([&](auto& _) { return _.isPrivate(); });
   }
 
   // Return true if the address is a multicast address.
   bool isMulticast() const {
-    return isV4() ? asV4().isMulticast()
-                  : asV6().isMulticast();
+    return pick([&](auto& _) { return _.isMulticast(); });
   }
 
   /**
@@ -349,8 +399,7 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * @return IPAddress instance with bits set to 0
    */
   IPAddress mask(uint8_t numBits) const {
-    return isV4() ? IPAddress(std::move(asV4().mask(numBits)))
-                  : IPAddress(std::move(asV6().mask(numBits)));
+    return pick([&](auto& _) { return IPAddress(_.mask(numBits)); });
   }
 
   /**
@@ -359,8 +408,7 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * @throws IPAddressFormatException on inet_ntop error
    */
   std::string str() const {
-    return isV4() ? asV4().str()
-                  : asV6().str();
+    return pick([&](auto& _) { return _.str(); });
   }
 
   /**
@@ -369,33 +417,39 @@ class IPAddress : boost::totally_ordered<IPAddress> {
    * this is the hex representation with : characters inserted every 4 digits.
    */
   std::string toFullyQualified() const {
-    return isV4() ? asV4().toFullyQualified()
-                  : asV6().toFullyQualified();
+    return pick([&](auto& _) { return _.toFullyQualified(); });
+  }
+
+  /// Same as toFullyQualified but append to an output string.
+  void toFullyQualifiedAppend(std::string& out) const {
+    return pick([&](auto& _) { return _.toFullyQualifiedAppend(out); });
   }
 
   // Address version (4 or 6)
   uint8_t version() const {
-    return isV4() ? asV4().version()
-                  : asV6().version();
+    return pick([&](auto& _) { return _.version(); });
   }
 
   /**
    * Access to address bytes, in network byte order.
    */
   const unsigned char* bytes() const {
-    return isV4() ? asV4().bytes() : asV6().bytes();
+    return pick([&](auto& _) { return _.bytes(); });
   }
 
  private:
+  [[noreturn]] void asV4Throw() const;
+  [[noreturn]] void asV6Throw() const;
+
   typedef union IPAddressV46 {
     IPAddressV4 ipV4Addr;
     IPAddressV6 ipV6Addr;
     // default constructor
-    IPAddressV46() {
+    IPAddressV46() noexcept {
       std::memset(this, 0, sizeof(IPAddressV46));
     }
-    explicit IPAddressV46(const IPAddressV4& addr): ipV4Addr(addr) {}
-    explicit IPAddressV46(const IPAddressV6& addr): ipV6Addr(addr) {}
+    explicit IPAddressV46(const IPAddressV4& addr) noexcept : ipV4Addr(addr) {}
+    explicit IPAddressV46(const IPAddressV6& addr) noexcept : ipV6Addr(addr) {}
   } IPAddressV46;
   IPAddressV46 addr_;
   sa_family_t family_;
@@ -420,14 +474,27 @@ void toAppend(IPAddress addr, fbstring* result);
 bool operator==(const IPAddress& addr1, const IPAddress& addr2);
 // Return true if addr1 < addr2
 bool operator<(const IPAddress& addr1, const IPAddress& addr2);
-
-}  // folly
+// Derived operators
+inline bool operator!=(const IPAddress& a, const IPAddress& b) {
+  return !(a == b);
+}
+inline bool operator>(const IPAddress& a, const IPAddress& b) {
+  return b < a;
+}
+inline bool operator<=(const IPAddress& a, const IPAddress& b) {
+  return !(a > b);
+}
+inline bool operator>=(const IPAddress& a, const IPAddress& b) {
+  return !(a < b);
+}
+
+} // namespace folly
 
 namespace std {
-template<>
+template <>
 struct hash<folly::IPAddress> {
   size_t operator()(const folly::IPAddress& addr) const {
     return addr.hash();
   }
 };
-}  // std
+} // namespace std