65f9f27ba64f1726b36f5ee7a3bbe44af9330f55
[mdsal.git] / model / ietf / ietf-type-util / src / main / java / org / opendaylight / mdsal / model / ietf / util / AbstractIetfInetUtil.java
1 /*
2  * Copyright (c) 2016 Pantheon Technologies s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.model.ietf.util;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import com.google.common.net.InetAddresses;
13 import java.net.Inet4Address;
14 import java.net.Inet6Address;
15 import java.net.InetAddress;
16 import java.net.UnknownHostException;
17 import java.util.AbstractMap.SimpleImmutableEntry;
18 import java.util.Map.Entry;
19 import javax.annotation.Nonnull;
20 import org.opendaylight.yangtools.yang.binding.util.StringValueObjectFactory;
21
22 /**
23  * A set of utility methods to efficiently instantiate various ietf-inet-types DTOs.
24  */
25 @Beta
26 public abstract class AbstractIetfInetUtil<A4, P4, A6, P6, A> {
27     private static final int INET4_LENGTH = 4;
28     private static final int INET6_LENGTH = 16;
29     private final StringValueObjectFactory<A4> address4Factory;
30     private final StringValueObjectFactory<P4> prefix4Factory;
31     private final StringValueObjectFactory<A6> address6Factory;
32     private final StringValueObjectFactory<P6> prefix6Factory;
33
34     protected AbstractIetfInetUtil(final Class<A4> addr4Class, final Class<P4> prefix4Class,
35             final Class<A6> addr6Class, final Class<P6> prefix6Class) {
36         this.address4Factory = StringValueObjectFactory.create(addr4Class, "0.0.0.0");
37         this.prefix4Factory = StringValueObjectFactory.create(prefix4Class, "0.0.0.0/0");
38         this.address6Factory = StringValueObjectFactory.create(addr6Class, "::0");
39         this.prefix6Factory = StringValueObjectFactory.create(prefix6Class, "::0/0");
40     }
41
42     protected abstract A ipv4Address(A4 addr);
43     protected abstract A ipv6Address(A6 addr);
44     protected abstract String ipv4AddressString(A4 addr);
45     protected abstract String ipv6AddressString(A6 addr);
46     protected abstract String ipv4PrefixString(P4 prefix);
47     protected abstract String ipv6PrefixString(P6 prefix);
48
49     @Nonnull public final A ipAddressFor(@Nonnull final byte[] bytes) {
50         switch (bytes.length) {
51             case 4:
52                 return ipv4Address(ipv4AddressFor(bytes));
53             case 16:
54                 return ipv6Address(ipv6AddressFor(bytes));
55             default:
56                 throw new IllegalArgumentException("Invalid array length " + bytes.length);
57         }
58     }
59
60     @Nonnull public final A ipAddressFor(@Nonnull final InetAddress addr) {
61         Preconditions.checkNotNull(addr, "Address must not be null");
62         if (addr instanceof Inet4Address) {
63             return ipv4Address(ipv4AddressFor(addr));
64         } else if (addr instanceof Inet6Address) {
65             return ipv6Address(ipv6AddressFor(addr));
66         } else {
67             throw new IllegalArgumentException("Unhandled address " + addr);
68         }
69     }
70
71     /**
72      * Create an Ipv4Address by interpreting input bytes as an IPv4 address.
73      *
74      * @param bytes 4-byte array
75      * @return An Ipv4Address object
76      * @throws IllegalArgumentException if bytes has length different from 4
77      * @throws NullPointerException if bytes is null
78      */
79     @Nonnull public final A4 ipv4AddressFor(@Nonnull final byte[] bytes) {
80         return address4Factory.newInstance(addressStringV4(bytes));
81     }
82
83     /**
84      * Create an Ipv4Address by interpreting an {@link Inet4Address}.
85      *
86      * @param addr An {@link Inet4Address}
87      * @return An Ipv4Address object
88      * @throws IllegalArgumentException if addr is not an {@link Inet4Address}
89      * @throws NullPointerException if addr is null
90      */
91     @Nonnull public final A4 ipv4AddressFor(@Nonnull final InetAddress addr) {
92         Preconditions.checkNotNull(addr, "Address must not be null");
93         Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address");
94         return address4Factory.newInstance(addr.getHostAddress());
95     }
96
97     @Nonnull public final A4 ipv4AddressFrom(@Nonnull final P4 prefix) {
98         return prefixToAddress(address4Factory, ipv4PrefixString(prefix));
99     }
100
101     @Nonnull public final byte[] ipv4AddressBytes(@Nonnull final A4 addr) {
102         /*
103          * This implementation relies heavily on the input string having been validated to comply with
104          * the Ipv4Address pattern, which may include a zone index.
105          */
106         final String str = ipv4AddressString(addr);
107         final byte[] bytes = new byte[INET4_LENGTH];
108         final int percent = str.indexOf('%');
109         Ipv4Utils.fillIpv4Bytes(bytes, 0, str, 0, percent == -1 ? str.length() : percent);
110         return bytes;
111     }
112
113     /**
114      * Create a /32 Ipv4Prefix by interpreting input bytes as an IPv4 address.
115      *
116      * @param bytes four-byte array
117      * @return An Ipv4Prefix object
118      * @throws IllegalArgumentException if bytes has length different from 4
119      * @throws NullPointerException if bytes is null
120      */
121     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final byte[] bytes) {
122         return prefix4Factory.newInstance(prefixStringV4(bytes));
123     }
124
125     /**
126      * Create a Ipv4Prefix by combining the address with a mask. The address
127      * bytes are interpreted as an address and the specified mask is concatenated to
128      * it. The address bytes are not masked, hence input <code>address = { 1, 2, 3, 4 }</code>
129      * and <code>mask=24</code> will result in <code>1.2.3.4/24</code>.
130      *
131      * @param address Input address as a 4-byte array
132      * @param mask Prefix mask
133      * @return An Ipv4Prefix object
134      * @throws IllegalArgumentException if bytes has length different from 4 or if mask is not in range 0-32
135      * @throws NullPointerException if bytes is null
136      */
137     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final byte[] address, final int mask) {
138         return prefix4Factory.newInstance(prefixStringV4(address, mask));
139     }
140
141     @Nonnull public final P4 ipv4PrefixForShort(@Nonnull final byte[] address, final int mask) {
142         if (mask == 0) {
143             // Easy case, reuse the template
144             return prefix4Factory.getTemplate();
145         }
146
147         return v4PrefixForShort(address, 0, (mask / Byte.SIZE) + ((mask % Byte.SIZE == 0) ? 0 : 1), mask);
148     }
149
150     @Nonnull public final P4 ipv4PrefixForShort(@Nonnull final byte[] array, final int startOffset, final int mask) {
151         if (mask == 0) {
152             // Easy case, reuse the template
153             return prefix4Factory.getTemplate();
154         }
155
156         return v4PrefixForShort(array, startOffset, (mask / Byte.SIZE) + ((mask % Byte.SIZE == 0) ? 0 : 1), mask);
157     }
158
159     /**
160      * Create a /32 Ipv4Prefix for an {@link Inet4Address}
161      *
162      * @param addr An {@link Inet4Address}
163      * @return An Ipv4Prefix object
164      * @throws IllegalArgumentException if addr is not an Inet4Address
165      * @throws NullPointerException if adds is null
166      */
167     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final InetAddress addr) {
168         Preconditions.checkNotNull(addr, "Address must not be null");
169         Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address");
170         return prefix4Factory.newInstance(addr.getHostAddress() + "/32");
171     }
172
173     /**
174      * Create a Ipv4Prefix by combining the address with a mask. The address bytes are not masked.
175      *
176      * @param addr An {@link Inet4Address}
177      * @param mask Prefix mask
178      * @return An Ipv4Prefix object
179      * @throws IllegalArgumentException if addr is not an Inet4Address or if mask is not in range 0-32
180      * @throws NullPointerException if addr is null
181      */
182     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final InetAddress addr, final int mask) {
183         Preconditions.checkNotNull(addr, "Address must not be null");
184         Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address");
185         Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask);
186         return prefix4Factory.newInstance(addr.getHostAddress() + '/' + mask);
187     }
188
189     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final A4 addr) {
190         Preconditions.checkNotNull(addr, "Address must not be null");
191         return prefix4Factory.newInstance(ipv4AddressString(addr) + "/32");
192     }
193
194     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final A4 addr, final int mask) {
195         Preconditions.checkNotNull(addr, "Address must not be null");
196         Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask);
197         return prefix4Factory.newInstance(ipv4AddressString(addr) + '/' + mask);
198     }
199
200     @Nonnull public final Entry<A4, Integer> splitIpv4Prefix(@Nonnull final P4 prefix) {
201         return splitPrefix(address4Factory, ipv4PrefixString(prefix));
202     }
203
204     @Nonnull public final byte[] ipv4PrefixToBytes(@Nonnull final P4 prefix) {
205         final String str = ipv4PrefixString(prefix);
206         final int slash = str.lastIndexOf('/');
207
208         final byte[] bytes = new byte[INET4_LENGTH + 1];
209         Ipv4Utils.fillIpv4Bytes(bytes, 0, str, 0, slash);
210         bytes[INET4_LENGTH] = (byte)Integer.parseInt(str.substring(slash + 1), 10);
211         return bytes;
212     }
213
214     /**
215      * Create an Ipv6Address by interpreting input bytes as an IPv6 address.
216      *
217      * @param bytes 16-byte array
218      * @return An Ipv6Address object
219      * @throws IllegalArgumentException if bytes has length different from 16
220      * @throws NullPointerException if bytes is null
221      */
222     @Nonnull public final A6 ipv6AddressFor(@Nonnull final byte[] bytes) {
223         return address6Factory.newInstance(addressStringV6(bytes));
224     }
225
226     /**
227      * Create an Ipv6Address by interpreting an {@link Inet6Address}.
228      *
229      * @param addr An {@link Inet6Address}
230      * @return An Ipv6Address object
231      * @throws IllegalArgumentException if addr is not an {@link Inet6Address}
232      * @throws NullPointerException if addr is null
233      */
234     @Nonnull public final A6 ipv6AddressFor(@Nonnull final InetAddress addr) {
235         Preconditions.checkNotNull(addr, "Address must not be null");
236         Preconditions.checkArgument(addr instanceof Inet6Address, "Address has to be an Inet6Address");
237         return address6Factory.newInstance(addressStringV6(addr));
238     }
239
240     @Nonnull public final A6 ipv6AddressFrom(@Nonnull final P6 prefix) {
241         return prefixToAddress(address6Factory, ipv6PrefixString(prefix));
242     }
243
244     @Nonnull public final byte[] ipv6AddressBytes(@Nonnull final A6 addr) {
245         String str = ipv6AddressString(addr);
246         final int percent = str.indexOf('%');
247         if (percent != -1) {
248             str = str.substring(0, percent);
249         }
250
251         return InetAddresses.forString(str).getAddress();
252     }
253
254     /**
255      * Create a /128 Ipv6Prefix by interpreting input bytes as an IPv6 address.
256      *
257      * @param bytes four-byte array
258      * @return An Ipv6Prefix object
259      * @throws IllegalArgumentException if bytes has length different from 16
260      * @throws NullPointerException if bytes is null
261      */
262     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final byte[] bytes) {
263         return prefix6Factory.newInstance(addressStringV6(bytes) + "/128");
264     }
265
266     /**
267      * Create a Ipv6Prefix by combining the address with a mask. The address
268      * bytes are interpreted as an address and the specified mask is concatenated to
269      * it. The address bytes are not masked.
270      *
271      * @param address Input address as a 4-byte array
272      * @param mask Prefix mask
273      * @return An Ipv6Prefix object
274      * @throws IllegalArgumentException if bytes has length different from 16 or if mask is not in range 0-128
275      * @throws NullPointerException if bytes is null
276      */
277     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final byte[] address, final int mask) {
278         Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask);
279         return prefix6Factory.newInstance(addressStringV6(address) + '/' + mask);
280     }
281
282     @Nonnull public final P6 ipv6PrefixForShort(@Nonnull final byte[] address, final int mask) {
283         return ipv6PrefixForShort(address, 0, mask);
284     }
285
286     @Nonnull public final P6 ipv6PrefixForShort(@Nonnull final byte[] array, final int startOffset, final int mask) {
287         if (mask == 0) {
288             // Easy case, reuse the template
289             return prefix6Factory.getTemplate();
290         }
291
292         Preconditions.checkArgument(mask > 0 && mask <= 128, "Invalid mask %s", mask);
293         final int size = (mask / Byte.SIZE) + ((mask % Byte.SIZE == 0) ? 0 : 1);
294
295         // Until we can instantiate an IPv6 address for a partial array, use a temporary buffer
296         byte[] tmp = new byte[INET6_LENGTH];
297         System.arraycopy(array, startOffset, tmp, 0, size);
298         return ipv6PrefixFor(tmp, mask);
299     }
300
301     /**
302      * Create a /128 Ipv6Prefix by interpreting input bytes as an IPv4 address.
303      *
304      * @param addr an {@link Inet6Address}
305      * @return An Ipv6Prefix object
306      * @throws IllegalArgumentException if addr is not an Inet6Address or if mask is not in range 0-128
307      * @throws NullPointerException if addr is null
308      */
309     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final InetAddress addr) {
310         return prefix6Factory.newInstance(addressStringV6(addr) + "/128");
311     }
312
313     /**
314      * Create a Ipv6Prefix by combining the address with a mask. The address
315      * bytes are interpreted as an address and the specified mask is concatenated to
316      * it. The address bytes are not masked.
317      *
318      * @param addr Input address
319      * @param mask Prefix mask
320      * @return An Ipv6Prefix object
321      * @throws IllegalArgumentException if addr is not an Inet6Address or if mask is not in range 0-128
322      * @throws NullPointerException if addr is null
323      */
324     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final InetAddress addr, final int mask) {
325         Preconditions.checkNotNull(addr, "Address must not be null");
326         Preconditions.checkArgument(addr instanceof Inet6Address, "Address has to be an Inet6Address");
327         Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask);
328         return prefix6Factory.newInstance(addressStringV6(addr) + '/' + mask);
329     }
330
331     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final A6 addr) {
332         Preconditions.checkNotNull(addr, "Address must not be null");
333         return prefix6Factory.newInstance(ipv6AddressString(addr) + "/128");
334     }
335
336     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final A6 addr, final int mask) {
337         Preconditions.checkNotNull(addr, "Address must not be null");
338         Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask);
339         return prefix6Factory.newInstance(ipv6AddressString(addr) + '/' + mask);
340     }
341
342     @Nonnull public final Entry<A6, Integer> splitIpv6Prefix(@Nonnull final P6 prefix) {
343         return splitPrefix(address6Factory, ipv6PrefixString(prefix));
344     }
345
346     private static <T> T prefixToAddress(final StringValueObjectFactory<T> factory, final String str) {
347         return factory.newInstance(str.substring(0, str.lastIndexOf('/')));
348     }
349
350     private static <T> Entry<T, Integer> splitPrefix(final StringValueObjectFactory<T> factory, final String str) {
351         final int slash = str.lastIndexOf('/');
352         return new SimpleImmutableEntry<>(factory.newInstance(str.substring(0, slash)),
353                 Integer.valueOf(str.substring(slash + 1)));
354     }
355
356     @Nonnull public final byte[] ipv6PrefixToBytes(@Nonnull final P6 prefix) {
357         final String str = ipv6PrefixString(prefix);
358         final int slash = str.lastIndexOf('/');
359
360         final byte[] bytes = new byte[INET6_LENGTH + 1];
361         System.arraycopy(InetAddresses.forString(str.substring(0, slash)).getAddress(), 0, bytes, 0, INET6_LENGTH);
362         bytes[INET6_LENGTH] = (byte)Integer.parseInt(str.substring(slash + 1), 10);
363         return bytes;
364     }
365
366     private static void appendIpv4String(final StringBuilder sb, final byte[] bytes) {
367         Preconditions.checkArgument(bytes.length == INET4_LENGTH, "IPv4 address length is 4 bytes");
368
369         sb.append(Byte.toUnsignedInt(bytes[0]));
370         for (int i = 1; i < INET4_LENGTH; ++i) {
371             sb.append('.');
372             sb.append(Byte.toUnsignedInt(bytes[i]));
373         }
374     }
375
376     private static String addressStringV4(final byte[] bytes) {
377         final StringBuilder sb = new StringBuilder(15);
378         appendIpv4String(sb, bytes);
379         return sb.toString();
380     }
381
382     private static String addressStringV6(final byte[] bytes) {
383         Preconditions.checkArgument(bytes.length == INET6_LENGTH, "IPv6 address length is 16 bytes");
384
385         try {
386             return addressStringV6(Inet6Address.getByAddress(bytes));
387         } catch (UnknownHostException e) {
388             throw new IllegalArgumentException(String.format("Invalid input %s", bytes), e);
389         }
390     }
391
392     private static String addressStringV6(final InetAddress addr) {
393         return InetAddresses.toAddrString(addr);
394     }
395
396     private static String prefixStringV4(final byte[] bytes) {
397         final StringBuilder sb = new StringBuilder(18);
398         appendIpv4String(sb, bytes);
399         sb.append("/32");
400         return sb.toString();
401     }
402
403     private static String prefixStringV4(final byte[] bytes, final int mask) {
404         Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask);
405
406         final StringBuilder sb = new StringBuilder(18);
407         appendIpv4String(sb, bytes);
408         sb.append('/');
409         sb.append(mask);
410         return sb.toString();
411     }
412
413     private P4 v4PrefixForShort(@Nonnull final byte[] array, final int startOffset, final int size, final int mask) {
414         if (startOffset == 0 && size == INET4_LENGTH && array.length == INET4_LENGTH) {
415             // Easy case, fall back to non-short
416             return ipv4PrefixFor(array, mask);
417         }
418
419         final StringBuilder sb = new StringBuilder(18);
420
421         // Add from address
422         sb.append(Byte.toUnsignedInt(array[startOffset]));
423         for (int i = 1; i < size; i++) {
424             sb.append('.');
425             sb.append(Byte.toUnsignedInt(array[startOffset + i]));
426         }
427
428         // Add zeros
429         for (int i = size; i < INET4_LENGTH; i++) {
430             sb.append('.');
431             sb.append(0);
432         }
433
434         // Add mask
435         Preconditions.checkArgument(mask > 0 && mask <= 32, "Invalid mask %s", mask);
436         sb.append('/');
437         sb.append(mask);
438
439         return prefix4Factory.newInstance(sb.toString());
440     }
441 }