VIPA 0.1.0
Header-only C++23 SIMD-assisted IPv4/IPv6 text parser.
Loading...
Searching...
No Matches
ipv6.hxx
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <array>
5#include <bit>
6#include <cstdint>
7#include <optional>
8#include <string_view>
9
10#include "address.hxx"
11#include "detail/classify.hxx"
12#include "detail/simd.hxx"
13#include "ipv4.hxx"
14
15namespace llmx::vipa {
16
17namespace detail {
18
20struct IPv6Scan {
22 uint64_t colon_mask{};
24 uint64_t dot_mask{};
25};
26
28inline constexpr auto low_mask64(std::size_t len) noexcept -> uint64_t {
29 return len == 64 ? 0xFFFF'FFFF'FFFF'FFFFull : ((uint64_t{1} << len) - 1u);
30}
31
33inline constexpr auto low_mask32(std::size_t len) noexcept -> uint32_t {
34 return len == 32 ? 0xFFFF'FFFFu : ((uint32_t{1} << len) - 1u);
35}
36
38inline constexpr auto range_mask64(std::size_t begin, std::size_t end) noexcept
39 -> uint64_t {
40 if (begin >= end)
41 return 0;
42 return low_mask64(end) & ~low_mask64(begin);
43}
44
45#if VIPA_USE_AVX2
47inline auto classify_ipv6_avx2(__m256i chunk, uint32_t text_mask,
48 uint32_t& colon_mask,
49 uint32_t& dot_mask) noexcept -> bool;
50
52inline auto scan_ipv6_half_avx2(const char* bytes, uint32_t text_mask,
53 uint32_t& colon_mask,
54 uint32_t& dot_mask) noexcept -> bool {
55 const auto chunk =
56 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes));
57 return classify_ipv6_avx2(chunk, text_mask, colon_mask, dot_mask);
58}
59
62inline auto classify_ipv6_avx2(__m256i chunk, uint32_t text_mask,
63 uint32_t& colon_mask,
64 uint32_t& dot_mask) noexcept -> bool {
65 const auto colon = _mm256_set1_epi8(':');
66 const auto dot = _mm256_set1_epi8('.');
67 const auto slash = _mm256_set1_epi8(static_cast<char>('0' - 1));
68 const auto colon_after_nine = _mm256_set1_epi8(static_cast<char>('9' + 1));
69 const auto before_upper_a = _mm256_set1_epi8(static_cast<char>('A' - 1));
70 const auto after_upper_f = _mm256_set1_epi8(static_cast<char>('F' + 1));
71 const auto before_lower_a = _mm256_set1_epi8(static_cast<char>('a' - 1));
72 const auto after_lower_f = _mm256_set1_epi8(static_cast<char>('f' + 1));
73
74 const auto is_colon = _mm256_cmpeq_epi8(chunk, colon);
75 const auto is_dot = _mm256_cmpeq_epi8(chunk, dot);
76 const auto is_digit =
77 _mm256_and_si256(_mm256_cmpgt_epi8(chunk, slash),
78 _mm256_cmpgt_epi8(colon_after_nine, chunk));
79 const auto is_upper =
80 _mm256_and_si256(_mm256_cmpgt_epi8(chunk, before_upper_a),
81 _mm256_cmpgt_epi8(after_upper_f, chunk));
82 const auto is_lower =
83 _mm256_and_si256(_mm256_cmpgt_epi8(chunk, before_lower_a),
84 _mm256_cmpgt_epi8(after_lower_f, chunk));
85 const auto is_hex =
86 _mm256_or_si256(is_digit, _mm256_or_si256(is_upper, is_lower));
87 const auto valid = _mm256_or_si256(is_hex, _mm256_or_si256(is_colon, is_dot));
88
89 colon_mask =
90 static_cast<uint32_t>(_mm256_movemask_epi8(is_colon)) & text_mask;
91 dot_mask = static_cast<uint32_t>(_mm256_movemask_epi8(is_dot)) & text_mask;
92 const auto valid_mask =
93 static_cast<uint32_t>(_mm256_movemask_epi8(valid)) & text_mask;
94 return valid_mask == text_mask;
95}
96
98inline auto scan_ipv6_tail_scalar(const char* bytes, std::size_t begin,
99 std::size_t end, std::size_t offset,
100 IPv6Scan& scan) noexcept -> bool {
101 for (std::size_t i = begin; i < end; ++i) {
102 const char c = bytes[i];
103 const auto bit = uint64_t{1} << (offset + i);
104 if (c == ':') {
105 scan.colon_mask |= bit;
106 continue;
107 }
108 if (c == '.') {
109 scan.dot_mask |= bit;
110 continue;
111 }
112 if (!is_hex_digit(c))
113 return false;
114 }
115 return true;
116}
117
124inline auto scan_ipv6_segment_avx2(const char* bytes, std::size_t len,
125 std::size_t offset, IPv6Scan& scan) noexcept
126 -> bool {
127 const std::size_t full_words = len / 4u;
128 const std::size_t full_bytes = full_words * 4u;
129 if (full_words != 0) {
130 const auto load_mask =
131 _mm256_setr_epi32(full_words > 0 ? -1 : 0, full_words > 1 ? -1 : 0,
132 full_words > 2 ? -1 : 0, full_words > 3 ? -1 : 0,
133 full_words > 4 ? -1 : 0, full_words > 5 ? -1 : 0,
134 full_words > 6 ? -1 : 0, full_words > 7 ? -1 : 0);
135 const auto chunk =
136 _mm256_maskload_epi32(reinterpret_cast<const int*>(bytes), load_mask);
137
138 uint32_t colon_mask = 0;
139 uint32_t dot_mask = 0;
140 const bool valid =
141 classify_ipv6_avx2(chunk, low_mask32(full_bytes), colon_mask, dot_mask);
142 scan.colon_mask |= static_cast<uint64_t>(colon_mask) << offset;
143 scan.dot_mask |= static_cast<uint64_t>(dot_mask) << offset;
144 if (!valid)
145 return false;
146 }
147
148 return scan_ipv6_tail_scalar(bytes, full_bytes, len, offset, scan);
149}
150
153inline auto scan_ipv6_avx2(std::string_view text, IPv6Scan& scan) noexcept
154 -> bool {
155 uint32_t colon_lo = 0;
156 uint32_t dot_lo = 0;
157 if (text.size() >= 32) {
158 if (!scan_ipv6_half_avx2(text.data(), 0xFFFF'FFFFu, colon_lo, dot_lo))
159 return false;
160 scan.colon_mask = colon_lo;
161 scan.dot_mask = dot_lo;
162 return scan_ipv6_segment_avx2(text.data() + 32, text.size() - 32, 32, scan);
163 }
164
165 return scan_ipv6_segment_avx2(text.data(), text.size(), 0, scan);
166}
167#endif
168
170inline auto scan_ipv6_scalar(std::string_view text, IPv6Scan& scan) noexcept
171 -> bool {
172 for (std::size_t i = 0; i < text.size(); ++i) {
173 const auto bit = uint64_t{1} << i;
174 const char c = text[i];
175 if (c == ':') {
176 scan.colon_mask |= bit;
177 continue;
178 }
179 if (c == '.') {
180 scan.dot_mask |= bit;
181 continue;
182 }
183 if (!is_hex_digit(c))
184 return false;
185 }
186 return true;
187}
188
190inline auto scan_ipv6(std::string_view text, IPv6Scan& scan) noexcept -> bool {
191#if VIPA_USE_AVX2
192 return scan_ipv6_avx2(text, scan);
193#else
194 return scan_ipv6_scalar(text, scan);
195#endif
196}
197
199inline bool parse_ipv6_piece(std::string_view piece, uint16_t& value) noexcept {
200 if (piece.empty() || piece.size() > 4)
201 return false;
202
203 uint16_t parsed = 0;
204 for (char c : piece) {
205 if (!is_hex_digit(c))
206 return false;
207 parsed = static_cast<uint16_t>((parsed << 4u) | hex_value(c));
208 }
209
210 value = parsed;
211 return true;
212}
213
215inline auto find_next_colon(IPv6Scan scan, std::size_t begin,
216 std::size_t end) noexcept -> std::size_t {
217 const auto mask = scan.colon_mask & range_mask64(begin, end);
218 if (mask == 0)
219 return end;
220 return static_cast<std::size_t>(std::countr_zero(mask));
221}
222
224inline auto append_ipv4_tail(std::array<uint16_t, 8>& groups, uint8_t& count,
225 std::string_view tail) noexcept -> bool {
226 const auto ipv4 = parse_ipv4(tail);
227 if (!ipv4 || count > 6)
228 return false;
229
230 groups[count++] =
231 static_cast<uint16_t>((ipv4->bytes[0] << 8u) | ipv4->bytes[1]);
232 groups[count++] =
233 static_cast<uint16_t>((ipv4->bytes[2] << 8u) | ipv4->bytes[3]);
234 return true;
235}
236
243inline auto parse_ipv6_side(std::string_view text, std::size_t begin,
244 std::size_t end, IPv6Scan scan,
245 std::array<uint16_t, 8>& groups,
246 uint8_t& count) noexcept -> bool {
247 while (begin < end) {
248 const auto next = find_next_colon(scan, begin, end);
249 const auto piece = text.substr(begin, next - begin);
250 const bool contains_dot = (scan.dot_mask & range_mask64(begin, next)) != 0;
251 if (contains_dot) {
252 if (next != end)
253 return false;
254 return append_ipv4_tail(groups, count, piece);
255 }
256
257 uint16_t value = 0;
258 if (!parse_ipv6_piece(piece, value) || count >= 8)
259 return false;
260 groups[count++] = value;
261
262 if (next == end)
263 return true;
264 begin = next + 1;
265 if (begin == end)
266 return false;
267 }
268 return true;
269}
270
271} // namespace detail
272
283[[nodiscard]] inline auto parse_ipv6(std::string_view text) noexcept
284 -> std::optional<IPv6Address> {
285 if (text.empty() || text.size() > 45) {
286 return std::nullopt;
287 }
288
289 detail::IPv6Scan scan{};
290 if (!detail::scan_ipv6(text, scan))
291 return std::nullopt;
292
293 std::array<uint16_t, 8> head{};
294 std::array<uint16_t, 8> tail{};
295 uint8_t head_count = 0;
296 uint8_t tail_count = 0;
297 std::optional<std::size_t> compress_at{};
298
299 const auto double_colon_mask = scan.colon_mask & (scan.colon_mask >> 1u);
300 if (double_colon_mask != 0) {
301 if (std::popcount(double_colon_mask) != 1)
302 return std::nullopt;
303
304 const auto double_colon =
305 static_cast<std::size_t>(std::countr_zero(double_colon_mask));
306 compress_at = double_colon;
307 if ((double_colon != 0 &&
308 !detail::parse_ipv6_side(text, 0, double_colon, scan, head,
309 head_count)) ||
310 (double_colon + 2 < text.size() &&
311 !detail::parse_ipv6_side(text, double_colon + 2, text.size(), scan,
312 tail, tail_count))) {
313 return std::nullopt;
314 }
315 if (head_count + tail_count >= 8)
316 return std::nullopt;
317 } else {
318 if (!detail::parse_ipv6_side(text, 0, text.size(), scan, head,
319 head_count) ||
320 head_count != 8)
321 return std::nullopt;
322 }
323
324 std::array<uint16_t, 8> groups{};
325 auto it = groups.begin();
326 it = std::copy(head.begin(), head.begin() + head_count, it);
327
328 if (compress_at) {
329 const auto zeros = 8 - head_count - tail_count;
330 it = std::fill_n(it, zeros, uint16_t{0});
331 it = std::copy(tail.begin(), tail.begin() + tail_count, it);
332 }
333
334 IPv6Address result{};
335 for (std::size_t i = 0; i < groups.size(); ++i) {
336 result.bytes[i * 2] = static_cast<uint8_t>(groups[i] >> 8u);
337 result.bytes[i * 2 + 1] = static_cast<uint8_t>(groups[i] & 0xFFu);
338 }
339
340 return result;
341}
342
343} // namespace llmx::vipa
Definition classify.hxx:5
auto parse_ipv6_side(std::string_view text, std::size_t begin, std::size_t end, IPv6Scan scan, std::array< uint16_t, 8 > &groups, uint8_t &count) noexcept -> bool
Parse one side of an IPv6 address around optional :: compression.
Definition ipv6.hxx:243
constexpr bool is_hex_digit(char c) noexcept
Return true when c is an ASCII hexadecimal digit.
Definition classify.hxx:8
auto append_ipv4_tail(std::array< uint16_t, 8 > &groups, uint8_t &count, std::string_view tail) noexcept -> bool
Append an embedded IPv4 tail as two IPv6 16-bit groups.
Definition ipv6.hxx:224
auto find_next_colon(IPv6Scan scan, std::size_t begin, std::size_t end) noexcept -> std::size_t
Find the next colon bit in [begin, end).
Definition ipv6.hxx:215
auto scan_ipv6(std::string_view text, IPv6Scan &scan) noexcept -> bool
Scan IPv6 text with the best enabled implementation.
Definition ipv6.hxx:190
auto scan_ipv6_scalar(std::string_view text, IPv6Scan &scan) noexcept -> bool
Scan IPv6 text with portable scalar character classification.
Definition ipv6.hxx:170
constexpr auto range_mask64(std::size_t begin, std::size_t end) noexcept -> uint64_t
Return a mask covering [begin, end) bit positions.
Definition ipv6.hxx:38
bool parse_ipv6_piece(std::string_view piece, uint16_t &value) noexcept
Parse one 1-4 digit IPv6 hexadecimal group.
Definition ipv6.hxx:199
constexpr auto hex_value(char c) noexcept -> uint8_t
Convert an ASCII hexadecimal digit to its numeric value.
Definition classify.hxx:14
constexpr auto low_mask64(std::size_t len) noexcept -> uint64_t
Return a low-bit mask with len bits set.
Definition ipv6.hxx:28
constexpr auto low_mask32(std::size_t len) noexcept -> uint32_t
Return a 32-bit low-bit mask with len bits set.
Definition ipv6.hxx:33
Definition address.hxx:8
auto parse_ipv4(std::string_view text) noexcept -> std::optional< IPv4Address >
Parse strict dotted-decimal IPv4 text.
Definition ipv4.hxx:129
auto parse_ipv6(std::string_view text) noexcept -> std::optional< IPv6Address >
Parse IPv6 text into sixteen network-order bytes.
Definition ipv6.hxx:283
SIMD feature detection and include for VIPA.
Parsed IPv6 address in network byte order.
Definition address.hxx:26
std::array< uint8_t, 16 > bytes
Definition address.hxx:29
Character-position masks collected while scanning IPv6 text.
Definition ipv6.hxx:20
uint64_t dot_mask
Definition ipv6.hxx:24
uint64_t colon_mask
Definition ipv6.hxx:22