src/corosio/src/ipv4_address.cpp

99.0% Lines (98/99) 100.0% Functions (15/15)
src/corosio/src/ipv4_address.cpp
Line Hits Source Code
1 //
2 // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.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/ipv4_address.hpp>
11
12 #include <ostream>
13 #include <stdexcept>
14
15 namespace boost::corosio {
16
17 8501 ipv4_address::ipv4_address(uint_type u) noexcept : addr_(u) {}
18
19 20477 ipv4_address::ipv4_address(bytes_type const& bytes) noexcept
20 {
21 20477 addr_ = (static_cast<std::uint32_t>(bytes[0]) << 24) |
22 20477 (static_cast<std::uint32_t>(bytes[1]) << 16) |
23 20477 (static_cast<std::uint32_t>(bytes[2]) << 8) |
24 20477 (static_cast<std::uint32_t>(bytes[3]));
25 20477 }
26
27 4 ipv4_address::ipv4_address(std::string_view s)
28 {
29 4 auto ec = parse_ipv4_address(s, *this);
30 4 if (ec)
31 3 throw std::invalid_argument("invalid IPv4 address");
32 1 }
33
34 auto
35 8500 ipv4_address::to_bytes() const noexcept -> bytes_type
36 {
37 bytes_type bytes;
38 8500 bytes[0] = static_cast<unsigned char>((addr_ >> 24) & 0xff);
39 8500 bytes[1] = static_cast<unsigned char>((addr_ >> 16) & 0xff);
40 8500 bytes[2] = static_cast<unsigned char>((addr_ >> 8) & 0xff);
41 8500 bytes[3] = static_cast<unsigned char>(addr_ & 0xff);
42 8500 return bytes;
43 }
44
45 auto
46 10 ipv4_address::to_uint() const noexcept -> uint_type
47 {
48 10 return addr_;
49 }
50
51 std::string
52 11 ipv4_address::to_string() const
53 {
54 char buf[max_str_len];
55 11 auto n = print_impl(buf);
56 22 return std::string(buf, n);
57 }
58
59 std::string_view
60 4 ipv4_address::to_buffer(char* dest, std::size_t dest_size) const
61 {
62 4 if (dest_size < max_str_len)
63 throw std::length_error("buffer too small for IPv4 address");
64 4 auto n = print_impl(dest);
65 4 return std::string_view(dest, n);
66 }
67
68 bool
69 5 ipv4_address::is_loopback() const noexcept
70 {
71 5 return (addr_ & 0xFF000000) == 0x7F000000;
72 }
73
74 bool
75 4 ipv4_address::is_unspecified() const noexcept
76 {
77 4 return addr_ == 0;
78 }
79
80 bool
81 4 ipv4_address::is_multicast() const noexcept
82 {
83 4 return (addr_ & 0xF0000000) == 0xE0000000;
84 }
85
86 std::ostream&
87 1 operator<<(std::ostream& os, ipv4_address const& addr)
88 {
89 char buf[ipv4_address::max_str_len];
90 1 os << addr.to_buffer(buf, sizeof(buf));
91 1 return os;
92 }
93
94 std::size_t
95 15 ipv4_address::print_impl(char* dest) const noexcept
96 {
97 15 auto const start = dest;
98 60 auto const write = [](char*& dest, unsigned char v) {
99 60 if (v >= 100)
100 {
101 19 *dest++ = '0' + v / 100;
102 19 v %= 100;
103 19 *dest++ = '0' + v / 10;
104 19 v %= 10;
105 }
106 41 else if (v >= 10)
107 {
108 5 *dest++ = '0' + v / 10;
109 5 v %= 10;
110 }
111 60 *dest++ = '0' + v;
112 60 };
113 15 write(dest, static_cast<unsigned char>((addr_ >> 24) & 0xff));
114 15 *dest++ = '.';
115 15 write(dest, static_cast<unsigned char>((addr_ >> 16) & 0xff));
116 15 *dest++ = '.';
117 15 write(dest, static_cast<unsigned char>((addr_ >> 8) & 0xff));
118 15 *dest++ = '.';
119 15 write(dest, static_cast<unsigned char>(addr_ & 0xff));
120 15 return static_cast<std::size_t>(dest - start);
121 }
122
123
124 namespace {
125
126 // Parse a decimal octet (0-255), no leading zeros except "0"
127 // Returns true on success, advances `it`
128 bool
129 193 parse_dec_octet(char const*& it, char const* end, unsigned char& octet) noexcept
130 {
131 193 if (it == end)
132 2 return false;
133
134 191 char c = *it;
135 191 if (c < '0' || c > '9')
136 6 return false;
137
138 185 unsigned v = static_cast<unsigned>(c - '0');
139 185 ++it;
140
141 185 if (v == 0)
142 {
143 // "0" is valid, but "00", "01", etc. are not
144 29 if (it != end && *it >= '0' && *it <= '9')
145 3 return false;
146 26 octet = 0;
147 26 return true;
148 }
149
150 // First digit was 1-9, can have more
151 156 if (it != end && *it >= '0' && *it <= '9')
152 {
153 42 v = v * 10 + static_cast<unsigned>(*it - '0');
154 42 ++it;
155
156 42 if (it != end && *it >= '0' && *it <= '9')
157 {
158 39 v = v * 10 + static_cast<unsigned>(*it - '0');
159 39 ++it;
160
161 // Can't have more than 3 digits
162 39 if (it != end && *it >= '0' && *it <= '9')
163 1 return false;
164 }
165 }
166
167 155 if (v > 255)
168 7 return false;
169
170 148 octet = static_cast<unsigned char>(v);
171 148 return true;
172 }
173
174 } // namespace
175
176 std::error_code
177 62 parse_ipv4_address(std::string_view s, ipv4_address& addr) noexcept
178 {
179 62 auto it = s.data();
180 62 auto const end = it + s.size();
181
182 unsigned char octets[4];
183
184 // Parse first octet
185 62 if (!parse_dec_octet(it, end, octets[0]))
186 13 return std::make_error_code(std::errc::invalid_argument);
187
188 // Parse remaining octets
189 174 for (int i = 1; i < 4; ++i)
190 {
191 137 if (it == end || *it != '.')
192 6 return std::make_error_code(std::errc::invalid_argument);
193 131 ++it; // skip '.'
194
195 131 if (!parse_dec_octet(it, end, octets[i]))
196 6 return std::make_error_code(std::errc::invalid_argument);
197 }
198
199 // Must have consumed entire string
200 37 if (it != end)
201 5 return std::make_error_code(std::errc::invalid_argument);
202
203 64 addr = ipv4_address(
204 32 ipv4_address::bytes_type{{octets[0], octets[1], octets[2], octets[3]}});
205
206 32 return {};
207 }
208
209 } // namespace boost::corosio
210