/*
* Copyright 2014, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.betfair.cougar.util;
/**
* Represents an IP network (as opposed to an IP Address) and is able to identify if an IP address is a member of the network
*/
public class NetworkAddress {
/**
* The address of this network
*/
private final byte[] network;
/**
* mask to apply to an address to obtain the network identifier
*/
private final byte[] netmask;
/**
* For private use only, clients should use the static factory method
*/
private NetworkAddress(byte[] network, byte[] netmask) {//NOSONAR
this.network = network;
this.netmask = netmask;
}
// for testing
byte[] getNetwork() {
return network;
}
// for testing
byte[] getNetmask() {
return netmask;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String sep = "";
for (byte b : network) {
sb.append(sep);
sb.append(toInt(b));
sep = ".";
}
sep = "/";
for (byte b : netmask) {
sb.append(sep);
sb.append(toInt(b));
sep = ".";
}
return sb.toString();
}
/**
*
* @param address
* @return true if address is a member of this network
*/
public boolean isAddressInNetwork(String address) {
boolean inNetwork = false;
if (address != null) {
byte[] addressBytes = parseDottedQuad(address);
byte[] networkPart = new byte[4];
for (int i=0; i<4; i++) {
networkPart[i] = (byte) (addressBytes[i] & netmask[i]);
}
inNetwork = network[0] == networkPart[0] &&
network[1] == networkPart[1] &&
network[2] == networkPart[2] &&
network[3] == networkPart[3] ;
}
return inNetwork;
}
/**
* parse a network address identifier, consisting of an ip4Address/netMask, where both ip4Address and netMask are
* presented in dotted quad notation. e.g. 92.6.4.0/255.255.255.0
* @return
*/
public static NetworkAddress parse(String networkAddress) {
NetworkAddress address = null;
if (networkAddress != null) {
String[] split = networkAddress.split("/");
if (split.length == 2) {
byte[] network = parseDottedQuad(split[0]);
byte[] netmask = parseDottedQuad(split[1]);
address = toNetworkAddress(network, netmask);
}
else {
throw new IllegalArgumentException("Network address must be ip4Address/netMask");
}
}
return address;
}
private static NetworkAddress toNetworkAddress(byte[] network, byte[] netmask) {
byte[] maskedNetworkAddress = new byte[4];
for (int i=0; i<4; i++) {
maskedNetworkAddress[i] = (byte) (network[i] & netmask[i]);
}
return new NetworkAddress(maskedNetworkAddress, netmask);
}
/**
* parse a CIDR network block identifier, consisting of an ip4Address/prefixSize, where both ip4Address and netMask are
* presented in dotted quad notation. e.g. 192.0.2.0/24
* @return
* @see {http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv4_CIDR_blocks}
*/
public static NetworkAddress parseBlock(String networkAddress) {
NetworkAddress address = null;
if (networkAddress != null) {
String[] split = networkAddress.split("/");
if (split.length == 2) {
byte[] network = parseDottedQuad(split[0]);
byte bits = Byte.parseByte(split[1]);
address = toNetworkAddress(network, toNetworkMask(bits));
}
else {
throw new IllegalArgumentException("Network address must be ip4Address/prefixSize");
}
}
return address;
}
/**
* @see {http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#IPv4_CIDR_blocks}
*/
private static byte[] toNetworkMask(byte bits) {
// lookup table is faster, and we only have 33 unique values to return
switch (bits) {
case 0: return new byte[] { toByte( 0), toByte( 0), toByte( 0), toByte( 0) };
case 1: return new byte[] { toByte(128), toByte( 0), toByte( 0), toByte( 0) };
case 2: return new byte[] { toByte(192), toByte( 0), toByte( 0), toByte( 0) };
case 3: return new byte[] { toByte(224), toByte( 0), toByte( 0), toByte( 0) };
case 4: return new byte[] { toByte(240), toByte( 0), toByte( 0), toByte( 0) };
case 5: return new byte[] { toByte(248), toByte( 0), toByte( 0), toByte( 0) };
case 6: return new byte[] { toByte(252), toByte( 0), toByte( 0), toByte( 0) };
case 7: return new byte[] { toByte(254), toByte( 0), toByte( 0), toByte( 0) };
case 8: return new byte[] { toByte(255), toByte( 0), toByte( 0), toByte( 0) };
case 9: return new byte[] { toByte(255), toByte(128), toByte( 0), toByte( 0) };
case 10: return new byte[] { toByte(255), toByte(192), toByte( 0), toByte( 0) };
case 11: return new byte[] { toByte(255), toByte(224), toByte( 0), toByte( 0) };
case 12: return new byte[] { toByte(255), toByte(240), toByte( 0), toByte( 0) };
case 13: return new byte[] { toByte(255), toByte(248), toByte( 0), toByte( 0) };
case 14: return new byte[] { toByte(255), toByte(252), toByte( 0), toByte( 0) };
case 15: return new byte[] { toByte(255), toByte(254), toByte( 0), toByte( 0) };
case 16: return new byte[] { toByte(255), toByte(255), toByte( 0), toByte( 0) };
case 17: return new byte[] { toByte(255), toByte(255), toByte(128), toByte( 0) };
case 18: return new byte[] { toByte(255), toByte(255), toByte(192), toByte( 0) };
case 19: return new byte[] { toByte(255), toByte(255), toByte(224), toByte( 0) };
case 20: return new byte[] { toByte(255), toByte(255), toByte(240), toByte( 0) };
case 21: return new byte[] { toByte(255), toByte(255), toByte(248), toByte( 0) };
case 22: return new byte[] { toByte(255), toByte(255), toByte(252), toByte( 0) };
case 23: return new byte[] { toByte(255), toByte(255), toByte(254), toByte( 0) };
case 24: return new byte[] { toByte(255), toByte(255), toByte(255), toByte( 0) };
case 25: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(128) };
case 26: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(192) };
case 27: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(224) };
case 28: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(240) };
case 29: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(248) };
case 30: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(252) };
case 31: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(254) };
case 32: return new byte[] { toByte(255), toByte(255), toByte(255), toByte(255) };
}
throw new IllegalArgumentException("Invalid prefix size: "+bits);
}
static byte toByte(int unsigned) {
if (unsigned < 0 || unsigned > 255) {
throw new IllegalStateException("Out of unsigned byte range: "+unsigned);
}
return (byte) (unsigned > 127 ? unsigned - 256 : unsigned); //a pox on java and it's signed bytes
}
static int toInt(byte signed) {
if (signed < 0) {
return signed + 256;
}
return signed;
}
/**
* Verify if a given string is a valid dotted quad notation IP Address
* @param networkAddress The address string
* @return true if its valid, false otherwise
*/
public static boolean isValidIPAddress(String networkAddress) {
if (networkAddress != null) {
String[] split = networkAddress.split("\\.");
if (split.length == 4) {
int[] octets = new int[4];
for (int i=0; i<4; i++) {
try {
octets[i] = Integer.parseInt(split[i]);
} catch (NumberFormatException e) {
return false;
}
if (octets[i] < 0 || octets[i] > 255) {
return false;
}
}
return true;
}
}
return false;
}
/**
* parse ip4 address as dotted quad notation into bytes
* @param address
* @return
*/
private static byte[] parseDottedQuad(String address) {
String[] splitString = address.split("\\.");
if (splitString.length == 4) {
int[] ints = new int[4];
byte[] bytes = new byte[4];
for (int i=0; i<4; i++) {
ints[i] = Integer.parseInt(splitString[i]);
if (ints[i] < 0 || ints[i] > 255) {
throw new IllegalArgumentException("Invalid ip4Address or netmask");
}
bytes[i] = toByte(ints[i]); //a pox on java and it's signed bytes
}
return bytes;
}
else {
throw new IllegalArgumentException("Address must be in dotted quad notation");
}
}
}