
inet.ipaddr.PrefixBlockAllocator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ipaddress Show documentation
Show all versions of ipaddress Show documentation
Library for handling IP addresses, both IPv4 and IPv6
/*
* Copyright 2022 Sean C Foley
*
* 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
* or at
* https://github.com/seancfoley/IPAddress/blob/master/LICENSE
*
* 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 inet.ipaddr;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import inet.ipaddr.IPAddress.IPVersion;
import inet.ipaddr.format.AddressItem;
/**
*
* Allocates blocks of the desired size from a set of seed blocks provided to it previously for allocation.
*
* Once a prefix block allocator of generic type IPAddress has been provided with either an IPv4 or IPv6 address or subnet for allocation,
* it can only be used with the same address version from that point onwards.
* In other words, it can allocate either IPv4 or IPv6 blocks, but not both.
*
* @author scfoley
*
* @param the address type
*/
public class PrefixBlockAllocator {
private static final IPAddress emptyBlocks[] = new IPAddress[0];
private IPVersion version;
private ArrayDeque blocks[];
int reservedCount, totalBlockCount;
/**
* Returns the count of available blocks in this allocator.
*/
public int getBlockCount() {
return totalBlockCount;
}
/**
* Returns the IP version of the available blocks in the allocator,
* which is determined by the version of the first block made available to the allocator.
*/
public IPVersion getVersion() {
return version;
}
/**
* Returns the total of the count of all individual addresses available in this allocator,
* which is the total number of individual addresses in all the blocks.
*/
public BigInteger getTotalCount() {
if(getBlockCount() == 0) {
return BigInteger.ZERO;
}
BigInteger result = BigInteger.ZERO;
if(blocks == null) {
return result;
}
IPVersion version = this.version;
for(int i = blocks.length - 1; i >= 0; i--) {
ArrayDeque rowBlocks = blocks[i];
if(rowBlocks == null) {
continue;
}
int blockCount = rowBlocks.size();
if(blockCount != 0) {
BigInteger size = AddressItem.getBlockSize(IPAddress.getBitCount(version) - i);
size = size.multiply(BigInteger.valueOf(blockCount));
result = result.add(size);
}
}
return result;
}
/**
* Sets the additional number of addresses to be included in any size allocation.
* Any request for a block of a given size will adjust that size by the given number.
* This can be useful when the size requests do not include the count of additional addresses that must be included in every block.
* For IPv4, it is common to reserve two addresses, the network and broadcast addresses.
* If the reservedCount is negative, then every request will be shrunk by that number, useful for cases where
* insufficient space requires that all subnets be reduced in size by an equal number.
*/
public void setReserved(int reservedCount) {
this.reservedCount = reservedCount;
}
/**
* Returns the reserved count. Use setReserved to change the reserved count.
*/
public int getReserved() {
return reservedCount;
}
void insertBlocks(E newBlocks[]) {
for(int i = 0; i < newBlocks.length; i++) {
E newBlock = newBlocks[i];
int prefLen = newBlock.getPrefixLength();
ArrayDeque existing = blocks[prefLen];
if(existing == null) {
blocks[prefLen] = existing = new ArrayDeque();
}
existing.addLast(newBlock);
totalBlockCount++;
}
}
/**
* Provides the given blocks to the allocator for allocating.
*/
@SuppressWarnings("unchecked")
public void addAvailable(E ...newBlocks) {
if(newBlocks.length == 0) {
return;
}
IPVersion version = this.version;
for(int i = 0; i < newBlocks.length; i++) {
E block = newBlocks[i];
if(version == null) {
this.version = version = block.getIPVersion();
} else if(!version.equals(block.getIPVersion())) {
throw new IncompatibleAddressException(block, "ipaddress.error.typeMismatch");
}
}
if(blocks == null){
int size = IPAddress.getBitCount(version) + 1;
blocks = new ArrayDeque[size];
} else if(totalBlockCount > 0){
ArrayList newList = new ArrayList(newBlocks.length + totalBlockCount);
for(int i = 0; i < blocks.length; i++) {
if(blocks[i] != null) {
newList.addAll(blocks[i]);
blocks[i].clear();
}
}
newList.addAll(Arrays.asList(newBlocks));
newBlocks = newList.toArray((E[]) new IPAddress[newList.size()]);
}
newBlocks = (E[]) newBlocks[0].mergeToPrefixBlocks(newBlocks);
insertBlocks(newBlocks);
}
/**
* Returns a list of all the blocks available for allocating in the allocator.
*/
@SuppressWarnings("unchecked")
public E[] getAvailable() {
if(totalBlockCount == 0) {
return (E[]) emptyBlocks;
}
ArrayList newList = new ArrayList(totalBlockCount);
for(int i = 0; i < blocks.length; i++) {
if(blocks[i] != null) {
newList.addAll(blocks[i]);
}
}
return newList.toArray((E[]) new IPAddress[newList.size()]);
}
/**
* Allocates a block with the given bit-length,
* the bit-length being the number of bits extending beyond the prefix length,
* or nil if no such block is available in the allocator.
* The reserved count is ignored when allocating by bit-length.
*/
@SuppressWarnings("unchecked")
public E allocateBitLength(int bitLength) {
if(totalBlockCount == 0) {
return null;
}
int newPrefixBitCount = IPAddress.getBitCount(version) - bitLength;
E block = null;
int i = newPrefixBitCount;
for(; i >= 0; i--) {
ArrayDeque blockRow = blocks[i];
if (blockRow != null && blockRow.size() > 0) {
block = blockRow.removeFirst();
totalBlockCount--;
break;
}
}
if(block == null || !block.isMultiple() || i == newPrefixBitCount) {
return block;
}
// block is larger than needed, adjust it
E adjustedBlock = (E) block.setPrefixLength(newPrefixBitCount, false);
Iterator blockIterator = (Iterator) adjustedBlock.prefixBlockIterator();
E result = blockIterator.next();
// now we add the remaining from the block iterator back into the list
IPAddressSeqRange range = blockIterator.next().getLower().spanWithRange(block.getUpper());
insertBlocks((E[]) range.spanWithPrefixBlocks());
return result;
}
/**
* Returns a block of sufficient size,
* the size indicating the number of distinct addresses required in the block.
* AllocateSize returns null if no such block is available in the allocator,
* or if the size required is zero or negative.
* The returned block will be able to accommodate sizeRequired hosts as well as the reserved count, if any.
* @param sizeRequired
* @return
*/
public E allocateSize(long sizeRequired) {
int bitsRequired;
if(reservedCount < 0) {
long adjustment = -reservedCount;
if(adjustment >= sizeRequired) {
return null;
}
sizeRequired -= adjustment;
bitsRequired = AddressItem.getBitsForCount(sizeRequired);
} else if(Long.MAX_VALUE - reservedCount < sizeRequired) {
// 63 bits holds Long.MAX_VALUE + 1 addresses.
// So we need to know how much total size of sizeRequired + reservedCount exceeds Long.MAX_VALUE + 1
long extra = sizeRequired - (Long.MAX_VALUE - reservedCount) - 1;
if(extra == 0) {
bitsRequired = 63;
} else {
bitsRequired = AddressItem.getBitsForCount(extra) + 63;
}
} else {
sizeRequired += reservedCount;
Integer bRequired = AddressItem.getBitsForCount(sizeRequired);
if(bRequired == null) {
return null;
}
bitsRequired = bRequired;
}
return allocateBitLength(bitsRequired);
}
/**
*
* Represents a block of addresses allocated for assignment to hosts.
* @author scfoley
*
* @param the address type
*/
public static class AllocatedBlock {
/**
* The number of requested addresses.
*/
public final BigInteger blockSize;
/**
* The allocated prefix block.
*/
public final E block;
/**
* The number of reserved addresses.
*/
public final int reservedCount;
AllocatedBlock(E block, BigInteger blockSize, int reservedCount) {
this.block = block;
this.blockSize = blockSize;
this.reservedCount = reservedCount;
}
/**
* Returns the total number of addresses within the block.
* blockSize + reservedCount will not exceed this value.
* @return
*/
public BigInteger getCount() {
return block.getCount();
}
/**
* Returns a string representation of the allocated block.
*/
@Override
public String toString() {
if( reservedCount > 0) {
return block + " for " + blockSize + " hosts and " +
reservedCount + " reserved addresses";
}
return block + " for " + blockSize + " hosts";
}
}
/**
* Returns multiple blocks of sufficient size for the given size required,
* or null if there is insufficient space in the allocator.
* The reserved count, if any, will be added to the required sizes.
*/
@SuppressWarnings("unchecked")
public AllocatedBlock[] allocateSizes(long ...blockSizes) {
List sizes = new ArrayList<>(blockSizes.length);
for(int i = 0; i < blockSizes.length; i++) {
sizes.add(blockSizes[i]);
}
// sort required subnets by size, largest first
sizes.sort((one, two) -> {
long diff = two - one;
if(diff < 0) {
return -1;
} else if (diff > 0) {
return 1;
}
return 0;
});
ArrayList> result = new ArrayList<>();
for(int i = 0; i < sizes.size(); i++) {
long blockSize = sizes.get(i);
if(reservedCount < 0 && -reservedCount >= blockSize) {
// size zero
continue;
}
E allocated = allocateSize(blockSize);
if(allocated == null) {
return null;
}
result.add(new AllocatedBlock(allocated, BigInteger.valueOf(blockSize), reservedCount));
}
return result.toArray(new AllocatedBlock[result.size()]);
}
/**
* Returns multiple blocks of the given bit-lengths,
* or null if there is insufficient space in the allocator.
* The reserved count is ignored when allocating by bit-length.
*/
@SuppressWarnings("unchecked")
public AllocatedBlock[] allocateBitLengths(int ...bitLengths) {
List lengths = new ArrayList<>(bitLengths.length);
for(int i = 0; i < bitLengths.length; i++) {
lengths.add(bitLengths[i]);
}
// sort required subnets by size, largest first
lengths.sort((one, two) -> {
long diff = two - one;
if(diff < 0) {
return -1;
} else if (diff > 0) {
return 1;
}
return 0;
});
ArrayList> result = new ArrayList<>();
for(int i = 0; i < lengths.size(); i++) {
int bitLength = lengths.get(i);
E allocated = allocateBitLength(bitLength);
if(allocated == null) {
return null;
}
BigInteger blockSize = AddressItem.getBlockSize(bitLength);
result.add(new AllocatedBlock(allocated, blockSize, 0));
}
return result.toArray(new AllocatedBlock[result.size()]);
}
/**
* Returns a string showing the counts of available blocks for each prefix size in the allocator.
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
IPVersion version = this.version;
boolean hasBlocks = false;
builder.append("available blocks:\n");
if(blocks != null) {
for(int i = blocks.length - 1; i >= 0; i--) {
ArrayDeque row = blocks[i];
if(row != null && row.size() != 0) {
int blockCount = row.size();
BigInteger size = AddressItem.getBlockSize(IPAddress.getBitCount(version) - i);
builder.append(blockCount);
if(blockCount == 1) {
builder.append(" block");
} else {
builder.append(" blocks");
}
builder.append(" with prefix length ").append(i).
append(" size ").append(size).append("\n");
hasBlocks = true;
}
}
}
if(!hasBlocks) {
builder.append("none\n");
}
return builder.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy