Botan 3.13.0
Crypto and TLS for C&
ipv4_address.cpp
Go to the documentation of this file.
1/*
2* (C) 2026 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#include <botan/ipv4_address.h>
8
9#include <botan/internal/fmt.h>
10#include <botan/internal/loadstor.h>
11#include <botan/internal/parsing.h>
12#include <bit>
13
14namespace Botan {
15
16namespace {
17
18/*
19* Convert a decimal-dotted string to binary IP
20*/
21std::optional<uint32_t> string_to_ipv4(std::string_view str) {
22 // At least 3 dots + 4 1-digit integers
23 // At most 3 dots + 4 3-digit integers
24 if(str.size() < 3 + 4 * 1 || str.size() > 3 + 4 * 3) {
25 return {};
26 }
27
28 // the final result
29 uint32_t ip = 0;
30 // the number of '.' seen so far
31 size_t dots = 0;
32 // accumulates one quad (range 0-255)
33 uint32_t accum = 0;
34 // # of digits pushed to accum since last dot
35 size_t cur_digits = 0;
36
37 for(const char c : str) {
38 if(c == '.') {
39 // . without preceding digit is invalid
40 if(cur_digits == 0) {
41 return {};
42 }
43 dots += 1;
44 // too many dots
45 if(dots > 3) {
46 return {};
47 }
48
49 cur_digits = 0;
50 ip = (ip << 8) | accum;
51 accum = 0;
52 } else if(c >= '0' && c <= '9') {
53 const auto d = static_cast<uint8_t>(c - '0');
54
55 // prohibit leading zero in quad (used for octal)
56 if(cur_digits > 0 && accum == 0) {
57 return {};
58 }
59 accum = (accum * 10) + d;
60
61 if(accum > 255) {
62 return {};
63 }
64
65 cur_digits++;
66 BOTAN_ASSERT_NOMSG(cur_digits <= 3);
67 } else {
68 return {};
69 }
70 }
71
72 // no trailing digits?
73 if(cur_digits == 0) {
74 return {};
75 }
76
77 // insufficient # of dots
78 if(dots != 3) {
79 return {};
80 }
81
82 ip = (ip << 8) | accum;
83
84 return ip;
85}
86
87} // namespace
88
89//static
90std::optional<IPv4Address> IPv4Address::from_string(std::string_view str) {
91 if(auto ipv4 = string_to_ipv4(str)) {
92 return IPv4Address(*ipv4);
93 } else {
94 return {};
95 }
96}
97
98//static
100 BOTAN_ARG_CHECK(bits <= 32, "IPv4 netmask prefix length must be at most 32");
101 if(bits == 0) {
102 return IPv4Address(0);
103 }
104 return IPv4Address(0xFFFFFFFF << (32 - bits));
105}
106
107std::array<uint8_t, 4> IPv4Address::to_bytes() const {
108 std::array<uint8_t, 4> out{};
109 store_be(m_ip, out);
110 return out;
111}
112
113std::string IPv4Address::to_string() const {
114 const auto addr = this->to_bytes();
115
116 std::string str;
117 str.reserve(15); // maximum possible size
118
119 for(size_t i = 0; i != 4; ++i) {
120 if(i > 0) {
121 str += ".";
122 }
123 str += std::to_string(addr[i]);
124 }
125
126 return str;
127}
128
129std::optional<size_t> IPv4Address::prefix_length() const {
130 // A 32-bit mask m is a CIDR prefix iff (~m) + 1 is a power of two or zero,
131 // i.e. (~m) & (~m + 1) == 0. If so, the prefix length is the leading-one count.
132 const uint32_t inv = ~m_ip;
133 if((inv & (inv + 1)) != 0) {
134 return std::nullopt;
135 }
136 return std::countl_one(m_ip);
137}
138
140 m_address(address & IPv4Address::netmask(prefix_length)), m_prefix_length(static_cast<uint8_t>(prefix_length)) {
141 // IPv4Address::netmask validates prefix_length <= 32, so by this point
142 // the static_cast is in range.
143}
144
145//static
146std::optional<IPv4Subnet> IPv4Subnet::from_address_and_mask(uint32_t addr, uint32_t mask) {
147 std::array<uint8_t, 8> addr_and_mask{};
148 store_be(&addr_and_mask[0], addr); // NOLINT(*-container-data-pointer)
149 store_be(&addr_and_mask[4], mask);
150 return IPv4Subnet::from_address_and_mask(addr_and_mask);
151}
152
153//static
154std::optional<IPv4Subnet> IPv4Subnet::from_address_and_mask(std::span<const uint8_t, 8> addr_and_mask) {
155 const IPv4Address addr(load_be<uint32_t>(addr_and_mask.data(), 0));
156 const IPv4Address mask(load_be<uint32_t>(addr_and_mask.data(), 1));
157
158 if(const auto plen = mask.prefix_length()) {
159 return IPv4Subnet(addr, *plen);
160 } else {
161 return {};
162 }
163}
164
165//static
166std::optional<IPv4Subnet> IPv4Subnet::from_string(std::string_view str) {
167 const auto slash = str.find('/');
168 if(slash == std::string_view::npos) {
169 return std::nullopt;
170 }
171
172 auto addr = IPv4Address::from_string(str.substr(0, slash));
173 if(!addr.has_value()) {
174 return std::nullopt;
175 }
176
177 // Parse the prefix length as a canonical decimal integer in [0, 32]
178 const auto plen_str = str.substr(slash + 1);
179
180 const auto plen = parse_sz(plen_str, /*require_canonical=*/true);
181
182 if(!plen.has_value() || plen.value() > 32) {
183 return std::nullopt;
184 }
185
186 const IPv4Subnet subnet(*addr, plen.value());
187
188 // Require the input to already be canonical: from_string and to_string
189 // are exact inverses, so a set host bit is rejected rather than masked away
190 if(subnet.to_string() != str) {
191 return std::nullopt;
192 }
193
194 return subnet;
195}
196
197bool IPv4Subnet::contains(const IPv4Address& ip) const {
198 return (ip & IPv4Address::netmask(m_prefix_length)) == m_address;
199}
200
201std::string IPv4Subnet::to_string() const {
202 return fmt("{}/{}", m_address.to_string(), static_cast<size_t>(m_prefix_length));
203}
204
205std::vector<uint8_t> IPv4Subnet::serialize() const {
206 std::vector<uint8_t> out;
207 if(is_host()) {
208 out.resize(4);
209 store_be(m_address.address(), out.data());
210 return out;
211 }
212 out.resize(8);
213 store_be(m_address.address(), out.data());
214 store_be(IPv4Address::netmask(m_prefix_length).address(), out.data() + 4);
215 return out;
216}
217
218} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static std::optional< IPv4Address > from_string(std::string_view str)
std::optional< size_t > prefix_length() const
IPv4Address(uint32_t ip)
std::string to_string() const
Dotted-decimal form, e.g. "10.0.0.1".
std::array< uint8_t, 4 > to_bytes() const
The address as four bytes, network-byte-order.
static IPv4Address netmask(size_t bits)
static std::optional< IPv4Subnet > from_address_and_mask(std::span< const uint8_t, 8 > addr_and_mask)
std::vector< uint8_t > serialize() const
bool contains(const IPv4Address &ip) const
True iff ip falls within this subnet.
bool is_host() const
True iff prefix_length() == 32.
IPv4Subnet(IPv4Address address, size_t prefix_length)
size_t prefix_length() const
Prefix length in [0, 32].
static std::optional< IPv4Subnet > from_string(std::string_view str)
std::string to_string() const
CIDR-style "10.0.0.0/8".
const IPv4Address & address() const
The network address (host bits already zeroed).
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
std::optional< size_t > parse_sz(std::string_view input, bool require_canonical)
Definition parsing.cpp:72
constexpr auto store_be(ParamTs &&... params)
Definition loadstor.h:745
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504