BUG-2825: do not instatiate temporary array
[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 com.google.common.net.InetAddresses;
13 import java.net.Inet4Address;
14 import java.net.InetAddress;
15 import javax.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         throw new UnsupportedOperationException();
54     }
55
56     /**
57      * Convert Ipv6Address object to a valid Canonical v6 address in byte format
58      *
59      * @param ipv6Address - v6 Address object
60      *
61      * FIXME: rovarga: this looks wrong
62      * @return - byte array of size 16. Last byte contains netmask
63      *
64      * @throws NullPointerException if ipv6address is null
65      */
66    public static @Nonnull byte[] bytesForString(final @Nonnull String ipv6Address) {
67        final int percentPos = ipv6Address.indexOf('%');
68        // FIXME: do not perform a copy, just set the limit here.
69        final String address = percentPos == -1 ? ipv6Address : ipv6Address.substring(0, percentPos);
70
71        /* Leading :: requires some special handling. */
72        int i = 0;
73        if (address.charAt(i) == ':') {
74            // Note ++i side-effect in check
75            Preconditions.checkArgument(address.charAt(++i) == ':', "Invalid v6 address '%s'", ipv6Address);
76        }
77
78        final byte[] dst = new byte[INADDR6SZ];
79
80        final int src_length = address.length();
81        boolean saw_xdigit = false;
82        int val = 0;
83        int colonp = -1;
84        int j = 0;
85        int curtok = i;
86        while (i < src_length) {
87            final char ch = address.charAt(i++);
88
89            /* v6 separator */
90            if (ch == ':') {
91                curtok = i;
92                if (!saw_xdigit) {
93                    /* no need to check separator position validity - regexp does that */
94                    colonp = j;
95                    continue;
96                }
97
98                /* removed overrun check - the regexp checks for valid data */
99
100                dst[j++] = (byte) ((val >>> 8) & 0xff);
101                dst[j++] = (byte) (val & 0xff);
102                saw_xdigit = false;
103                val = 0;
104                continue;
105            }
106
107            /* frankenstein - v4 attached to v6, mixed notation */
108            if (ch == '.' && ((j + INADDR4SZ) <= INADDR6SZ)) {
109
110                /* this has passed the regexp so it is fairly safe to parse it
111                 * straight away. As v4 addresses do not suffer from the same
112                 * deficiencies as the java v6 implementation we can invoke it
113                 * straight away and be done with it
114                 */
115                Preconditions.checkArgument(j != (INADDR6SZ - INADDR4SZ - 1), "Invalid v4 in v6 mapping in %s", ipv6Address);
116                InetAddress _inet_form = InetAddresses.forString(address.substring(curtok, src_length));
117
118                Preconditions.checkArgument(_inet_form instanceof Inet4Address);
119                System.arraycopy(_inet_form.getAddress(), 0, dst, j, INADDR4SZ);
120                j += INADDR4SZ;
121
122                saw_xdigit = false;
123                break;
124            }
125
126            /* Business as usual - ipv6 address digit.
127             * We can remove all checks from the original BSD code because
128             * the regexp has already verified that we are not being fed
129             * anything bigger than 0xffff between the separators.
130             */
131            final int chval = AbstractIetfYangUtil.hexValue(ch);
132            val = (val << 4) | chval;
133            saw_xdigit = true;
134        }
135
136        if (saw_xdigit) {
137            Verify.verify(j + INT16SZ <= INADDR6SZ, "Overrun in parsing of '%s', should not occur", ipv6Address);
138            dst[j++] = (byte) ((val >> 8) & 0xff);
139            dst[j++] = (byte) (val & 0xff);
140        }
141
142        if (colonp != -1) {
143            Verify.verify(j != INADDR6SZ, "Overrun in parsing of '%s', should not occur", ipv6Address);
144
145            final int n = j - colonp;
146            for (i = 1; i <= n; i++) {
147                dst[INADDR6SZ - i] = dst[colonp + n - i];
148                dst[colonp + n - i] = 0;
149            }
150            j = INADDR6SZ;
151        }
152
153        Verify.verify(j == INADDR6SZ, "Overrun in parsing of '%s', should not occur", ipv6Address);
154        return dst;
155    }
156 }