001/*
002 * JPPF.
003 * Copyright (C) 2005-2015 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.jppf.net;
020
021import java.net.InetAddress;
022
023import org.jppf.utils.RegexUtils;
024
025/**
026 * Represents a netmask used for IPv6 addresses inclusion and exclusion lists.<br/>
027 * A netmask is in CIDR notation and represents an IPv6 address of which only
028 * the first N bits are significant.
029 * <p>
030 * If no slash is included and there is no double colon ("::") the string is
031 * treated as a {@link org.jppf.net.IPv6AddressPattern}; otherwise it must be a
032 * valid IPv6 address. This class does not handle Dotted Quad notation.
033 * <p>
034 * Examples:
035 * <ul>
036 * <li>1080::8:800:200C:417A represents a single IPv6 address</li>
037 * <li>1080::8:800:200C:417A/128 represents a single IPv6 address</li>
038 * <li>1080::8:800:200C:417A/125 represents all IPv6 addresses in the range
039 * 1080::8:800:200C:4178 - 1080::8:800:200C:417F</li>
040 * <li>1080::8:800:200C:417A/112 represents all IPv6 addresses in the range
041 * 1080::8:800:200C:0 - 1080::8:800:200C:FFFF</li>
042 * <li>1080::8:800:200C:417A/96 represents all IPv6 addresses in the range
043 * 1080::8:800:0:0 - 1080::8:800:FFFF:FFFF</li>
044 * </ul>
045 * 
046 * @author Daniel Widdis
047 * @since 4.2
048 */
049public class IPv6AddressNetmask extends IPv6AddressPattern {
050
051  /**
052   * Initialize this object with the specified string pattern.
053   * 
054   * @param source
055   *        the source pattern as a string.
056   * @throws IllegalArgumentException
057   *         if the pattern is null or invalid.
058   */
059  public IPv6AddressNetmask(final String source)
060      throws IllegalArgumentException {
061    super(netmaskToRange(source));
062  }
063
064  /**
065   * Converts a String in CIDR notation to use the range notation expected by
066   * AbstractIPAddressPattern
067   * 
068   * @param source
069   *        IPv6 Address in CIDR notation
070   * @return String representing this IP Address in range notation
071   */
072  private static String netmaskToRange(final String source) {
073    // If no slash, use unmodified string directly to treat as
074    // IPv6AddressPattern except for "::" parsing to follow
075    String sourceIP = source;
076    int netmask = 128;
077    if (source.contains("/")) {
078      String[] ipAndNetmask = RegexUtils.SLASH_PATTERN.split(source);
079      sourceIP = ipAndNetmask[0];
080      netmask = Integer.parseInt(ipAndNetmask[1]);
081    }
082    // Ensure netmask in range
083    if (netmask < 0 || netmask > 128) {
084      throw new IllegalArgumentException("Netmask " + netmask
085          + " must be between 0 and 128");
086    }
087    // Handle leading or trailing "::" by appending a 0
088    if (sourceIP.startsWith("::")) {
089      sourceIP = "0".concat(sourceIP);
090    }
091    if (sourceIP.endsWith("::")) {
092      sourceIP = sourceIP.concat("0");
093    }
094    // Sanity check IP format. Fail if no ":" or more than seven; if invalid
095    // ":::" or more than one "::"
096    int parts = RegexUtils.COLUMN_PATTERN.split(sourceIP).length;
097    if (parts < 2 || parts > 8 || sourceIP.contains(":::")
098        || sourceIP.split("::").length > 2) {
099      //throw new IllegalArgumentException("Invalid IP address pattern: " + sourceIP);
100      return sourceIP;
101    }
102    // Replace "::" with ":0:0:" as needed to create 8 parts
103    if (sourceIP.contains("::")) {
104      StringBuilder zeroes = new StringBuilder(":0");
105      for (int i = parts; i < 8; i++) {
106        zeroes.append(":0");
107      }
108      zeroes.append(":");
109      sourceIP = sourceIP.replace("::", zeroes.toString());
110    }
111    String[] ip = RegexUtils.COLUMN_PATTERN.split(sourceIP);
112    // This array should have exactly 8 parts if it was a valid IPv6
113    if (ip.length != 8) {
114      // The only way to be here is if no "::" is included. If netmask is 128,
115      // return the original string to be used in IPv6AddressPattern
116      if (netmask == 128) {
117        return source;
118      }
119      // Invalid IP address combined with a netmask
120      throw new IllegalArgumentException("Invalid IP address pattern: "
121          + sourceIP + " (source=" + source + ")");
122    }
123    // Construct IP range from source. Significant bits left untouched.
124    for (int i = 0; i < 8; i++) {
125      int maskBits = 16 * (i + 1) - netmask;
126      // If <= 0, all bits are significant; do nothing to this element
127      // If >= 16, no bits are significant; leave blank in pattern
128      if (maskBits >= 16) {
129        ip[i] = "";
130      } else if (maskBits > 0) {
131        // Mask the insignificant bits
132        int b = Integer.parseInt(ip[i], 16);
133        int mask = (1 << maskBits) - 1;
134        ip[i] = String.format("%s-%s", Integer.toHexString(b & ~mask),
135            Integer.toHexString(b | mask));
136      }
137    }
138    // Build a string from the ip array, stopping at insignificant elements
139    StringBuilder pattern = new StringBuilder(ip[0]);
140    for (int i = 1; i < 8; i++) {
141      if (!ip[i].equals("")) {
142        pattern.append(":").append(ip[i]);
143      }
144    }
145    return pattern.toString();
146  }
147
148  /**
149   * Main method.
150   * 
151   * @param args
152   *        not used.
153   */
154  public static void main(final String[] args) {
155    System.out.println("***** IP v6 *****");
156    String[] ipv6patterns = { "1080:0:0:0:8:800:200C:417A", ":0::::::",
157        "0:0:aa-bbcc:0:0:0:0:0", "1:2:3:4:5-:6:7:8", "::1", "::", "::1/128",
158        "::1/112", "::1/96", "::1/80", "::1/64", "2001:db8::/32",
159        "1080::8:800:200C:417A/128", "1080::0:8:800:200C:417A/127",
160        "1080::0:0:8:800:200C:417A/126", "1080::0:0:8:800:200C:417A/125",
161        "1080::0:0:8:800:200C:417A/124", "1080::0:0:8:800:200C:417A/123",
162        "1080::0:0:8:800:200C:417A/97", "1080::0:0:8:800:200C:417A/96",
163        "1080::0:0:8:800:200C:417A/95", "1080::0:0:8:800:200C:417A/94",
164        "1080::0:0:8:800:200C:417A/1", "1080::0:0:8:800:200C:417A/2"
165    };
166    //String[] ipv6patterns = { "::" };
167    String ip = "1080:0:0:0:8:800:200C:417A";
168    for (int i = 0; i < ipv6patterns.length; i++) {
169      try {
170        IPv6AddressNetmask p = new IPv6AddressNetmask(ipv6patterns[i]);
171        InetAddress addr = InetAddress.getByName(ip);
172        System.out.println("pattern " + i + " for source '" + ipv6patterns[i]
173            + "' = '" + p + "', ip match = " + p.matches(addr));
174      } catch (Exception e) {
175        System.out.println("#" + i + " pattern='" + ipv6patterns[i] + "' : " + e.getMessage());
176      }
177    }
178  }
179}