Ayaka Koshibe

netmask support in IpAddress

Change-Id: Ie5f4276a1fa1cdd56bebbd3cd1ee74ecacdab598
...@@ -5,7 +5,7 @@ import java.util.Arrays; ...@@ -5,7 +5,7 @@ import java.util.Arrays;
5 /** 5 /**
6 * A class representing an IPv4 address. 6 * A class representing an IPv4 address.
7 */ 7 */
8 -public class IpAddress { 8 +public final class IpAddress {
9 9
10 //IP Versions 10 //IP Versions
11 public enum Version { INET, INET6 }; 11 public enum Version { INET, INET6 };
...@@ -14,13 +14,30 @@ public class IpAddress { ...@@ -14,13 +14,30 @@ public class IpAddress {
14 public static final int INET_LEN = 4; 14 public static final int INET_LEN = 4;
15 public static final int INET6_LEN = 16; 15 public static final int INET6_LEN = 16;
16 16
17 + //maximum CIDR value
18 + public static final int MAX_INET_MASK = 32;
19 + public static final int DEFAULT_MASK = 0;
20 +
21 + /**
22 + * Default value indicating an unspecified address.
23 + */
24 + public static final byte [] ANY = new byte [] {0, 0, 0, 0};
25 +
17 protected Version version; 26 protected Version version;
18 - //does it make more sense to have a integral address? 27 +
19 protected byte[] octets; 28 protected byte[] octets;
29 + protected int netmask;
20 30
21 - protected IpAddress(Version ver, byte[] octets) { 31 + private IpAddress(Version ver, byte[] octets, int netmask) {
22 this.version = ver; 32 this.version = ver;
23 this.octets = Arrays.copyOf(octets, INET_LEN); 33 this.octets = Arrays.copyOf(octets, INET_LEN);
34 + this.netmask = netmask;
35 + }
36 +
37 + private IpAddress(Version ver, byte[] octets) {
38 + this.version = ver;
39 + this.octets = Arrays.copyOf(octets, INET_LEN);
40 + this.netmask = DEFAULT_MASK;
24 } 41 }
25 42
26 /** 43 /**
...@@ -34,38 +51,87 @@ public class IpAddress { ...@@ -34,38 +51,87 @@ public class IpAddress {
34 } 51 }
35 52
36 /** 53 /**
37 - * Converts an integer into an IPv4 address. 54 + * Converts a byte array into an IP address.
38 * 55 *
39 - * @param address an integer representing an IP value 56 + * @param address a byte array
57 + * @param netmask the CIDR value subnet mask
40 * @return an IP address 58 * @return an IP address
41 */ 59 */
42 - public static IpAddress valueOf(int address) { 60 + public static IpAddress valueOf(byte [] address, int netmask) {
61 + return new IpAddress(Version.INET, address, netmask);
62 + }
63 +
64 + /**
65 + * Helper to convert an integer into a byte array.
66 + *
67 + * @param address the integer to convert
68 + * @return a byte array
69 + */
70 + private static byte [] bytes(int address) {
43 byte [] bytes = new byte [INET_LEN]; 71 byte [] bytes = new byte [INET_LEN];
44 for (int i = 0; i < INET_LEN; i++) { 72 for (int i = 0; i < INET_LEN; i++) {
45 bytes[i] = (byte) ((address >> (INET_LEN - (i + 1)) * 8) & 0xff); 73 bytes[i] = (byte) ((address >> (INET_LEN - (i + 1)) * 8) & 0xff);
46 } 74 }
47 75
48 - return new IpAddress(Version.INET, bytes); 76 + return bytes;
77 + }
78 +
79 + /**
80 + * Converts an integer into an IPv4 address.
81 + *
82 + * @param address an integer representing an IP value
83 + * @return an IP address
84 + */
85 + public static IpAddress valueOf(int address) {
86 + return new IpAddress(Version.INET, bytes(address));
87 + }
88 +
89 + /**
90 + * Converts an integer into an IPv4 address.
91 + *
92 + * @param address an integer representing an IP value
93 + * @param netmask the CIDR value subnet mask
94 + * @return an IP address
95 + */
96 + public static IpAddress valueOf(int address, int netmask) {
97 + return new IpAddress(Version.INET, bytes(address), netmask);
49 } 98 }
50 99
51 /** 100 /**
52 - * Converts a string in dotted-decimal notation (x.x.x.x) into 101 + * Converts a dotted-decimal string (x.x.x.x) into an IPv4 address. The
53 - * an IPv4 address. 102 + * string can also be in CIDR (slash) notation.
54 * 103 *
55 - * @param address a string representing an IP address, e.g. "10.0.0.1" 104 + * @param address a IP address in string form, e.g. "10.0.0.1", "10.0.0.1/24"
56 * @return an IP address 105 * @return an IP address
57 */ 106 */
58 public static IpAddress valueOf(String address) { 107 public static IpAddress valueOf(String address) {
59 - final String [] parts = address.split("\\."); 108 +
60 - if (parts.length != INET_LEN) { 109 + final String [] parts = address.split("\\/");
110 + if (parts.length > 2) {
111 + throw new IllegalArgumentException("Malformed IP address string; "
112 + + "Addres must take form \"x.x.x.x\" or \"x.x.x.x/y\"");
113 + }
114 +
115 + int mask = DEFAULT_MASK;
116 + if (parts.length == 2) {
117 + mask = Integer.valueOf(parts[1]);
118 + if (mask > MAX_INET_MASK) {
119 + throw new IllegalArgumentException(
120 + "Value of subnet mask cannot exceed "
121 + + MAX_INET_MASK);
122 + }
123 + }
124 +
125 + final String [] net = parts[0].split("\\.");
126 + if (net.length != INET_LEN) {
61 throw new IllegalArgumentException("Malformed IP address string; " 127 throw new IllegalArgumentException("Malformed IP address string; "
62 + "Addres must have four decimal values separated by dots (.)"); 128 + "Addres must have four decimal values separated by dots (.)");
63 } 129 }
64 final byte [] bytes = new byte[INET_LEN]; 130 final byte [] bytes = new byte[INET_LEN];
65 for (int i = 0; i < INET_LEN; i++) { 131 for (int i = 0; i < INET_LEN; i++) {
66 - bytes[i] = Byte.parseByte(parts[i], 10); 132 + bytes[i] = (byte) Short.parseShort(net[i], 10);
67 } 133 }
68 - return new IpAddress(Version.INET, bytes); 134 + return new IpAddress(Version.INET, bytes, mask);
69 } 135 }
70 136
71 /** 137 /**
...@@ -99,34 +165,122 @@ public class IpAddress { ...@@ -99,34 +165,122 @@ public class IpAddress {
99 return address; 165 return address;
100 } 166 }
101 167
102 - @Override 168 + /**
103 - public String toString() { 169 + * Helper for computing the mask value from CIDR.
104 - final StringBuilder builder = new StringBuilder(); 170 + *
105 - for (final byte b : this.octets) { 171 + * @return an integer bitmask
106 - if (builder.length() > 0) { 172 + */
107 - builder.append("."); 173 + private int mask() {
174 + int shift = MAX_INET_MASK - this.netmask;
175 + return ((Integer.MAX_VALUE >>> (shift - 1)) << shift);
108 } 176 }
109 - builder.append(String.format("%d", b)); 177 +
178 + /**
179 + * Returns the subnet mask in IpAddress form. The netmask value for
180 + * the returned IpAddress is 0, as the address itself is a mask.
181 + *
182 + * @return the subnet mask
183 + */
184 + public IpAddress netmask() {
185 + return new IpAddress(Version.INET, bytes(mask()));
110 } 186 }
111 - return builder.toString(); 187 +
188 + /**
189 + * Returns the network portion of this address as an IpAddress.
190 + * The netmask of the returned IpAddress is the current mask. If this
191 + * address doesn't have a mask, this returns an all-0 IpAddress.
192 + *
193 + * @return the network address or null
194 + */
195 + public IpAddress network() {
196 + if (netmask == DEFAULT_MASK) {
197 + return new IpAddress(version, ANY, DEFAULT_MASK);
198 + }
199 +
200 + byte [] net = new byte [4];
201 + byte [] mask = bytes(mask());
202 + for (int i = 0; i < INET_LEN; i++) {
203 + net[i] = (byte) (octets[i] & mask[i]);
204 + }
205 + return new IpAddress(version, net, netmask);
206 + }
207 +
208 + /**
209 + * Returns the host portion of the IPAddress, as an IPAddress.
210 + * The netmask of the returned IpAddress is the current mask. If this
211 + * address doesn't have a mask, this returns a copy of the current
212 + * address.
213 + *
214 + * @return the host address
215 + */
216 + public IpAddress host() {
217 + if (netmask == DEFAULT_MASK) {
218 + new IpAddress(version, octets, netmask);
219 + }
220 +
221 + byte [] host = new byte [INET_LEN];
222 + byte [] mask = bytes(mask());
223 + for (int i = 0; i < INET_LEN; i++) {
224 + host[i] = (byte) (octets[i] & ~mask[i]);
225 + }
226 + return new IpAddress(version, host, netmask);
112 } 227 }
113 228
114 @Override 229 @Override
115 public int hashCode() { 230 public int hashCode() {
116 - return Arrays.hashCode(octets); 231 + final int prime = 31;
232 + int result = 1;
233 + result = prime * result + netmask;
234 + result = prime * result + Arrays.hashCode(octets);
235 + result = prime * result + ((version == null) ? 0 : version.hashCode());
236 + return result;
117 } 237 }
118 238
119 @Override 239 @Override
120 public boolean equals(Object obj) { 240 public boolean equals(Object obj) {
121 - 241 + if (this == obj) {
122 - if (obj instanceof IpAddress) {
123 - IpAddress other = (IpAddress) obj;
124 -
125 - if (this.version.equals(other.version)
126 - && (Arrays.equals(this.octets, other.octets))) {
127 return true; 242 return true;
128 } 243 }
244 + if (obj == null) {
245 + return false;
246 + }
247 + if (getClass() != obj.getClass()) {
248 + return false;
129 } 249 }
250 + IpAddress other = (IpAddress) obj;
251 + if (netmask != other.netmask) {
130 return false; 252 return false;
131 } 253 }
254 + if (!Arrays.equals(octets, other.octets)) {
255 + return false;
256 + }
257 + if (version != other.version) {
258 + return false;
259 + }
260 + return true;
261 + }
262 +
263 + @Override
264 + /*
265 + * (non-Javadoc)
266 + * format is "x.x.x.x" for non-masked (netmask 0) addresses,
267 + * and "x.x.x.x/y" for masked addresses.
268 + *
269 + * @see java.lang.Object#toString()
270 + */
271 + public String toString() {
272 + final StringBuilder builder = new StringBuilder();
273 + for (final byte b : this.octets) {
274 + if (builder.length() > 0) {
275 + builder.append(".");
276 + }
277 + builder.append(String.format("%d", b & 0xff));
278 + }
279 + if (netmask != DEFAULT_MASK) {
280 + builder.append("/");
281 + builder.append(String.format("%d", netmask));
282 + }
283 + return builder.toString();
284 + }
285 +
132 } 286 }
......
1 package org.onlab.packet; 1 package org.onlab.packet;
2 2
3 import static org.junit.Assert.assertEquals; 3 import static org.junit.Assert.assertEquals;
4 +import static org.junit.Assert.assertTrue;
4 5
5 import java.util.Arrays; 6 import java.util.Arrays;
6 7
...@@ -11,33 +12,65 @@ import com.google.common.testing.EqualsTester; ...@@ -11,33 +12,65 @@ import com.google.common.testing.EqualsTester;
11 12
12 public class IPAddressTest { 13 public class IPAddressTest {
13 14
14 - private static final byte [] BYTES1 = new byte [] {0x0, 0x0, 0x0, 0xa}; 15 + private static final byte [] BYTES1 = new byte [] {0xa, 0x0, 0x0, 0xa};
15 - private static final byte [] BYTES2 = new byte [] {0x0, 0x0, 0x0, 0xb}; 16 + private static final byte [] BYTES2 = new byte [] {0xa, 0x0, 0x0, 0xb};
16 - private static final int INTVAL1 = 10; 17 + private static final int INTVAL1 = 167772170;
17 - private static final int INTVAL2 = 12; 18 + private static final int INTVAL2 = 167772171;
18 - private static final String STRVAL = "0.0.0.11"; 19 + private static final String STRVAL = "10.0.0.12";
20 + private static final int MASK = 16;
19 21
20 @Test 22 @Test
21 public void testEquality() { 23 public void testEquality() {
22 IpAddress ip1 = IpAddress.valueOf(BYTES1); 24 IpAddress ip1 = IpAddress.valueOf(BYTES1);
23 - IpAddress ip2 = IpAddress.valueOf(BYTES2); 25 + IpAddress ip2 = IpAddress.valueOf(INTVAL1);
24 - IpAddress ip3 = IpAddress.valueOf(INTVAL1); 26 + IpAddress ip3 = IpAddress.valueOf(BYTES2);
25 IpAddress ip4 = IpAddress.valueOf(INTVAL2); 27 IpAddress ip4 = IpAddress.valueOf(INTVAL2);
26 IpAddress ip5 = IpAddress.valueOf(STRVAL); 28 IpAddress ip5 = IpAddress.valueOf(STRVAL);
27 29
28 - new EqualsTester().addEqualityGroup(ip1, ip3) 30 + new EqualsTester().addEqualityGroup(ip1, ip2)
29 - .addEqualityGroup(ip2, ip5) 31 + .addEqualityGroup(ip3, ip4)
30 - .addEqualityGroup(ip4) 32 + .addEqualityGroup(ip5)
31 .testEquals(); 33 .testEquals();
34 +
35 + // string conversions
36 + IpAddress ip6 = IpAddress.valueOf(BYTES1, MASK);
37 + IpAddress ip7 = IpAddress.valueOf("10.0.0.10/16");
38 + IpAddress ip8 = IpAddress.valueOf(new byte [] {0xa, 0x0, 0x0, 0xc});
39 + assertEquals("incorrect address conversion", ip6, ip7);
40 + assertEquals("incorrect address conversion", ip5, ip8);
32 } 41 }
33 42
34 @Test 43 @Test
35 public void basics() { 44 public void basics() {
36 - IpAddress ip4 = IpAddress.valueOf(BYTES1); 45 + IpAddress ip1 = IpAddress.valueOf(BYTES1, MASK);
37 - assertEquals("incorrect IP Version", Version.INET, ip4.version()); 46 + final byte [] bytes = new byte [] {0xa, 0x0, 0x0, 0xa};
38 - assertEquals("faulty toOctets()", Arrays.equals( 47 +
39 - new byte [] {0x0, 0x0, 0x0, 0xa}, ip4.toOctets()), true); 48 + //check fields
40 - assertEquals("faulty toInt()", INTVAL1, ip4.toInt()); 49 + assertEquals("incorrect IP Version", Version.INET, ip1.version());
41 - assertEquals("faulty toString()", "0.0.0.10", ip4.toString()); 50 + assertEquals("incorrect netmask", 16, ip1.netmask);
51 + assertTrue("faulty toOctets()", Arrays.equals(bytes, ip1.toOctets()));
52 + assertEquals("faulty toInt()", INTVAL1, ip1.toInt());
53 + assertEquals("faulty toString()", "10.0.0.10/16", ip1.toString());
54 + }
55 +
56 + @Test
57 + public void netmasks() {
58 + // masked
59 + IpAddress ip1 = IpAddress.valueOf(BYTES1, MASK);
60 +
61 + IpAddress host = IpAddress.valueOf("0.0.0.10/16");
62 + IpAddress network = IpAddress.valueOf("10.0.0.0/16");
63 + assertEquals("incorrect host address", host, ip1.host());
64 + assertEquals("incorrect network address", network, ip1.network());
65 + assertEquals("incorrect netmask", "255.255.0.0", ip1.netmask().toString());
66 +
67 + //unmasked
68 + IpAddress ip2 = IpAddress.valueOf(BYTES1);
69 + IpAddress umhost = IpAddress.valueOf("10.0.0.10/0");
70 + IpAddress umnet = IpAddress.valueOf("0.0.0.0/0");
71 + assertEquals("incorrect host address", umhost, ip2.host());
72 + assertEquals("incorrect host address", umnet, ip2.network());
73 + assertTrue("incorrect netmask",
74 + Arrays.equals(IpAddress.ANY, ip2.netmask().toOctets()));
42 } 75 }
43 } 76 }
......