| //===----------------------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file implements helper functions for parsing and formatting network |
| /// addresses. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #include "src/__support/net/address.h" |
| #include "hdr/inet-address-macros.h" |
| #include "hdr/stdint_proxy.h" |
| #include "hdr/types/in_addr_t.h" |
| #include "hdr/types/struct_in6_addr.h" |
| #include "hdr/types/struct_in_addr.h" |
| #include "src/__support/CPP/string_view.h" |
| #include "src/__support/common.h" |
| #include "src/__support/ctype_utils.h" |
| #include "src/__support/endian_internal.h" |
| #include "src/__support/fixedvector.h" |
| #include "src/__support/libc_assert.h" |
| #include "src/__support/str_to_integer.h" |
| #include "src/string/memory_utils/inline_bzero.h" |
| #include "src/string/memory_utils/inline_memcpy.h" |
| |
| namespace LIBC_NAMESPACE_DECL { |
| |
| namespace net { |
| |
| [[nodiscard]] bool str_to_ipv4(cpp::string_view src, struct in_addr &dst) { |
| uint8_t bytes[4]; |
| size_t idx = 0; |
| uint32_t current_val = 0; |
| size_t digits_in_octet = 0; |
| |
| for (char c : src) { |
| if (internal::isdigit(c)) { |
| // Reject octals and leading zeros |
| if (digits_in_octet > 0 && current_val == 0) |
| return false; |
| |
| current_val = current_val * 10 + internal::b36_char_to_int(c); |
| if (current_val > 255) |
| return false; |
| |
| ++digits_in_octet; |
| } else if (c == '.') { |
| if (digits_in_octet == 0 || idx == 3) |
| return false; // Empty part or too many dots |
| |
| bytes[idx++] = static_cast<uint8_t>(current_val); |
| current_val = 0; |
| digits_in_octet = 0; |
| } else { |
| return false; |
| } |
| } |
| |
| if (idx != 3 || digits_in_octet == 0) |
| return 0; |
| |
| bytes[3] = static_cast<uint8_t>(current_val); |
| inline_memcpy(&dst.s_addr, bytes, 4); |
| return true; |
| } |
| |
| [[nodiscard]] bool str_to_ipv6(cpp::string_view src, struct in6_addr &dst) { |
| constexpr size_t NUM_COMPONENTS = 8; |
| // `parts[0]` collects 16-bit groups preceding "::", while `parts[1]` collects |
| // groups following "::". When "::" is encountered, `part_idx` switches to 1. |
| // After parsing, any omitted zero groups are filled between the two parts. |
| FixedVector<uint16_t, NUM_COMPONENTS> parts[2]; |
| size_t part_idx = 0; |
| |
| while (!src.empty()) { |
| size_t non_colon = src.find_first_not_of(':'); |
| if (non_colon == cpp::string_view::npos) { |
| if (src.size() == 2 && part_idx == 0) { |
| part_idx = 1; |
| break; |
| } |
| return false; |
| } |
| |
| switch (non_colon) { |
| case 0: |
| if (part_idx > 0 || !parts[0].empty()) |
| return false; |
| break; |
| case 1: |
| if (part_idx == 0 && parts[0].empty()) |
| return false; |
| src.remove_prefix(1); |
| break; |
| case 2: |
| if (part_idx > 0) |
| return false; |
| part_idx = 1; |
| src.remove_prefix(2); |
| break; |
| default: |
| return false; |
| } |
| |
| size_t colon_pos = src.find_first_of(':'); |
| if (colon_pos == cpp::string_view::npos && src.contains('.')) { |
| struct in_addr in4; |
| if (!str_to_ipv4(src, in4)) |
| return false; |
| |
| uint16_t v4_words[2]; |
| inline_memcpy(v4_words, &in4.s_addr, sizeof(v4_words)); |
| if (!parts[part_idx].push_back(v4_words[0]) || |
| !parts[part_idx].push_back(v4_words[1])) |
| return false; |
| |
| break; |
| } |
| |
| if (internal::isspace(src[0]) || src[0] == '+' || src[0] == '-' || |
| src.starts_with("0x") || src.starts_with("0X")) |
| return false; |
| |
| auto result = internal::strtointeger<uint16_t>(src.data(), 16, src.size()); |
| if (result.has_error() || result.parsed_len == 0 || result.parsed_len > 4) |
| return false; |
| |
| if (!parts[part_idx].push_back(Endian::to_big_endian(result.value))) |
| return false; |
| |
| src.remove_prefix(static_cast<size_t>(result.parsed_len)); |
| } |
| |
| if (part_idx > 0) { |
| if (parts[0].size() + parts[1].size() >= NUM_COMPONENTS) |
| return false; |
| |
| size_t num_zeroes = NUM_COMPONENTS - parts[0].size() - parts[1].size(); |
| uint16_t *ptr = dst.s6_addr16; |
| if (!parts[0].empty()) { |
| inline_memcpy(ptr, parts[0].begin(), parts[0].size() * sizeof(uint16_t)); |
| ptr += parts[0].size(); |
| } |
| inline_bzero(ptr, num_zeroes * sizeof(uint16_t)); |
| ptr += num_zeroes; |
| if (!parts[1].empty()) |
| inline_memcpy(ptr, parts[1].begin(), parts[1].size() * sizeof(uint16_t)); |
| } else { |
| if (parts[0].size() != NUM_COMPONENTS) |
| return false; |
| inline_memcpy(dst.s6_addr16, parts[0].begin(), sizeof(dst)); |
| } |
| |
| return true; |
| } |
| |
| cpp::optional<in_addr_t> inet_addr(cpp::string_view src) { |
| constexpr int IPV4_MAX_DOT_NUM = 3; |
| in_addr_t parts[IPV4_MAX_DOT_NUM + 1] = {0}; |
| int dot_num = 0; |
| |
| for (; dot_num <= IPV4_MAX_DOT_NUM; ++dot_num) { |
| // strtointeger skips leading whitespace and signs, and accepts C23 binary |
| // string prefix (0b1.+2.-3. 4), but we don't want any of that, so we |
| // explicitly check reject these constructs. |
| if (src.empty() || !internal::isdigit(src[0]) || src.starts_with("0b") || |
| src.starts_with("0B")) |
| return cpp::nullopt; |
| |
| auto result = internal::strtointeger<in_addr_t>(src.data(), 0, src.size()); |
| parts[dot_num] = result; |
| |
| if (result.has_error() || result.parsed_len == 0) |
| return cpp::nullopt; |
| src.remove_prefix(result.parsed_len); |
| if (src.empty() || internal::isspace(src[0])) |
| break; |
| if (src[0] != '.') |
| return cpp::nullopt; |
| src.remove_prefix(1); |
| } |
| |
| if (dot_num > IPV4_MAX_DOT_NUM) |
| return cpp::nullopt; |
| |
| // converts the Internet host address src from the IPv4 numbers-and-dots |
| // notation (a[.b[.c[.d]]]) into binary form (in network byte order) |
| in_addr_t result = 0; |
| for (int i = 0; i <= dot_num; ++i) { |
| in_addr_t max_part = i == dot_num ? (0xffffffffu >> (8 * dot_num)) : 0xffu; |
| if (parts[i] > max_part) |
| return cpp::nullopt; |
| int shift = i == dot_num ? 0 : 8 * (IPV4_MAX_DOT_NUM - i); |
| result |= parts[i] << shift; |
| } |
| |
| return Endian::to_big_endian(result); |
| } |
| |
| namespace { |
| |
| size_t ipv4_num_bytes(cpp::span<const uint8_t> src) { |
| size_t result = 8; // four digits, three dots and '\0' |
| for (uint8_t val : src) |
| result += (val >= 10) + (val >= 100); |
| return result; |
| } |
| |
| size_t ipv4_to_str_unchecked(cpp::span<const uint8_t> src, |
| cpp::span<char> dst) { |
| size_t pos = 0; |
| for (unsigned i = 0; i < 4; ++i) { |
| uint8_t val = src[i]; |
| if (val >= 100) { |
| uint8_t cent = val / 100; |
| uint8_t rem = val % 100; |
| dst[pos++] = internal::int_to_b36_char(cent); |
| dst[pos++] = internal::int_to_b36_char(rem / 10); |
| dst[pos++] = internal::int_to_b36_char(rem % 10); |
| } else if (val >= 10) { |
| dst[pos++] = internal::int_to_b36_char(val / 10); |
| dst[pos++] = internal::int_to_b36_char(val % 10); |
| } else { |
| dst[pos++] = internal::int_to_b36_char(val); |
| } |
| dst[pos++] = i < 3 ? '.' : '\0'; |
| } |
| return pos; |
| } |
| |
| size_t ipv6_to_str_unchecked(const struct in6_addr &src, cpp::span<char> dst) { |
| // Find the longest run of zeroes to compress to "::" |
| struct Run { |
| unsigned start = 0; |
| unsigned len = 0; |
| }; |
| Run best, current; |
| for (unsigned i = 0; i < 8; ++i) { |
| uint16_t val = src.s6_addr16[i]; |
| if (val == 0) { |
| ++current.len; |
| } else { |
| // In case of ties, the first sequence wins. |
| if (current.len > best.len) |
| best = current; |
| current = {i + 1, 0}; |
| } |
| } |
| if (current.len > best.len) |
| best = current; |
| |
| bool is_mapped = |
| best.start == 0 && |
| (best.len == 6 || (best.len == 5 && src.s6_addr16[5] == 0xffff)); |
| unsigned num_words = is_mapped ? 6 : 8; |
| |
| size_t pos = 0; |
| auto append_word = [&](unsigned i) { |
| uint16_t word = Endian::from_big_endian(src.s6_addr16[i]); |
| // This isn't using int_to_b36_char because it's large intermediate |
| // representation prevents append_word from being inlined. |
| static constexpr char DIGITS[] = "0123456789abcdef"; |
| if (word >= 0x1000) { |
| dst[pos] = DIGITS[word >> 12]; |
| dst[pos + 1] = DIGITS[(word >> 8) & 0xf]; |
| dst[pos + 2] = DIGITS[(word >> 4) & 0xf]; |
| dst[pos + 3] = DIGITS[word & 0xf]; |
| pos += 4; |
| } else if (word >= 0x100) { |
| dst[pos] = DIGITS[word >> 8]; |
| dst[pos + 1] = DIGITS[(word >> 4) & 0xf]; |
| dst[pos + 2] = DIGITS[word & 0xf]; |
| pos += 3; |
| } else if (word >= 0x10) { |
| dst[pos] = DIGITS[(word >> 4) & 0xf]; |
| dst[pos + 1] = DIGITS[word & 0xf]; |
| pos += 2; |
| } else { |
| dst[pos] = DIGITS[word]; |
| pos += 1; |
| } |
| }; |
| |
| if (best.len < 2) { |
| // No compression |
| for (unsigned i = 0; i < 7; ++i) { |
| append_word(i); |
| dst[pos++] = ':'; |
| } |
| append_word(7); |
| dst[pos++] = '\0'; |
| return pos; |
| } |
| |
| // Left part |
| for (unsigned i = 0; i < best.start; ++i) { |
| append_word(i); |
| dst[pos++] = ':'; |
| } |
| // Compressed part |
| if (best.start == 0) |
| dst[pos++] = ':'; |
| dst[pos++] = ':'; |
| |
| // Right part (if it exists) |
| if (best.start + best.len < num_words) { |
| unsigned end = num_words - 1; |
| for (unsigned i = best.start + best.len; i < end; ++i) { |
| append_word(i); |
| dst[pos++] = ':'; |
| } |
| append_word(end); |
| if (num_words == 6) |
| dst[pos++] = ':'; |
| } |
| |
| if (is_mapped) { |
| cpp::span<const uint8_t> ipv4_part(src.s6_addr + 12, 4); |
| pos += ipv4_to_str_unchecked(ipv4_part, dst.subspan(pos)); |
| } else { |
| dst[pos++] = '\0'; |
| } |
| |
| return pos; |
| } |
| |
| } // anonymous namespace |
| |
| bool ipv4_to_str(const struct in_addr &src, cpp::span<char> dst) { |
| cpp::span<const uint8_t> addr(reinterpret_cast<const uint8_t *>(&src), 4); |
| |
| if (dst.size() < INET_ADDRSTRLEN) { |
| if (dst.size() < ipv4_num_bytes(addr)) |
| return false; |
| } |
| |
| ipv4_to_str_unchecked(addr, dst); |
| return true; |
| } |
| |
| bool ipv6_to_str(const struct in6_addr &src, cpp::span<char> dst) { |
| if (dst.size() >= INET6_ADDRSTRLEN) { |
| ipv6_to_str_unchecked(src, dst); |
| return true; |
| } |
| char buf[INET6_ADDRSTRLEN]; |
| size_t len = ipv6_to_str_unchecked(src, buf); |
| LIBC_ASSERT(len < INET6_ADDRSTRLEN); |
| if (len > dst.size()) |
| return false; |
| inline_memcpy(dst.data(), buf, len); |
| return true; |
| } |
| |
| } // namespace net |
| } // namespace LIBC_NAMESPACE_DECL |