Fix string duplication in Ipv6Utils
[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 org.eclipse.jdt.annotation.NonNull;
15
16 /**
17  * IPv6 address parsing for ietf-inet-types ipv6-address and ipv6-prefix. This is an internal implementation
18  * class, not meant to be exposed in any shape or form to the outside world, as the code relies on the fact that
19  * the strings presented to it have been previously validated to conform to the regular expressions defined in
20  * the YANG model.
21  */
22 /*
23  * v6 routines added by Anton Ivanov on 14.6.2015
24  * revised by Robert Varga
25  *
26  * BIG FAT WARNING!!!
27  * Read all of the following before you touch any v6 code or decide to
28  * optimize it by invoking a "simple" Guava call
29  *
30  * Java IPv6 is fundamentally broken and Google libraries do not fix it.
31  * 1. Java will allways implicitly rewrite v4 mapped into v6 as a v4 address
32  *      and there is absolutely no way to override this behaviour
33  * 2. Guava libraries cannot parse non-canonical IPv6. They will throw an
34  *      exception. Even if they did, they re-use the same broken java code
35  *      underneath.
36  *
37  * This is why we have to parse v6 by ourselves.
38  *
39  * The following conversion code is based on inet_cidr_pton_ipv6 in NetBSD
40  *
41  * The original BSD code is licensed under standard BSD license. While we
42  * are not obliged to provide an attribution, credit where credit is due.
43  * As far as why it is similar to Sun's sun.net.util please ask Sun why
44  * their code has the same variable names, comments and code flow.
45  */
46 final class Ipv6Utils {
47     private static final int INADDR4SZ = 4;
48     private static final int INADDR6SZ = 16;
49     private static final int INT16SZ = Short.BYTES;
50
51     private Ipv6Utils() {
52
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     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 + INADDR4SZ <= INADDR6SZ) {
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 += INADDR4SZ;
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             final int chval = AbstractIetfYangUtil.hexValue(ch);
116             val = val << 4 | chval;
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 }