LCOV - code coverage report
Current view: top level - src - ipv6_address.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 90.0 % 251 226
Test Date: 2026-02-06 05:04:16 Functions: 100.0 % 17 17

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2026 Vinnie Falco (vinnie dot falco at gmail dot com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/corosio
       8              : //
       9              : 
      10              : #include <boost/corosio/ipv6_address.hpp>
      11              : #include <boost/corosio/ipv4_address.hpp>
      12              : 
      13              : #include <cstring>
      14              : #include <ostream>
      15              : #include <stdexcept>
      16              : 
      17              : namespace boost::corosio {
      18              : 
      19           40 : ipv6_address::ipv6_address(bytes_type const& bytes) noexcept
      20              : {
      21           40 :     std::memcpy(addr_.data(), bytes.data(), 16);
      22           40 : }
      23              : 
      24            3 : ipv6_address::ipv6_address(ipv4_address const& addr) noexcept
      25              : {
      26            3 :     auto const v = addr.to_bytes();
      27           12 :     addr_ = {{
      28              :         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      29            3 :         0xff, 0xff, v[0], v[1], v[2], v[3]
      30              :     }};
      31            3 : }
      32              : 
      33            3 : ipv6_address::ipv6_address(std::string_view s)
      34              : {
      35            3 :     auto ec = parse_ipv6_address(s, *this);
      36            3 :     if (ec)
      37            2 :         throw std::invalid_argument("invalid IPv6 address");
      38            1 : }
      39              : 
      40              : std::string
      41            6 : ipv6_address::to_string() const
      42              : {
      43              :     char buf[max_str_len];
      44            6 :     auto n = print_impl(buf);
      45           12 :     return std::string(buf, n);
      46              : }
      47              : 
      48              : std::string_view
      49            2 : ipv6_address::to_buffer(char* dest, std::size_t dest_size) const
      50              : {
      51            2 :     if (dest_size < max_str_len)
      52            0 :         throw std::length_error("buffer too small for IPv6 address");
      53            2 :     auto n = print_impl(dest);
      54            2 :     return std::string_view(dest, n);
      55              : }
      56              : 
      57              : bool
      58            3 : ipv6_address::is_unspecified() const noexcept
      59              : {
      60            3 :     return *this == ipv6_address();
      61              : }
      62              : 
      63              : bool
      64           10 : ipv6_address::is_loopback() const noexcept
      65              : {
      66           10 :     return *this == loopback();
      67              : }
      68              : 
      69              : bool
      70           12 : ipv6_address::is_v4_mapped() const noexcept
      71              : {
      72              :     return
      73           24 :         addr_[ 0] == 0 && addr_[ 1] == 0 &&
      74           10 :         addr_[ 2] == 0 && addr_[ 3] == 0 &&
      75           10 :         addr_[ 4] == 0 && addr_[ 5] == 0 &&
      76           10 :         addr_[ 6] == 0 && addr_[ 7] == 0 &&
      77           10 :         addr_[ 8] == 0 && addr_[ 9] == 0 &&
      78           28 :         addr_[10] == 0xff &&
      79           16 :         addr_[11] == 0xff;
      80              : }
      81              : 
      82              : ipv6_address
      83           22 : ipv6_address::loopback() noexcept
      84              : {
      85           22 :     ipv6_address a;
      86           22 :     a.addr_[15] = 1;
      87           22 :     return a;
      88              : }
      89              : 
      90              : std::ostream&
      91            1 : operator<<(std::ostream& os, ipv6_address const& addr)
      92              : {
      93              :     char buf[ipv6_address::max_str_len];
      94            1 :     os << addr.to_buffer(buf, sizeof(buf));
      95            1 :     return os;
      96              : }
      97              : 
      98              : std::size_t
      99            8 : ipv6_address::print_impl(char* dest) const noexcept
     100              : {
     101           27 :     auto const count_zeroes = [](unsigned char const* first,
     102              :                                   unsigned char const* const last)
     103              :     {
     104           27 :         std::size_t n = 0;
     105           66 :         while (first != last)
     106              :         {
     107           65 :             if (first[0] != 0 || first[1] != 0)
     108              :                 break;
     109           39 :             n += 2;
     110           39 :             first += 2;
     111              :         }
     112           27 :         return n;
     113              :     };
     114              : 
     115           21 :     auto const print_hex = [](char* dest, unsigned short v)
     116              :     {
     117           21 :         char const* const dig = "0123456789abcdef";
     118           21 :         if (v >= 0x1000)
     119              :         {
     120            2 :             *dest++ = dig[v >> 12];
     121            2 :             v &= 0x0fff;
     122            2 :             *dest++ = dig[v >> 8];
     123            2 :             v &= 0x0ff;
     124            2 :             *dest++ = dig[v >> 4];
     125            2 :             v &= 0x0f;
     126            2 :             *dest++ = dig[v];
     127              :         }
     128           19 :         else if (v >= 0x100)
     129              :         {
     130            0 :             *dest++ = dig[v >> 8];
     131            0 :             v &= 0x0ff;
     132            0 :             *dest++ = dig[v >> 4];
     133            0 :             v &= 0x0f;
     134            0 :             *dest++ = dig[v];
     135              :         }
     136           19 :         else if (v >= 0x10)
     137              :         {
     138            0 :             *dest++ = dig[v >> 4];
     139            0 :             v &= 0x0f;
     140            0 :             *dest++ = dig[v];
     141              :         }
     142              :         else
     143              :         {
     144           19 :             *dest++ = dig[v];
     145              :         }
     146           21 :         return dest;
     147              :     };
     148              : 
     149            8 :     auto const dest0 = dest;
     150              :     // find longest run of zeroes
     151            8 :     std::size_t best_len = 0;
     152            8 :     int best_pos = -1;
     153            8 :     auto it = addr_.data();
     154            8 :     auto const v4 = is_v4_mapped();
     155           16 :     auto const end = v4 ? (it + addr_.size() - 4) : it + addr_.size();
     156              : 
     157           35 :     while (it != end)
     158              :     {
     159           27 :         auto n = count_zeroes(it, end);
     160           27 :         if (n == 0)
     161              :         {
     162           21 :             it += 2;
     163           21 :             continue;
     164              :         }
     165            6 :         if (n > best_len)
     166              :         {
     167            6 :             best_pos = static_cast<int>(it - addr_.data());
     168            6 :             best_len = n;
     169              :         }
     170            6 :         it += n;
     171              :     }
     172              : 
     173            8 :     it = addr_.data();
     174            8 :     if (best_pos != 0)
     175              :     {
     176            2 :         unsigned short v = static_cast<unsigned short>(
     177            2 :             it[0] * 256U + it[1]);
     178            2 :         dest = print_hex(dest, v);
     179            2 :         it += 2;
     180              :     }
     181              :     else
     182              :     {
     183            6 :         *dest++ = ':';
     184            6 :         it += best_len;
     185            6 :         if (it == end)
     186            1 :             *dest++ = ':';
     187              :     }
     188              : 
     189           27 :     while (it != end)
     190              :     {
     191           19 :         *dest++ = ':';
     192           19 :         if (it - addr_.data() == best_pos)
     193              :         {
     194            0 :             it += best_len;
     195            0 :             if (it == end)
     196            0 :                 *dest++ = ':';
     197            0 :             continue;
     198              :         }
     199           19 :         unsigned short v = static_cast<unsigned short>(
     200           19 :             it[0] * 256U + it[1]);
     201           19 :         dest = print_hex(dest, v);
     202           19 :         it += 2;
     203              :     }
     204              : 
     205            8 :     if (v4)
     206              :     {
     207              :         ipv4_address::bytes_type bytes;
     208            2 :         bytes[0] = it[0];
     209            2 :         bytes[1] = it[1];
     210            2 :         bytes[2] = it[2];
     211            2 :         bytes[3] = it[3];
     212            2 :         ipv4_address a(bytes);
     213            2 :         *dest++ = ':';
     214              :         char buf[ipv4_address::max_str_len];
     215            2 :         auto sv = a.to_buffer(buf, sizeof(buf));
     216            2 :         std::memcpy(dest, sv.data(), sv.size());
     217            2 :         dest += sv.size();
     218              :     }
     219              : 
     220            8 :     return static_cast<std::size_t>(dest - dest0);
     221              : }
     222              : 
     223              : //------------------------------------------------
     224              : 
     225              : namespace {
     226              : 
     227              : // Convert hex character to value (0-15), or -1 if not hex
     228              : inline int
     229          304 : hexdig_value(char c) noexcept
     230              : {
     231          304 :     if (c >= '0' && c <= '9')
     232          199 :         return c - '0';
     233          105 :     if (c >= 'a' && c <= 'f')
     234           23 :         return c - 'a' + 10;
     235           82 :     if (c >= 'A' && c <= 'F')
     236            0 :         return c - 'A' + 10;
     237           82 :     return -1;
     238              : }
     239              : 
     240              : // Parse h16 (1-4 hex digits) returning 16-bit value
     241              : // Returns true on success, advances `it`
     242              : bool
     243          120 : parse_h16(
     244              :     char const*& it,
     245              :     char const* end,
     246              :     unsigned char& hi,
     247              :     unsigned char& lo) noexcept
     248              : {
     249          120 :     if (it == end)
     250            0 :         return false;
     251              : 
     252          120 :     int d = hexdig_value(*it);
     253          120 :     if (d < 0)
     254            2 :         return false;
     255              : 
     256          118 :     unsigned v = static_cast<unsigned>(d);
     257          118 :     ++it;
     258              : 
     259          164 :     for (int i = 0; i < 3 && it != end; ++i)
     260              :     {
     261          124 :         d = hexdig_value(*it);
     262          124 :         if (d < 0)
     263           78 :             break;
     264           46 :         v = (v << 4) | static_cast<unsigned>(d);
     265           46 :         ++it;
     266              :     }
     267              : 
     268          118 :     hi = static_cast<unsigned char>((v >> 8) & 0xff);
     269          118 :     lo = static_cast<unsigned char>(v & 0xff);
     270          118 :     return true;
     271              : }
     272              : 
     273              : // Check if a hex word could be 0..255 if interpreted as decimal
     274              : bool
     275            4 : maybe_octet(unsigned char const* p) noexcept
     276              : {
     277            4 :     unsigned short word = static_cast<unsigned short>(
     278            4 :         p[0]) * 256 + static_cast<unsigned short>(p[1]);
     279            4 :     if (word > 0x255)
     280            0 :         return false;
     281            4 :     if (((word >> 4) & 0xf) > 9)
     282            0 :         return false;
     283            4 :     if ((word & 0xf) > 9)
     284            0 :         return false;
     285            4 :     return true;
     286              : }
     287              : 
     288              : } // namespace
     289              : 
     290              : std::error_code
     291           52 : parse_ipv6_address(
     292              :     std::string_view s,
     293              :     ipv6_address& addr) noexcept
     294              : {
     295           52 :     auto it = s.data();
     296           52 :     auto const end = it + s.size();
     297              : 
     298           52 :     int n = 8;      // words needed
     299           52 :     int b = -1;     // value of n when '::' seen
     300           52 :     bool c = false; // need colon
     301           52 :     auto prev = it;
     302           52 :     ipv6_address::bytes_type bytes{};
     303              :     unsigned char hi, lo;
     304              : 
     305              :     for (;;)
     306              :     {
     307          205 :         if (it == end)
     308              :         {
     309           32 :             if (b != -1)
     310              :             {
     311              :                 // end in "::"
     312           28 :                 break;
     313              :             }
     314              :             // not enough words
     315            4 :             return std::make_error_code(std::errc::invalid_argument);
     316              :         }
     317              : 
     318          173 :         if (*it == ':')
     319              :         {
     320          109 :             ++it;
     321          109 :             if (it == end)
     322              :             {
     323              :                 // expected ':'
     324            3 :                 return std::make_error_code(std::errc::invalid_argument);
     325              :             }
     326          106 :             if (*it == ':')
     327              :             {
     328           42 :                 if (b == -1)
     329              :                 {
     330              :                     // first "::"
     331           41 :                     ++it;
     332           41 :                     --n;
     333           41 :                     b = n;
     334           41 :                     if (n == 0)
     335            0 :                         break;
     336           41 :                     c = false;
     337           41 :                     continue;
     338              :                 }
     339              :                 // extra "::" found
     340            1 :                 return std::make_error_code(std::errc::invalid_argument);
     341              :             }
     342           64 :             if (c)
     343              :             {
     344           61 :                 prev = it;
     345           61 :                 if (!parse_h16(it, end, hi, lo))
     346            0 :                     return std::make_error_code(std::errc::invalid_argument);
     347           61 :                 bytes[2 * (8 - n) + 0] = hi;
     348           61 :                 bytes[2 * (8 - n) + 1] = lo;
     349           61 :                 --n;
     350           61 :                 if (n == 0)
     351            5 :                     break;
     352           56 :                 continue;
     353              :             }
     354              :             // expected h16
     355            3 :             return std::make_error_code(std::errc::invalid_argument);
     356              :         }
     357              : 
     358           64 :         if (*it == '.')
     359              :         {
     360            4 :             if (b == -1 && n > 1)
     361              :             {
     362              :                 // not enough h16
     363            0 :                 return std::make_error_code(std::errc::invalid_argument);
     364              :             }
     365            4 :             if (!maybe_octet(&bytes[2 * (7 - n)]))
     366              :             {
     367              :                 // invalid octet
     368            0 :                 return std::make_error_code(std::errc::invalid_argument);
     369              :             }
     370              :             // rewind the h16 and parse it as IPv4
     371            4 :             it = prev;
     372            4 :             ipv4_address v4;
     373            4 :             auto ec = parse_ipv4_address(
     374            4 :                 std::string_view(it, static_cast<std::size_t>(end - it)), v4);
     375            4 :             if (ec)
     376            0 :                 return ec;
     377              :             // Must consume exactly the IPv4 address portion
     378              :             // Re-parse to find where it ends
     379            4 :             auto v4_it = it;
     380           45 :             while (v4_it != end && (*v4_it == '.' || 
     381           29 :                    (*v4_it >= '0' && *v4_it <= '9')))
     382           41 :                 ++v4_it;
     383              :             // Verify it parsed correctly by re-parsing the exact substring
     384            4 :             ipv4_address v4_check;
     385            4 :             ec = parse_ipv4_address(
     386            4 :                 std::string_view(it, static_cast<std::size_t>(v4_it - it)),
     387              :                 v4_check);
     388            4 :             if (ec)
     389            0 :                 return ec;
     390            4 :             it = v4_it;
     391            4 :             auto const b4 = v4_check.to_bytes();
     392            4 :             bytes[2 * (7 - n) + 0] = b4[0];
     393            4 :             bytes[2 * (7 - n) + 1] = b4[1];
     394            4 :             bytes[2 * (7 - n) + 2] = b4[2];
     395            4 :             bytes[2 * (7 - n) + 3] = b4[3];
     396            4 :             --n;
     397            4 :             break;
     398              :         }
     399              : 
     400           60 :         auto d = hexdig_value(*it);
     401           60 :         if (b != -1 && d < 0)
     402              :         {
     403              :             // ends in "::"
     404            0 :             break;
     405              :         }
     406              : 
     407           60 :         if (!c)
     408              :         {
     409           59 :             prev = it;
     410           59 :             if (!parse_h16(it, end, hi, lo))
     411            2 :                 return std::make_error_code(std::errc::invalid_argument);
     412           57 :             bytes[2 * (8 - n) + 0] = hi;
     413           57 :             bytes[2 * (8 - n) + 1] = lo;
     414           57 :             --n;
     415           57 :             if (n == 0)
     416            1 :                 break;
     417           56 :             c = true;
     418           56 :             continue;
     419              :         }
     420              : 
     421              :         // ':' divides a word
     422            1 :         return std::make_error_code(std::errc::invalid_argument);
     423          153 :     }
     424              : 
     425              :     // Must have consumed entire string
     426           38 :     if (it != end)
     427            2 :         return std::make_error_code(std::errc::invalid_argument);
     428              : 
     429           36 :     if (b == -1)
     430              :     {
     431            1 :         addr = ipv6_address{bytes};
     432            1 :         return {};
     433              :     }
     434              : 
     435           35 :     if (b == n)
     436              :     {
     437              :         // "::" last
     438            2 :         auto const i = 2 * (7 - n);
     439            2 :         std::memset(&bytes[i], 0, 16 - i);
     440              :     }
     441           33 :     else if (b == 7)
     442              :     {
     443              :         // "::" first
     444           19 :         auto const i = 2 * (b - n);
     445           19 :         std::memmove(&bytes[16 - i], &bytes[2], i);
     446           19 :         std::memset(&bytes[0], 0, 16 - i);
     447              :     }
     448              :     else
     449              :     {
     450              :         // "::" in middle
     451           14 :         auto const i0 = 2 * (7 - b);
     452           14 :         auto const i1 = 2 * (b - n);
     453           14 :         std::memmove(&bytes[16 - i1], &bytes[i0 + 2], i1);
     454           14 :         std::memset(&bytes[i0], 0, 16 - (i0 + i1));
     455              :     }
     456              : 
     457           35 :     addr = ipv6_address{bytes};
     458           35 :     return {};
     459              : }
     460              : 
     461              : } // namespace boost::corosio
        

Generated by: LCOV version 2.3