BUG-2825: reuse IPv4 address formatting
[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     /**
142      * Create a /32 Ipv4Prefix for an {@link Inet4Address}
143      *
144      * @param addr An {@link Inet4Address}
145      * @return An Ipv4Prefix object
146      * @throws IllegalArgumentException if addr is not an Inet4Address
147      * @throws NullPointerException if adds is null
148      */
149     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final InetAddress addr) {
150         Preconditions.checkNotNull(addr, "Address must not be null");
151         Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address");
152         return prefix4Factory.newInstance(addr.getHostAddress() + "/32");
153     }
154
155     /**
156      * Create a Ipv4Prefix by combining the address with a mask. The address bytes are not masked.
157      *
158      * @param addr An {@link Inet4Address}
159      * @param mask Prefix mask
160      * @return An Ipv4Prefix object
161      * @throws IllegalArgumentException if addr is not an Inet4Address or if mask is not in range 0-32
162      * @throws NullPointerException if addr is null
163      */
164     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final InetAddress addr, final int mask) {
165         Preconditions.checkNotNull(addr, "Address must not be null");
166         Preconditions.checkArgument(addr instanceof Inet4Address, "Address has to be an Inet4Address");
167         Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask);
168         return prefix4Factory.newInstance(addr.getHostAddress() + '/' + mask);
169     }
170
171     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final A4 addr) {
172         Preconditions.checkNotNull(addr, "Address must not be null");
173         return prefix4Factory.newInstance(ipv4AddressString(addr) + "/32");
174     }
175
176     @Nonnull public final P4 ipv4PrefixFor(@Nonnull final A4 addr, final int mask) {
177         Preconditions.checkNotNull(addr, "Address must not be null");
178         Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask);
179         return prefix4Factory.newInstance(ipv4AddressString(addr) + '/' + mask);
180     }
181
182     @Nonnull public final Entry<A4, Integer> splitIpv4Prefix(@Nonnull final P4 prefix) {
183         return splitPrefix(address4Factory, ipv4PrefixString(prefix));
184     }
185
186     @Nonnull public final byte[] ipv4PrefixToBytes(@Nonnull final P4 prefix) {
187         final String str = ipv4PrefixString(prefix);
188         final int slash = str.lastIndexOf('/');
189
190         final byte[] bytes = new byte[INET4_LENGTH + 1];
191         Ipv4Utils.fillIpv4Bytes(bytes, 0, str, 0, slash);
192         bytes[INET4_LENGTH] = (byte)Integer.parseInt(str.substring(slash + 1), 10);
193         return bytes;
194     }
195
196     /**
197      * Create an Ipv6Address by interpreting input bytes as an IPv6 address.
198      *
199      * @param bytes 16-byte array
200      * @return An Ipv6Address object
201      * @throws IllegalArgumentException if bytes has length different from 16
202      * @throws NullPointerException if bytes is null
203      */
204     @Nonnull public final A6 ipv6AddressFor(@Nonnull final byte[] bytes) {
205         return address6Factory.newInstance(addressStringV6(bytes));
206     }
207
208     /**
209      * Create an Ipv6Address by interpreting an {@link Inet6Address}.
210      *
211      * @param addr An {@link Inet6Address}
212      * @return An Ipv6Address object
213      * @throws IllegalArgumentException if addr is not an {@link Inet6Address}
214      * @throws NullPointerException if addr is null
215      */
216     @Nonnull public final A6 ipv6AddressFor(@Nonnull final InetAddress addr) {
217         Preconditions.checkNotNull(addr, "Address must not be null");
218         Preconditions.checkArgument(addr instanceof Inet6Address, "Address has to be an Inet6Address");
219         return address6Factory.newInstance(addressStringV6(addr));
220     }
221
222     @Nonnull public final A6 ipv6AddressFrom(@Nonnull final P6 prefix) {
223         return prefixToAddress(address6Factory, ipv6PrefixString(prefix));
224     }
225
226     @Nonnull public final byte[] ipv6AddressBytes(@Nonnull final A6 addr) {
227         String str = ipv6AddressString(addr);
228         final int percent = str.indexOf('%');
229         if (percent != -1) {
230             str = str.substring(0, percent);
231         }
232
233         return InetAddresses.forString(str).getAddress();
234     }
235
236     /**
237      * Create a /128 Ipv6Prefix by interpreting input bytes as an IPv6 address.
238      *
239      * @param bytes four-byte array
240      * @return An Ipv6Prefix object
241      * @throws IllegalArgumentException if bytes has length different from 16
242      * @throws NullPointerException if bytes is null
243      */
244     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final byte[] bytes) {
245         return prefix6Factory.newInstance(addressStringV6(bytes) + "/128");
246     }
247
248     /**
249      * Create a Ipv6Prefix by combining the address with a mask. The address
250      * bytes are interpreted as an address and the specified mask is concatenated to
251      * it. The address bytes are not masked.
252      *
253      * @param address Input address as a 4-byte array
254      * @param mask Prefix mask
255      * @return An Ipv6Prefix object
256      * @throws IllegalArgumentException if bytes has length different from 16 or if mask is not in range 0-128
257      * @throws NullPointerException if bytes is null
258      */
259     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final byte[] address, final int mask) {
260         Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask);
261         return prefix6Factory.newInstance(addressStringV6(address) + '/' + mask);
262     }
263
264     /**
265      * Create a /128 Ipv6Prefix by interpreting input bytes as an IPv4 address.
266      *
267      * @param addr an {@link Inet6Address}
268      * @return An Ipv6Prefix object
269      * @throws IllegalArgumentException if addr is not an Inet6Address or if mask is not in range 0-128
270      * @throws NullPointerException if addr is null
271      */
272     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final InetAddress addr) {
273         return prefix6Factory.newInstance(addressStringV6(addr) + "/128");
274     }
275
276     /**
277      * Create a Ipv6Prefix by combining the address with a mask. The address
278      * bytes are interpreted as an address and the specified mask is concatenated to
279      * it. The address bytes are not masked.
280      *
281      * @param addr Input address
282      * @param mask Prefix mask
283      * @return An Ipv6Prefix object
284      * @throws IllegalArgumentException if addr is not an Inet6Address or if mask is not in range 0-128
285      * @throws NullPointerException if addr is null
286      */
287     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final InetAddress addr, final int mask) {
288         Preconditions.checkNotNull(addr, "Address must not be null");
289         Preconditions.checkArgument(addr instanceof Inet6Address, "Address has to be an Inet6Address");
290         Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask);
291         return prefix6Factory.newInstance(addressStringV6(addr) + '/' + mask);
292     }
293
294     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final A6 addr) {
295         Preconditions.checkNotNull(addr, "Address must not be null");
296         return prefix6Factory.newInstance(ipv6AddressString(addr) + "/128");
297     }
298
299     @Nonnull public final P6 ipv6PrefixFor(@Nonnull final A6 addr, final int mask) {
300         Preconditions.checkNotNull(addr, "Address must not be null");
301         Preconditions.checkArgument(mask >= 0 && mask <= 128, "Invalid mask %s", mask);
302         return prefix6Factory.newInstance(ipv6AddressString(addr) + '/' + mask);
303     }
304
305     @Nonnull public final Entry<A6, Integer> splitIpv6Prefix(@Nonnull final P6 prefix) {
306         return splitPrefix(address6Factory, ipv6PrefixString(prefix));
307     }
308
309     private static <T> T prefixToAddress(final StringValueObjectFactory<T> factory, final String str) {
310         return factory.newInstance(str.substring(0, str.lastIndexOf('/')));
311     }
312
313     private static <T> Entry<T, Integer> splitPrefix(final StringValueObjectFactory<T> factory, final String str) {
314         final int slash = str.lastIndexOf('/');
315         return new SimpleImmutableEntry<>(factory.newInstance(str.substring(0, slash)),
316                 Integer.valueOf(str.substring(slash + 1)));
317     }
318
319     @Nonnull public final byte[] ipv6PrefixToBytes(@Nonnull final P6 prefix) {
320         final String str = ipv6PrefixString(prefix);
321         final int slash = str.lastIndexOf('/');
322
323         final byte[] bytes = new byte[INET6_LENGTH + 1];
324         System.arraycopy(InetAddresses.forString(str.substring(0, slash)).getAddress(), 0, bytes, 0, INET6_LENGTH);
325         bytes[INET6_LENGTH] = (byte)Integer.parseInt(str.substring(slash + 1), 10);
326         return bytes;
327     }
328
329     private static void appendIpv4String(final StringBuilder sb, final byte[] bytes) {
330         Preconditions.checkArgument(bytes.length == INET4_LENGTH, "IPv4 address length is 4 bytes");
331
332         sb.append(Byte.toUnsignedInt(bytes[0]));
333         for (int i = 1; i < INET4_LENGTH; ++i) {
334             sb.append('.');
335             sb.append(Byte.toUnsignedInt(bytes[i]));
336         }
337     }
338
339     private static String addressStringV4(final byte[] bytes) {
340         final StringBuilder sb = new StringBuilder(15);
341         appendIpv4String(sb, bytes);
342         return sb.toString();
343     }
344
345     private static String addressStringV6(final byte[] bytes) {
346         Preconditions.checkArgument(bytes.length == INET6_LENGTH, "IPv6 address length is 16 bytes");
347
348         try {
349             return addressStringV6(Inet6Address.getByAddress(bytes));
350         } catch (UnknownHostException e) {
351             throw new IllegalArgumentException(String.format("Invalid input %s", bytes), e);
352         }
353     }
354
355     private static String addressStringV6(final InetAddress addr) {
356         return InetAddresses.toAddrString(addr);
357     }
358
359     private static String prefixStringV4(final byte[] bytes) {
360         final StringBuilder sb = new StringBuilder(18);
361         appendIpv4String(sb, bytes);
362         sb.append("/32");
363         return sb.toString();
364     }
365
366     private static String prefixStringV4(final byte[] bytes, final int mask) {
367         Preconditions.checkArgument(mask >= 0 && mask <= 32, "Invalid mask %s", mask);
368
369         final StringBuilder sb = new StringBuilder(18);
370         appendIpv4String(sb, bytes);
371         sb.append('/');
372         sb.append(mask);
373         return sb.toString();
374     }
375 }