Botan 3.12.0
Crypto and TLS for C&
ipv6_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/ipv6_address.h>
8
9#include <botan/ipv4_address.h>
10#include <botan/internal/fmt.h>
11#include <botan/internal/loadstor.h>
12#include <botan/internal/parsing.h>
13#include <bit>
14
15namespace Botan {
16
17IPv6Address::IPv6Address(std::span<const uint8_t, 16> ip) : m_ip{} {
18 for(size_t i = 0; i != 16; ++i) {
19 m_ip[i] = ip[i];
20 }
21}
22
23//static
24std::optional<IPv6Address> IPv6Address::from_string(std::string_view str) {
25 if(auto ipv6 = string_to_ipv6(str)) {
26 return IPv6Address(*ipv6);
27 } else {
28 return {};
29 }
30}
31
32//static
34 BOTAN_ARG_CHECK(bits <= 128, "IPv6 netmask prefix length must be at most 128");
35
36 const size_t full_bytes = bits / 8;
37 const size_t leftover = bits % 8;
38
39 std::array<uint8_t, 16> m{};
40 for(size_t i = 0; i != full_bytes; ++i) {
41 m[i] = 0xFF;
42 }
43
44 if(leftover > 0) {
45 m[full_bytes] = static_cast<uint8_t>(0xFF << (8 - leftover));
46 }
47
48 return IPv6Address(m);
49}
50
51std::string IPv6Address::to_string() const {
52 return ipv6_to_string(m_ip);
53}
54
56 std::array<uint8_t, 16> masked{};
57 for(size_t i = 0; i != 16; ++i) {
58 masked[i] = m_ip[i] & other.m_ip[i];
59 }
60 return IPv6Address(masked);
61}
62
63std::optional<size_t> IPv6Address::prefix_length() const {
64 // Count leading one bits, stopping at the first byte that isn't fully set.
65 size_t leading = 0;
66 for(size_t i = 0; i != 16; ++i) {
67 const size_t hw = (m_ip[i] == 0xFF) ? 8 : std::countl_one(m_ip[i]);
68 leading += hw;
69 if(hw != 8) {
70 break;
71 }
72 }
73
74 // Verify this is exactly equal to a netmask of that size
75 if(*this != netmask(leading)) {
76 return std::nullopt;
77 }
78 return leading;
79}
80
81std::optional<IPv4Address> IPv6Address::as_ipv4() const {
82 const uint32_t ip0 = load_be<uint32_t>(m_ip.data(), 0);
83 const uint32_t ip1 = load_be<uint32_t>(m_ip.data(), 1);
84 const uint32_t ip2 = load_be<uint32_t>(m_ip.data(), 2);
85 const uint32_t ip3 = load_be<uint32_t>(m_ip.data(), 3);
86
87 if(ip0 == 0x00000000 && ip1 == 0x00000000 && (ip2 == 0x00000000 || ip2 == 0x0000FFFF)) {
88 return IPv4Address(ip3);
89 } else {
90 return {};
91 }
92}
93
95 m_address(address & IPv6Address::netmask(prefix_length)), m_prefix_length(static_cast<uint8_t>(prefix_length)) {
96 // IPv6Address::netmask validates prefix_length <= 128, so by this point
97 // the static_cast is in range.
98}
99
100//static
101std::optional<IPv6Subnet> IPv6Subnet::from_address_and_mask(std::span<const uint8_t, 32> addr_and_mask) {
102 const auto addr = IPv6Address(addr_and_mask.first<16>());
103 const auto mask = IPv6Address(addr_and_mask.last<16>());
104
105 if(const auto plen = mask.prefix_length()) {
106 return IPv6Subnet(addr, *plen);
107 } else {
108 return {};
109 }
110}
111
112//static
113std::optional<IPv6Subnet> IPv6Subnet::from_string(std::string_view str) {
114 const auto slash = str.find('/');
115 if(slash == std::string_view::npos) {
116 return std::nullopt;
117 }
118
119 auto addr = IPv6Address::from_string(str.substr(0, slash));
120 if(!addr.has_value()) {
121 return std::nullopt;
122 }
123
124 // Parse the prefix length as a decimal integer in [0, 128].
125 const auto plen_str = str.substr(slash + 1);
126 if(plen_str.empty() || plen_str.size() > 3) {
127 return std::nullopt;
128 }
129 size_t plen = 0;
130 for(const char c : plen_str) {
131 if(c < '0' || c > '9') {
132 return std::nullopt;
133 }
134 plen = plen * 10 + static_cast<size_t>(c - '0');
135 }
136 if(plen > 128) {
137 return std::nullopt;
138 }
139
140 return IPv6Subnet(*addr, plen);
141}
142
143bool IPv6Subnet::contains(const IPv6Address& ip) const {
144 return (ip & IPv6Address::netmask(m_prefix_length)) == m_address;
145}
146
147std::string IPv6Subnet::to_string() const {
148 return fmt("{}/{}", m_address.to_string(), static_cast<size_t>(m_prefix_length));
149}
150
151std::vector<uint8_t> IPv6Subnet::serialize() const {
152 const auto addr = m_address.address();
153 if(is_host()) {
154 return std::vector<uint8_t>(addr.begin(), addr.end());
155 }
156 const auto mask = IPv6Address::netmask(m_prefix_length).address();
157 std::vector<uint8_t> out;
158 out.reserve(32);
159 out.insert(out.end(), addr.begin(), addr.end());
160 out.insert(out.end(), mask.begin(), mask.end());
161 return out;
162}
163
164} // namespace Botan
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
std::optional< IPv4Address > as_ipv4() const
IPv6Address(std::span< const uint8_t, 16 > ip)
std::string to_string() const
static IPv6Address netmask(size_t bits)
static std::optional< IPv6Address > from_string(std::string_view str)
IPv6Address operator&(const IPv6Address &other) const
std::optional< size_t > prefix_length() const
std::span< const uint8_t, 16 > address() const
bool contains(const IPv6Address &ip) const
True iff ip falls within this subnet.
size_t prefix_length() const
Prefix length in [0, 128].
std::string to_string() const
CIDR-style "2001:db8::/32".
static std::optional< IPv6Subnet > from_address_and_mask(std::span< const uint8_t, 32 > addr_and_mask)
const IPv6Address & address() const
The network address (host bits already zeroed).
static std::optional< IPv6Subnet > from_string(std::string_view str)
IPv6Subnet(IPv6Address address, size_t prefix_length)
bool is_host() const
True iff prefix_length() == 128.
std::vector< uint8_t > serialize() const
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
std::optional< std::array< uint8_t, 16 > > string_to_ipv6(std::string_view str)
Definition parsing.cpp:221
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504
std::string ipv6_to_string(std::span< const uint8_t, 16 > a)
Definition parsing.cpp:334