package org.batfish.datamodel; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.BitSet; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonValue; public class PrefixSpace implements Serializable { private static class BitTrie implements Serializable { /** * */ private static final long serialVersionUID = 1L; private BitTrieNode _root; public BitTrie() { _root = new BitTrieNode(); } public void addPrefixRange(PrefixRange prefixRange) { Prefix prefix = prefixRange.getPrefix(); int prefixLength = prefix.getPrefixLength(); BitSet bits = getAddressBits(prefix.getAddress()); _root.addPrefixRange(prefixRange, bits, prefixLength, 0); } public void addTrieNodeSpace(BitTrieNode node) { if (node._left != null) { addTrieNodeSpace(node._left); } if (node._right != null) { addTrieNodeSpace(node._right); } for (PrefixRange prefixRange : node._prefixRanges) { addPrefixRange(prefixRange); } } public boolean containsPrefixRange(PrefixRange prefixRange) { Prefix prefix = prefixRange.getPrefix(); int prefixLength = prefix.getPrefixLength(); BitSet bits = getAddressBits(prefix.getAddress()); return _root.containsPrefixRange(prefixRange, bits, prefixLength, 0); } public Set<PrefixRange> getPrefixRanges() { Set<PrefixRange> prefixRanges = new HashSet<>(); _root.collectPrefixRanges(prefixRanges); return prefixRanges; } } private static class BitTrieNode implements Serializable { /** * */ private static final long serialVersionUID = 1L; private BitTrieNode _left; private Set<PrefixRange> _prefixRanges; private BitTrieNode _right; public BitTrieNode() { _prefixRanges = new HashSet<>(); } public void addPrefixRange(PrefixRange prefixRange, BitSet bits, int prefixLength, int depth) { for (PrefixRange nodeRange : _prefixRanges) { if (nodeRange.includesPrefixRange(prefixRange)) { return; } } if (prefixLength == depth) { _prefixRanges.add(prefixRange); prune(prefixRange); } else { boolean currentBit = bits.get(depth); if (currentBit) { if (_right == null) { _right = new BitTrieNode(); } _right.addPrefixRange(prefixRange, bits, prefixLength, depth + 1); } else { if (_left == null) { _left = new BitTrieNode(); } _left.addPrefixRange(prefixRange, bits, prefixLength, depth + 1); } } } public void collectPrefixRanges(Set<PrefixRange> prefixRanges) { prefixRanges.addAll(_prefixRanges); if (_left != null) { _left.collectPrefixRanges(prefixRanges); } if (_right != null) { _right.collectPrefixRanges(prefixRanges); } } public boolean containsPrefixRange(PrefixRange prefixRange, BitSet bits, int prefixLength, int depth) { for (PrefixRange nodeRange : _prefixRanges) { if (nodeRange.includesPrefixRange(prefixRange)) { return true; } } if (prefixLength == depth) { return false; } else { boolean currentBit = bits.get(depth); if (currentBit) { if (_right == null) { return false; } else { return _right.containsPrefixRange(prefixRange, bits, prefixLength, depth + 1); } } else { if (_left == null) { return false; } else { return _left.containsPrefixRange(prefixRange, bits, prefixLength, depth + 1); } } } } private boolean isEmpty() { return _left == null && _right == null && _prefixRanges.isEmpty(); } private void prune(PrefixRange prefixRange) { if (_left != null) { _left.prune(prefixRange); if (_left.isEmpty()) { _left = null; } } if (_right != null) { _right.prune(prefixRange); if (_right.isEmpty()) { _right = null; } } Set<PrefixRange> oldPrefixRanges = new HashSet<>(); oldPrefixRanges.addAll(_prefixRanges); for (PrefixRange oldPrefixRange : oldPrefixRanges) { if (!prefixRange.equals(oldPrefixRange) && prefixRange.includesPrefixRange(oldPrefixRange)) { _prefixRanges.remove(oldPrefixRange); } } } } /** * */ private static final long serialVersionUID = 1L; private static BitSet getAddressBits(Ip address) { int addressAsInt = (int) (address.asLong()); ByteBuffer b = ByteBuffer.allocate(4); b.order(ByteOrder.LITTLE_ENDIAN); b.putInt(addressAsInt); BitSet bitsWithHighestMostSignificant = BitSet.valueOf(b.array()); BitSet bits = new BitSet(Prefix.MAX_PREFIX_LENGTH); for (int i = Prefix.MAX_PREFIX_LENGTH - 1, j = 0; i >= 0; i--, j++) { bits.set(j, bitsWithHighestMostSignificant.get(i)); } return bits; } private transient ConcurrentMap<Prefix, Boolean> _cache; private BitTrie _trie; public PrefixSpace() { _trie = new BitTrie(); _cache = new ConcurrentHashMap<>(); } @JsonCreator public PrefixSpace(Set<PrefixRange> prefixRanges) { this(); for (PrefixRange prefixRange : prefixRanges) { _trie.addPrefixRange(prefixRange); } } public void addPrefix(Prefix prefix) { addPrefixRange(PrefixRange.fromPrefix(prefix)); } public void addPrefixRange(PrefixRange prefixRange) { _trie.addPrefixRange(prefixRange); } public void addSpace(PrefixSpace prefixSpace) { _trie.addTrieNodeSpace(prefixSpace._trie._root); } public boolean containsPrefix(Prefix prefix) { if (_cache.containsKey(prefix)) { return _cache.get(prefix); } else { boolean contained = containsPrefixRange( PrefixRange.fromPrefix(prefix)); _cache.put(prefix, contained); return contained; } } public boolean containsPrefixRange(PrefixRange prefixRange) { return _trie.containsPrefixRange(prefixRange); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } return getPrefixRanges().equals(((PrefixSpace) obj).getPrefixRanges()); } @JsonValue public Set<PrefixRange> getPrefixRanges() { return _trie.getPrefixRanges(); } @Override public int hashCode() { return getPrefixRanges().hashCode(); } public PrefixSpace intersection(PrefixSpace intersectSpace) { PrefixSpace newSpace = new PrefixSpace(); Set<PrefixRange> intersectRanges = intersectSpace.getPrefixRanges(); for (PrefixRange intersectRange : intersectRanges) { if (containsPrefixRange(intersectRange)) { newSpace.addPrefixRange(intersectRange); } } return newSpace; } @JsonIgnore public boolean isEmpty() { return _trie._root.isEmpty(); } public boolean overlaps(PrefixSpace intersectSpace) { PrefixSpace intersection = intersection(intersectSpace); return !intersection.isEmpty(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); _cache = new ConcurrentHashMap<>(); } @Override public String toString() { return getPrefixRanges().toString(); } }