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