Binary Addition
Breaking down an IPv4 address (and the netmask) to a binary string, implementing 1-bit adders, and then converting back to an IPv4 address seems to me to be doing things the hard way.
Granted, there really isn't an 'easy' way to go about manipulating IP addresses, but the route that I chose to go down when I implemented a set of IP Address utility methods (it was one of my first big projects as a professional developer, when script statement limits in Apex were still a thing) was to work in integers as much as possible.
I might go about it slightly differently if I were to re-implement things today, but the starting point I used was to convert an IPv4 address, represented in dot-decimal notation as a string, to a List<Integer>
.
That bit is simple enough.
// Assume ipString is declared elsewhere, and is of the form '192.168.0.0'
// split takes a regexp, so to split on the period/full-stop literal, we need to escape.
// Working in apex, we also need to escape the backslash.
List<String> stringOctets = ipString.split('\\.')
List<Integer> octets = new List<Integer>();
for(String octet :stringOctets){
octets.add(Integer.valueOf(octet));
}
// octets contains [192, 168, 0, 0]
Once both the given IPv4 address and the netmask are integer lists, getting the first address in the range is as simple as stepping through each octet (of both the IPv4 address, and the netmask), and performing a bitwise-and.
// Assume that netmask and ipAddress are both List<Integer>s
List<Integer> firstAddressInRange = new List<Integer>();
for(Integer i = 0; i < netmask.size(); i++){
// A single ampersand (&) is the bitwise-and operator
firstAddressInRange.add(netmask[i] & ipAddress[i]);
}
The last address in a range is a bit trickier. It's the bitwise-or of the given ip address together with the inverse of the netmask. There isn't a bitwise-not that I'm aware of, so we'll need to implement invert()
ourselves. Luckily, it's easy enough.
// Since we can't say that we want to use an 8-bit unsigned integer, we'll
// need to do a little bit of work to ensure that every bit after the 8 least-significant
// bits are 0.
// If we don't, we risk things going horribly wrong in other bitwise operations
private static Integer invert(Integer original){
// 255 - original gives us the inverse
// bitwise-and with 255 ensures that everything beyond the first 8 bits is 0
return 255 & (255 - original);
}
Now that we have our invert()
method, we can find the last address in a range
// Assume that netmask and ipAddress are both List<Integer>s
List<Integer> lastAddressInRange = new List<Integer>();
for(Integer i = 0; i < netmask.size(); i++){
// A single pipe (|) is the bitwise-or operator
lastAddressInRange.add(invert(netmask[i]) | ipAddress[i]);
}
From here, you could add handling for IPv6 without too much trouble. The algorithms for finding the first and last addresses in the range remain the same. The invert
method would simply need to have 255
replaced with 65535
. The biggest pain, really, would be that you need to convert from hexadecimal to decimal (and then back again when you wanted to display the resulting IPv6 address).
If you need anything other than the first and last addresses in a range, then implementing add()
and subtract()
methods would be helpful. I won't provide an implementation of those (unless asked), but the algorithm is simple enough.
- Add the number passed to 'add()' to the last octet
- Iterate over the octets in reverse (i.e. if the IP address is 192.168.0.1, start with the '1' octet)
- Perform integer division to see how much needs to be added to the next octet
- Set current octet's value to (current octet % 256)
Subtracting is nothing more than adding a negative, but you'd need to change the logic a bit to support borrowing from greater octets.