1
2
3
4 package de.powerstat.validation.values;
5
6
7 import java.util.Locale;
8 import java.util.Objects;
9 import java.util.regex.Pattern;
10
11 import de.powerstat.validation.interfaces.IValueObject;
12
13
14
15
16
17
18
19
20
21 public final class IPV6Address implements Comparable<IPV6Address>, IValueObject
22 {
23
24
25
26
27
28
29
30
31
32
33
34
35
36 private static final Pattern IPV6_REGEXP = Pattern.compile("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$");
37
38
39
40
41 private static final String BLOCK_ZERO = "0000";
42
43
44
45
46 private static final String HEX_OUTPUT = "%02x";
47
48
49
50
51 private static final String IPV6_EXP = "::";
52
53
54
55
56 private static final String IV6_SEP = ":";
57
58
59
60
61 private final String address;
62
63
64
65
66 private final String[] blocks;
67
68
69
70
71
72
73
74
75
76 private IPV6Address(final String address)
77 {
78 super();
79 Objects.requireNonNull(address, "address");
80 if ((address.length() < 2) || (address.length() > 45))
81 {
82 throw new IllegalArgumentException("To short or long for an IP V6 address");
83 }
84 String expandedAddress = address.toLowerCase(Locale.getDefault());
85 expandedAddress = expandIPV4Address(expandedAddress);
86 expandedAddress = expandExpansionBlock(expandedAddress);
87 if (!IPV6Address.IPV6_REGEXP.matcher(expandedAddress).matches())
88 {
89 throw new IllegalArgumentException("Not an IP V6 address");
90 }
91 expandedAddress = normalizeIPV6Address(expandedAddress);
92 this.address = expandedAddress;
93 this.blocks = expandedAddress.split(IPV6Address.IV6_SEP);
94 }
95
96
97
98
99
100
101
102
103
104
105 private static String expandIPV4Address(final String address)
106 {
107 final int ipv4pos = address.indexOf('.');
108 if (ipv4pos == -1)
109 {
110 return address;
111 }
112 final int blockStart = address.lastIndexOf(':', ipv4pos);
113 final var ipv4 = address.substring(blockStart + 1);
114 IPV4Address.of(ipv4);
115 final var newAddress = address.substring(0, blockStart + 1);
116 final String[] parts = ipv4.split("\\.");
117 final int block1 = Integer.parseInt(parts[0]);
118 final int block2 = Integer.parseInt(parts[1]);
119 final int block3 = Integer.parseInt(parts[2]);
120 final int block4 = Integer.parseInt(parts[3]);
121 return newAddress + Integer.toHexString(block1) + String.format(IPV6Address.HEX_OUTPUT, block2) + ':' + Integer.toHexString(block3) + String.format(IPV6Address.HEX_OUTPUT, block4);
122 }
123
124
125
126
127
128
129
130
131 private static int countColons(final String str)
132 {
133 int colons = 0;
134 int expPos = -1;
135 do
136 {
137 expPos = str.indexOf(':', expPos + 1);
138 if (expPos > -1)
139 {
140 ++colons;
141 }
142 }
143 while (expPos > -1);
144 return colons;
145 }
146
147
148
149
150
151
152
153
154 private static String expandExpansionBlock(final String address)
155 {
156 final int expPos = address.indexOf(IPV6Address.IPV6_EXP);
157 if ((expPos == -1))
158 {
159 return address;
160 }
161 if (address.indexOf(IPV6Address.IPV6_EXP, expPos + 1) != -1)
162 {
163 throw new IllegalArgumentException("Not an IP V6 address (more than one expansion block)");
164 }
165 final var start = address.substring(0, expPos);
166 final var end = address.substring(expPos + 2);
167 int blocks = 8;
168 if (start.length() > 0)
169 {
170 blocks -= countColons(start) + 1;
171 }
172 if (end.length() > 0)
173 {
174 blocks -= countColons(end) + 1;
175 }
176 final var replace = new StringBuilder();
177 if (start.length() > 0)
178 {
179 replace.append(':');
180 }
181 while (blocks > 0)
182 {
183 replace.append(IPV6Address.BLOCK_ZERO);
184 --blocks;
185 if (blocks > 0)
186 {
187 replace.append(':');
188 }
189 }
190 if (end.length() > 0)
191 {
192 replace.append(':');
193 }
194 replace.append(end);
195 replace.insert(0, start);
196 return replace.toString();
197 }
198
199
200
201
202
203
204
205
206 private static String normalizeIPV6Address(final String address)
207 {
208 final String[] parts = address.split(IPV6Address.IV6_SEP);
209 final var normalizedAddress = new StringBuilder();
210 for (final String part : parts)
211 {
212 normalizedAddress.append(IPV6Address.BLOCK_ZERO.substring(part.length())).append(part).append(':');
213 }
214 normalizedAddress.setLength(normalizedAddress.length() - 1);
215 return normalizedAddress.toString();
216 }
217
218
219
220
221
222
223
224
225 public static IPV6Address of(final String address)
226 {
227
228
229
230
231
232
233
234
235
236
237
238
239
240 return new IPV6Address(address);
241 }
242
243
244
245
246
247
248
249
250
251
252
253 @SuppressWarnings("java:S1313")
254 public boolean isPrivate()
255 {
256 return ("00fe:0080:0000:0000:0000:0000:0000:0000".equals(this.address) ||
257 "00fc".equals(this.blocks[0]) || "00fd".equals(this.blocks[0])
258 );
259 }
260
261
262
263
264
265
266
267
268
269
270
271 public boolean isSpecial()
272 {
273 return ("0000:0000:0000:0000:0000:0000:0000:0000".equals(this.address) || "0000:0000:0000:0000:0000:0000:0000:0001".equals(this.address) ||
274 "00ff".equals(this.blocks[0])
275 );
276 }
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 public boolean isPublic()
294 {
295 return !isPrivate() && !isSpecial();
296 }
297
298
299
300
301
302
303
304 @Override
305 public String stringValue()
306 {
307 return this.address;
308 }
309
310
311
312
313
314
315
316
317 @Override
318 public int hashCode()
319 {
320 return this.address.hashCode();
321 }
322
323
324
325
326
327
328
329
330
331 @Override
332 public boolean equals(final Object obj)
333 {
334 if (this == obj)
335 {
336 return true;
337 }
338 if (!(obj instanceof IPV6Address))
339 {
340 return false;
341 }
342 final IPV6Address other = (IPV6Address)obj;
343 return this.address.equals(other.address);
344 }
345
346
347
348
349
350
351
352
353
354
355
356
357 @Override
358 public String toString()
359 {
360 final var builder = new StringBuilder(21);
361 builder.append("IPV6Address[address=").append(this.address).append(']');
362 return builder.toString();
363 }
364
365
366
367
368
369
370
371
372
373 @Override
374 public int compareTo(final IPV6Address obj)
375 {
376 Objects.requireNonNull(obj, "obj");
377 return this.address.compareTo(obj.address);
378 }
379
380 }