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