1 /*
2 * Copyright (c) 2002-2025 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.util;
16
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19
20 /**
21 * Performs subnet calculations given a network address and a subnet mask.
22 * Inspired by org.apache.commons.net.util.SubnetUtils.
23 *
24 * @see "http://www.faqs.org/rfcs/rfc1519.html"
25 *
26 * @author Ronald Brill
27 */
28 public class SubnetUtils {
29
30 private static final String IP_ADDRESS = "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})";
31 private static final Pattern ADDRESS_PATTERN = Pattern.compile(IP_ADDRESS);
32 private static final String PARSE_FAIL = "Could not parse [%s]";
33 private static final long UNSIGNED_INT_MASK = 0x0FFFFFFFFL;
34
35 private final int netmask_;
36 private final int address_;
37 private final int network_;
38 private final int broadcast_;
39
40 /**
41 * Constructs an instance from a dotted decimal address and a dotted decimal mask.
42 *
43 * @param address An IP address, e.g. "192.168.0.1"
44 * @param mask A dotted decimal netmask e.g. "255.255.0.0"
45 * @throws IllegalArgumentException if the address or mask is invalid, i.e. does not match n.n.n.n where n=1-3 decimal digits and the mask is not all zeros
46 */
47 public SubnetUtils(final String address, final String mask) {
48 address_ = toInteger(address);
49 netmask_ = toInteger(mask);
50
51 if ((netmask_ & -netmask_) - 1 != ~netmask_) {
52 throw new IllegalArgumentException(String.format(PARSE_FAIL, mask));
53 }
54
55 network_ = address_ & netmask_;
56 broadcast_ = network_ | ~netmask_;
57 }
58
59 /*
60 * Extracts the components of a dotted decimal address and pack into an integer using a regex match
61 */
62 private static int matchAddress(final Matcher matcher) {
63 int addr = 0;
64 for (int i = 1; i <= 4; ++i) {
65 final int n = rangeCheck(Integer.parseInt(matcher.group(i)), 0, 255);
66 addr |= (n & 0xff) << 8 * (4 - i);
67 }
68 return addr;
69 }
70
71 /*
72 * Checks integer boundaries. Checks if a value x is in the range [begin,end]. Returns x if it is in range, throws an exception otherwise.
73 */
74 private static int rangeCheck(final int value, final int begin, final int end) {
75 // (begin,end]
76 if (value >= begin && value <= end) {
77 return value;
78 }
79 throw new IllegalArgumentException("Value [" + value + "] not in range [" + begin + "," + end + "]");
80 }
81
82 /*
83 * Converts a dotted decimal format address to a packed integer format
84 */
85 private static int toInteger(final String address) {
86 final Matcher matcher = ADDRESS_PATTERN.matcher(address);
87 if (matcher.matches()) {
88 return matchAddress(matcher);
89 }
90 throw new IllegalArgumentException(String.format(PARSE_FAIL, address));
91 }
92
93 private long broadcastLong() {
94 return broadcast_ & UNSIGNED_INT_MASK;
95 }
96
97 private int high() {
98 return broadcastLong() - networkLong() > 1 ? broadcast_ - 1 : 0;
99 }
100
101 private int low() {
102 return broadcastLong() - networkLong() > 1 ? network_ + 1 : 0;
103 }
104
105 /** Long versions of the values (as unsigned int) which are more suitable for range checking. */
106 private long networkLong() {
107 return network_ & UNSIGNED_INT_MASK;
108 }
109
110 /**
111 * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet.
112 * This excludes the network and broadcast addresses by default.
113 *
114 * @param address the address to check
115 * @return true if it is in range
116 */
117 private boolean isInRange(final int address) {
118 if (address == 0) {
119 return false;
120 }
121 final long addLong = address & UNSIGNED_INT_MASK;
122 final long lowLong = low() & UNSIGNED_INT_MASK;
123 final long highLong = high() & UNSIGNED_INT_MASK;
124 return addLong >= lowLong && addLong <= highLong;
125 }
126
127 /**
128 * Tests if the parameter <code>address</code> is in the range of usable endpoint addresses for this subnet.
129 * This excludes the network and broadcast addresses.
130 *
131 * @param address A dot-delimited IPv4 address, e.g. "192.168.0.1"
132 * @return true if in range, false otherwise
133 */
134 public boolean isInRange(final String address) {
135 return isInRange(toInteger(address));
136 }
137 }