| // Copyright 2017 The Fuchsia 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 "src/lib/inet/ip_address.h" | 
 |  | 
 | #include <arpa/inet.h> | 
 | #include <endian.h> | 
 | #include <netdb.h> | 
 | #include <sys/socket.h> | 
 |  | 
 | #include <sstream> | 
 |  | 
 | namespace inet { | 
 | namespace { | 
 |  | 
 | // Parses a string. Match functions either return true and update the position | 
 | // of the parser or return false and leave the position unchanged. | 
 | class Parser { | 
 |  public: | 
 |   Parser(const std::string& str) : str_(str), pos_(0) {} | 
 |  | 
 |   // Matches end-of-string. | 
 |   bool MatchEnd() { return pos_ == str_.length(); } | 
 |  | 
 |   // Matches a specified character. | 
 |   bool Match(char to_match) { | 
 |     if (pos_ == str_.length() || str_[pos_] != to_match) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     ++pos_; | 
 |  | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Matches a single decimal digit. | 
 |   bool MatchDecDigit(uint8_t* out) { | 
 |     FX_DCHECK(out); | 
 |  | 
 |     if (pos_ == str_.length() || !std::isdigit(static_cast<unsigned char>(str_[pos_]))) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     *out = str_[pos_] - '0'; | 
 |     ++pos_; | 
 |  | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Matches a single lowercase hexadecimal digit. | 
 |   bool MatchLowerHexDigit(uint8_t* out) { | 
 |     FX_DCHECK(out); | 
 |  | 
 |     if (pos_ == str_.length() || !std::isxdigit(static_cast<unsigned char>(str_[pos_]))) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     if (std::isdigit(static_cast<unsigned char>(str_[pos_]))) { | 
 |       *out = str_[pos_] - '0'; | 
 |     } else if (std::islower(static_cast<unsigned char>(str_[pos_]))) { | 
 |       *out = 10 + (str_[pos_] - 'a'); | 
 |     } else { | 
 |       // Uppercase hexadecimal is not permitted. | 
 |       return false; | 
 |     } | 
 |  | 
 |     ++pos_; | 
 |  | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Matches a decimal byte of at most 3 digits. The match will succeed even | 
 |   // if the decimal byte is followed immediately by a digit. If matching three | 
 |   // digits would produce a value greater than 255, only two digits are matched. | 
 |   bool MatchMax3DigitDecByte(uint8_t* byte_out) { | 
 |     FX_DCHECK(byte_out); | 
 |  | 
 |     uint8_t digit = 0; | 
 |     if (!MatchDecDigit(&digit)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     uint16_t accum = digit; | 
 |     if (MatchDecDigit(&digit)) { | 
 |       accum = accum * 10 + digit; | 
 |       if (accum <= 25 && MatchDecDigit(&digit)) { | 
 |         if (accum < 25 || digit < 6) { | 
 |           accum = accum * 10 + digit; | 
 |         } else { | 
 |           // Including that last digit would produce a value > 255. | 
 |           --pos_; | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     FX_DCHECK(accum <= 255); | 
 |     *byte_out = static_cast<uint8_t>(accum); | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Matches a lowercase hexadecimal word of at most 4 digits. The match will | 
 |   // succeed even if the hexadecimal word is followed immediately by a | 
 |   // hexadecimal digit. | 
 |   bool MatchMax4DigitLowerHexWord(uint16_t* word_out) { | 
 |     FX_DCHECK(word_out); | 
 |  | 
 |     uint8_t digit = 0; | 
 |     if (!MatchLowerHexDigit(&digit)) { | 
 |       return false; | 
 |     } | 
 |  | 
 |     uint16_t accum = digit; | 
 |     if (MatchLowerHexDigit(&digit)) { | 
 |       accum = accum * 16 + digit; | 
 |       if (MatchLowerHexDigit(&digit)) { | 
 |         accum = accum * 16 + digit; | 
 |         if (MatchLowerHexDigit(&digit)) { | 
 |           accum = accum * 16 + digit; | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     *word_out = accum; | 
 |     return true; | 
 |   } | 
 |  | 
 |   // Matches an IPV4 address. | 
 |   bool MatchIpV4Address(IpAddress* address_out) { | 
 |     FX_DCHECK(address_out); | 
 |  | 
 |     size_t old_pos = pos_; | 
 |     uint8_t b0, b1, b2, b3; | 
 |     if (MatchMax3DigitDecByte(&b0) && Match('.') && MatchMax3DigitDecByte(&b1) && Match('.') && | 
 |         MatchMax3DigitDecByte(&b2) && Match('.') && MatchMax3DigitDecByte(&b3)) { | 
 |       *address_out = IpAddress(b0, b1, b2, b3); | 
 |       return true; | 
 |     } | 
 |  | 
 |     pos_ = old_pos; | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Matches an IPV6 address. | 
 |   bool MatchIpV6Address(IpAddress* address_out) { | 
 |     FX_DCHECK(address_out); | 
 |  | 
 |     size_t old_pos = pos_; | 
 |     uint16_t words[8]; | 
 |     size_t word_index = 0; | 
 |     ssize_t ellipsis_word_index = -1; | 
 |  | 
 |     if (Match(':')) { | 
 |       ellipsis_word_index = 0; | 
 |       words[0] = 0; | 
 |     } else if (!MatchMax4DigitLowerHexWord(&words[word_index])) { | 
 |       pos_ = old_pos; | 
 |       return false; | 
 |     } | 
 |  | 
 |     if (Match(':')) { | 
 |       while (true) { | 
 |         // At this point, we've matched at least one word, we've just matched a | 
 |         // colon, and |word_index| indexes the last word we matched. | 
 |         ++word_index; | 
 |  | 
 |         // Check for "::" ellipsis. | 
 |         if (Match(':')) { | 
 |           if (ellipsis_word_index != -1) { | 
 |             // More than one "::" ellipsis. | 
 |             break; | 
 |           } | 
 |  | 
 |           ellipsis_word_index = word_index; | 
 |         } | 
 |  | 
 |         if (MatchMax4DigitLowerHexWord(&words[word_index])) { | 
 |           if (word_index < 7 && Match(':')) { | 
 |             // More words to read. | 
 |             continue; | 
 |           } | 
 |         } else { | 
 |           // We didn't match that last word. | 
 |           --word_index; | 
 |           if (word_index == 0) { | 
 |             if (ellipsis_word_index != 1) { | 
 |               // Need at least two words, if we don't have a trailing pair of colons. | 
 |               break; | 
 |             } | 
 |           } else { | 
 |             // We've read a ':' past the end. | 
 |             --pos_; | 
 |           } | 
 |         } | 
 |  | 
 |         if (word_index == 7) { | 
 |           if (ellipsis_word_index != -1) { | 
 |             // We parsed 8 words, and there's an ellipsis. | 
 |             break; | 
 |           } | 
 |         } else { | 
 |           if (ellipsis_word_index == -1) { | 
 |             // We parsed less than 8 words, and there's no ellipsis. | 
 |             break; | 
 |           } | 
 |  | 
 |           // Insert zeros for the ellipsis. | 
 |           ssize_t to = 7; | 
 |           for (ssize_t from = word_index; from >= ellipsis_word_index; --from) { | 
 |             words[to] = words[from]; | 
 |             --to; | 
 |           } | 
 |  | 
 |           for (; to >= ellipsis_word_index; --to) { | 
 |             words[to] = 0; | 
 |           } | 
 |         } | 
 |  | 
 |         *address_out = IpAddress(words[0], words[1], words[2], words[3], words[4], words[5], | 
 |                                  words[6], words[7]); | 
 |         return true; | 
 |       } | 
 |     } | 
 |  | 
 |     pos_ = old_pos; | 
 |     return false; | 
 |   } | 
 |  | 
 |  private: | 
 |   const std::string& str_; | 
 |   size_t pos_; | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | // static | 
 | const IpAddress IpAddress::kInvalid; | 
 | // static | 
 | const IpAddress IpAddress::kV4Loopback(127, 0, 0, 1); | 
 | // static | 
 | const IpAddress IpAddress::kV6Loopback(0, 0, 0, 0, 0, 0, 0, 1); | 
 |  | 
 | // static | 
 | IpAddress IpAddress::FromString(const std::string address_string, sa_family_t family) { | 
 |   FX_DCHECK(family == AF_UNSPEC || family == AF_INET || family == AF_INET6); | 
 |  | 
 |   Parser parser(address_string); | 
 |   IpAddress address; | 
 |   if ((parser.MatchIpV4Address(&address) || parser.MatchIpV6Address(&address)) && | 
 |       parser.MatchEnd()) { | 
 |     return address; | 
 |   } | 
 |  | 
 |   return kInvalid; | 
 | } | 
 |  | 
 | IpAddress::IpAddress() { | 
 |   family_ = AF_UNSPEC; | 
 |   std::memset(&v6_, 0, sizeof(v6_)); | 
 | } | 
 |  | 
 | IpAddress::IpAddress(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3) { | 
 |   family_ = AF_INET; | 
 |   uint8_t* bytes = reinterpret_cast<uint8_t*>(&v4_.s_addr); | 
 |   bytes[0] = b0; | 
 |   bytes[1] = b1; | 
 |   bytes[2] = b2; | 
 |   bytes[3] = b3; | 
 | } | 
 |  | 
 | IpAddress::IpAddress(in_addr_t addr) { | 
 |   family_ = AF_INET; | 
 |   v4_.s_addr = addr; | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const in_addr& addr) { | 
 |   family_ = AF_INET; | 
 |   v4_ = addr; | 
 | } | 
 |  | 
 | IpAddress::IpAddress(uint16_t w0, uint16_t w1, uint16_t w2, uint16_t w3, uint16_t w4, uint16_t w5, | 
 |                      uint16_t w6, uint16_t w7) { | 
 |   family_ = AF_INET6; | 
 |   uint16_t* words = v6_.s6_addr16; | 
 |   words[0] = htobe16(w0); | 
 |   words[1] = htobe16(w1); | 
 |   words[2] = htobe16(w2); | 
 |   words[3] = htobe16(w3); | 
 |   words[4] = htobe16(w4); | 
 |   words[5] = htobe16(w5); | 
 |   words[6] = htobe16(w6); | 
 |   words[7] = htobe16(w7); | 
 | } | 
 |  | 
 | IpAddress::IpAddress(uint16_t w0, uint16_t w7) { | 
 |   family_ = AF_INET6; | 
 |   std::memset(&v6_, 0, sizeof(v6_)); | 
 |   uint16_t* words = v6_.s6_addr16; | 
 |   words[0] = htobe16(w0); | 
 |   words[7] = htobe16(w7); | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const in6_addr& addr) { | 
 |   family_ = AF_INET6; | 
 |   v6_ = addr; | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const sockaddr* addr) { | 
 |   FX_DCHECK(addr != nullptr); | 
 |   switch (addr->sa_family) { | 
 |     case AF_INET: | 
 |       family_ = AF_INET; | 
 |       v4_ = *reinterpret_cast<const in_addr*>(addr->sa_data); | 
 |       break; | 
 |     case AF_INET6: | 
 |       family_ = AF_INET6; | 
 |       v6_ = *reinterpret_cast<const in6_addr*>(addr->sa_data); | 
 |       break; | 
 |     default: | 
 |       family_ = AF_UNSPEC; | 
 |       std::memset(&v6_, 0, sizeof(v6_)); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const sockaddr_storage& addr) { | 
 |   switch (addr.ss_family) { | 
 |     case AF_INET: | 
 |       family_ = AF_INET; | 
 |       v4_ = *reinterpret_cast<const in_addr*>(reinterpret_cast<const uint8_t*>(&addr) + | 
 |                                               sizeof(sa_family_t)); | 
 |       break; | 
 |     case AF_INET6: | 
 |       family_ = AF_INET6; | 
 |       v6_ = *reinterpret_cast<const in6_addr*>(reinterpret_cast<const uint8_t*>(&addr) + | 
 |                                                sizeof(sa_family_t)); | 
 |       break; | 
 |     default: | 
 |       family_ = AF_UNSPEC; | 
 |       std::memset(&v6_, 0, sizeof(v6_)); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const fuchsia::net::Ipv4Address* addr) { | 
 |   FX_DCHECK(addr != nullptr); | 
 |   family_ = AF_INET; | 
 |   memcpy(&v4_, addr->addr.data(), 4); | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const fuchsia::net::Ipv6Address* addr) { | 
 |   FX_DCHECK(addr != nullptr); | 
 |   family_ = AF_INET6; | 
 |   memcpy(&v6_, addr->addr.data(), 16); | 
 | } | 
 |  | 
 | IpAddress::IpAddress(const fuchsia::net::IpAddress* addr) { | 
 |   FX_DCHECK(addr != nullptr); | 
 |   switch (addr->Which()) { | 
 |     case fuchsia::net::IpAddress::Tag::kIpv4: | 
 |       family_ = AF_INET; | 
 |       memcpy(&v4_, addr->ipv4().addr.data(), 4); | 
 |       break; | 
 |     case fuchsia::net::IpAddress::Tag::kIpv6: | 
 |       family_ = AF_INET6; | 
 |       memcpy(&v6_, addr->ipv6().addr.data(), 16); | 
 |       break; | 
 |     default: | 
 |       FX_DCHECK(false); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | bool IpAddress::is_mapped_from_v4() const { | 
 |   // A V6 address mapped from a V4 address takes the form 0::ffff:xxxx:xxxx, where the x's make | 
 |   // up the V4 address. | 
 |   return is_v6() && v6_.s6_addr16[0] == 0 && v6_.s6_addr16[1] == 0 && v6_.s6_addr16[2] == 0 && | 
 |          v6_.s6_addr16[3] == 0 && v6_.s6_addr16[4] == 0 && v6_.s6_addr16[5] == 0xffff; | 
 | } | 
 |  | 
 | IpAddress IpAddress::mapped_v4_address() const { | 
 |   FX_DCHECK(is_mapped_from_v4()); | 
 |   auto bytes = as_bytes(); | 
 |   return IpAddress(bytes[12], bytes[13], bytes[14], bytes[15]); | 
 | } | 
 |  | 
 | IpAddress IpAddress::mapped_as_v6() const { | 
 |   FX_DCHECK(is_v4()); | 
 |   auto bytes = as_bytes(); | 
 |   // The words passed in to this constructor are stored in big-endian order. | 
 |   return IpAddress(0, 0, 0, 0, 0, 0xffff, static_cast<uint16_t>(bytes[0]) << 8 | bytes[1], | 
 |                    static_cast<uint16_t>(bytes[2]) << 8 | bytes[3]); | 
 | } | 
 |  | 
 | bool IpAddress::is_loopback() const { | 
 |   switch (family_) { | 
 |     case AF_INET: | 
 |       return *this == kV4Loopback; | 
 |     case AF_INET6: | 
 |       return *this == kV6Loopback; | 
 |     default: | 
 |       return false; | 
 |   } | 
 | } | 
 |  | 
 | std::string IpAddress::ToString() const { | 
 |   std::ostringstream os; | 
 |   os << *this; | 
 |   return os.str(); | 
 | } | 
 |  | 
 | std::ostream& operator<<(std::ostream& os, const IpAddress& value) { | 
 |   if (!value.is_valid()) { | 
 |     return os << "<invalid>"; | 
 |   } | 
 |  | 
 |   if (value.is_v4()) { | 
 |     const uint8_t* bytes = value.as_bytes(); | 
 |     return os << static_cast<int>(bytes[0]) << '.' << static_cast<int>(bytes[1]) << '.' | 
 |               << static_cast<int>(bytes[2]) << '.' << static_cast<int>(bytes[3]); | 
 |   } else { | 
 |     // IPV6 text representation per RFC 5952: | 
 |     // 1) Suppress leading zeros in hex representation of words. | 
 |     // 2) Don't use '::' to shorten a just single zero word. | 
 |     // 3) Shorten the longest sequence of zero words preferring the leftmost | 
 |     //    sequence if there's a tie. | 
 |     // 4) Use lower-case hexadecimal. | 
 |  | 
 |     const uint16_t* words = value.as_words(); | 
 |  | 
 |     // Figure out where the longest span of zeros is. | 
 |     uint8_t start_of_zeros; | 
 |     uint8_t zeros_seen = 0; | 
 |     uint8_t start_of_best_zeros = 255; | 
 |     // Don't bother if the longest sequence is length 1. | 
 |     uint8_t best_zeros_seen = 1; | 
 |  | 
 |     for (uint8_t i = 0; i < 8; ++i) { | 
 |       if (words[i] == 0) { | 
 |         if (zeros_seen == 0) { | 
 |           start_of_zeros = i; | 
 |         } | 
 |         ++zeros_seen; | 
 |       } else if (zeros_seen != 0) { | 
 |         if (zeros_seen > best_zeros_seen) { | 
 |           start_of_best_zeros = start_of_zeros; | 
 |           best_zeros_seen = zeros_seen; | 
 |         } | 
 |         zeros_seen = 0; | 
 |       } | 
 |     } | 
 |  | 
 |     if (zeros_seen > best_zeros_seen) { | 
 |       start_of_best_zeros = start_of_zeros; | 
 |       best_zeros_seen = zeros_seen; | 
 |     } | 
 |  | 
 |     os << std::hex; | 
 |     for (uint8_t i = 0; i < 8; ++i) { | 
 |       if (i < start_of_best_zeros || i >= start_of_best_zeros + best_zeros_seen) { | 
 |         os << betoh16(words[i]); | 
 |         if (i != 7) { | 
 |           os << ":"; | 
 |         } | 
 |       } else if (i == start_of_best_zeros) { | 
 |         if (i == 0) { | 
 |           os << "::"; | 
 |         } else { | 
 |           os << ":";  // We just wrote a ':', so we only need one more. | 
 |         } | 
 |       } | 
 |     } | 
 |     return os << std::dec; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace inet |