Move AbstractIetfInetUtil
[mdsal.git] / model / ietf / ietf-type-util / src / main / java / org / opendaylight / mdsal / model / ietf / util / Ipv6Utils.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 static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12
13 import java.util.Arrays;
14 import java.util.HexFormat;
15 import org.eclipse.jdt.annotation.NonNull;
16
17 /**
18  * IPv6 address parsing for {@code ietf-inet-types} ipv6-address and ipv6-prefix. This is an internal implementation
19  * class, not meant to be used directly in any shape or form to the outside world, as the code relies on the fact that
20  * the strings presented to it have been previously validated to conform to the regular expressions defined in the YANG
21  * model.
22  *
23  * <p>
24  * IPv6 routines added by Anton Ivanov on 14.6.2015, revised by Robert Varga
25  *
26  * <p>
27  * <b>BIG FAT WARNING!!!</b>
28  * Read all of the following before you touch any v6 code or decide to optimize it by invoking a "simple" Guava call.
29  *
30  * <p>
31  * Java IPv6 is fundamentally broken and Google libraries do not fix it.
32  * <ol>
33  *   <li>Java will always implicitly rewrite v4 mapped into v6 as a v4 address and there is absolutely no way to
34  *       override this behaviour</li>
35  *   <li>Guava libraries cannot parse non-canonical IPv6. They will throw an exception. Even if they did, they re-use
36  *       the same broken java code underneath</li>
37  * </ol>
38  * This is why we have to parse v6 by ourselves.
39  *
40  * <p>
41  * The following conversion code is based on inet_cidr_pton_ipv6 in NetBSD.
42  *
43  * <p>
44  * The original BSD code is licensed under standard BSD license. While we are not obliged to provide an attribution,
45  * credit where credit is due. As far as why it is similar to Sun's sun.net.util please ask Sun why their code has the
46  * same variable names, comments and code flow.
47  */
48 public final class Ipv6Utils {
49     public static final int INET6_LENGTH = 16;
50
51     private Ipv6Utils() {
52         // Hidden on purpose
53     }
54
55     /**
56      * Convert Ipv6Address object to a valid Canonical v6 address in byte format.
57      *
58      * @param bytes Byte array for output
59      * @param str String representation
60      * @param strLimit String offset which should not be processed
61      * @throws NullPointerException if ipv6address is null
62      */
63     @SuppressWarnings("checkstyle:localVariableName")
64     public static void fillIpv6Bytes(final byte @NonNull[] bytes, final String str, final int strLimit) {
65         // Leading :: requires some special handling.
66         int i = 0;
67         if (str.charAt(i) == ':') {
68             // Note ++i side-effect in check
69             checkArgument(str.charAt(++i) == ':', "Invalid v6 address '%s'", str);
70         }
71
72         boolean haveVal = false;
73         int val = 0;
74         int colonp = -1;
75         int j = 0;
76         int curtok = i;
77         while (i < strLimit) {
78             final char ch = str.charAt(i++);
79
80             // v6 separator
81             if (ch == ':') {
82                 curtok = i;
83                 if (haveVal) {
84                     // removed overrun check - the regexp checks for valid data
85                     bytes[j++] = (byte) (val >>> 8 & 0xff);
86                     bytes[j++] = (byte) (val & 0xff);
87                     haveVal = false;
88                     val = 0;
89                 } else {
90                     // no need to check separator position validity - regexp does that
91                     colonp = j;
92                 }
93
94                 continue;
95             }
96
97             // frankenstein - v4 attached to v6, mixed notation
98             if (ch == '.' && j + Ipv4Utils.INET4_LENGTH <= INET6_LENGTH) {
99                 /*
100                  * This has passed the regexp so it is fairly safe to parse it
101                  * straight away. Use the Ipv4Utils for that.
102                  */
103                 Ipv4Utils.fillIpv4Bytes(bytes, j, str, curtok, strLimit);
104                 j += Ipv4Utils.INET4_LENGTH;
105                 haveVal = false;
106                 break;
107             }
108
109             /*
110              * Business as usual - ipv6 address digit.
111              * We can remove all checks from the original BSD code because
112              * the regexp has already verified that we are not being fed
113              * anything bigger than 0xffff between the separators.
114              */
115             val = (val << 4) + HexFormat.fromHexDigit(ch);
116             haveVal = true;
117         }
118
119         if (haveVal) {
120             verifySize(j + Short.BYTES <= INET6_LENGTH, str);
121             bytes[j++] = (byte) (val >> 8 & 0xff);
122             bytes[j++] = (byte) (val & 0xff);
123         }
124
125         if (colonp != -1) {
126             verifySize(j != INET6_LENGTH, str);
127             expandZeros(bytes, colonp, j);
128         } else {
129             verifySize(j == INET6_LENGTH, str);
130         }
131     }
132
133     private static void verifySize(final boolean expression, final String str) {
134         verify(expression, "Overrun in parsing of '%s', should not occur", str);
135     }
136
137     private static void expandZeros(final byte[] bytes, final int where, final int filledBytes) {
138         final int tailLength = filledBytes - where;
139         final int tailOffset = INET6_LENGTH - tailLength;
140         System.arraycopy(bytes, where, bytes, tailOffset, tailLength);
141         Arrays.fill(bytes, where, tailOffset, (byte)0);
142     }
143 }