f2ba8c82f01f3271682aaddf20d63b6506526cb2
[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 ietf-inet-types ipv6-address and ipv6-prefix. This is an internal implementation
19  * class, not meant to be exposed 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
21  * the YANG model.
22  */
23 /*
24  * v6 routines added by Anton Ivanov on 14.6.2015
25  * revised by Robert Varga
26  *
27  * BIG FAT WARNING!!!
28  * Read all of the following before you touch any v6 code or decide to
29  * optimize it by invoking a "simple" Guava call
30  *
31  * Java IPv6 is fundamentally broken and Google libraries do not fix it.
32  * 1. Java will allways implicitly rewrite v4 mapped into v6 as a v4 address
33  *      and there is absolutely no way to override this behaviour
34  * 2. Guava libraries cannot parse non-canonical IPv6. They will throw an
35  *      exception. Even if they did, they re-use the same broken java code
36  *      underneath.
37  *
38  * This is why we have to parse v6 by ourselves.
39  *
40  * The following conversion code is based on inet_cidr_pton_ipv6 in NetBSD
41  *
42  * The original BSD code is licensed under standard BSD license. While we
43  * are not obliged to provide an attribution, credit where credit is due.
44  * As far as why it is similar to Sun's sun.net.util please ask Sun why
45  * their code has the same variable names, comments and code flow.
46  */
47 final class Ipv6Utils {
48     private static final int INADDR4SZ = 4;
49     private static final int INADDR6SZ = 16;
50     private static final int INT16SZ = Short.BYTES;
51
52     private Ipv6Utils() {
53
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 + INADDR4SZ <= INADDR6SZ) {
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 += INADDR4SZ;
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 + INT16SZ <= INADDR6SZ, str);
122             bytes[j++] = (byte) (val >> 8 & 0xff);
123             bytes[j++] = (byte) (val & 0xff);
124         }
125
126         if (colonp != -1) {
127             verifySize(j != INADDR6SZ, str);
128             expandZeros(bytes, colonp, j);
129         } else {
130             verifySize(j == INADDR6SZ, 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 = INADDR6SZ - tailLength;
141         System.arraycopy(bytes, where, bytes, tailOffset, tailLength);
142         Arrays.fill(bytes, where, tailOffset, (byte)0);
143     }
144 }