import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.TreeMap;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
/**
* Check whether the current match conflicts with the passed filter match
* This match conflicts with the filter if for at least a MatchType defined
- * in the filter match, the respective MatchFields differ or are not compatible
+ * in the filter match, the respective MatchFields differ or are not
+ * compatible
+ *
+ * In other words the function returns true if the set of packets described
+ * by one match and the set of packets described by the other match are
+ * disjoint. Equivalently, if the intersection of the two sets of packets
+ * described by the two matches is an empty.
*
* For example, Let's suppose the filter has the following MatchFields:
* DL_TYPE = 0x800
- * NW_DST = 172.20.30.110/24
+ * NW_DST = 172.20.30.110/24
*
* while this match has the following MatchFields:
+ * DL_TYPE = 0x800
* NW_DST = 172.20.30.45/24
* TP_DST = 80
*
- * Then the function would return false as the two Match are not conflicting
+ * Then the function would return false as the two Match are not
+ * conflicting.
*
- * Note: the mask value is taken into account only for MatchType.NW_SRC and MatchType.NW_DST
+ * Note: the mask value is taken into account only for MatchType.NW_SRC and
+ * MatchType.NW_DST
*
- * @param match the MAtch describing the filter
- * @return true if the match is conflicting with the filter, false otherwise
+ * @param match
+ * the Match describing the filter
+ * @return true if the set of packets described by one match and the set of
+ * packets described by the other match are disjoint, false
+ * otherwise
*/
public boolean conflictWithFilter(Match filter) {
- // Iterate through the MatchType defined in the filter
- for (MatchType type : filter.getMatchesList()) {
+ return !this.intersetcs(filter);
+ }
+
+ /**
+ * Merge the current Match fields with the fields of the filter Match. A
+ * check is first run to see if this Match is compatible with the filter
+ * Match. If it is not, the merge is not attempted.
+ *
+ * The result is the match object representing the intersection of the set
+ * of packets described by this match with the set of packets described by
+ * the filter match. If the intersection of the two sets is empty, the
+ * return match will be null.
+ *
+ * @param filter
+ * the match with which attempting the merge
+ * @return a new Match object describing the set of packets represented by
+ * the intersection of this and the filter matches. null if the
+ * intersection is empty.
+ */
+ public Match mergeWithFilter(Match filter) {
+ return this.getIntersection(filter);
+ }
+
+ /**
+ * Return the match representing the intersection of the set of packets
+ * described by this match with the set of packets described by the other
+ * match. Such as m.getIntersection(m) == m, m.getIntersection(u) == m and
+ * m.getIntersection(o) == o where u is an empty match (universal set, all
+ * packets) and o is the null match (empty set).
+ *
+ * @param other
+ * the match with which computing the intersection
+ * @return a new Match object representing the intersection of the set of
+ * packets described by this match with the set of packets described
+ * by the other match. null when the intersection is the empty set.
+ */
+ public Match getIntersection(Match other) {
+ // If no intersection, return the empty set
+ if (!this.intersetcs(other)) {
+ return null;
+ }
+ // Check if any of the two is the universal match
+ if (this.getMatches() == 0) {
+ return other.clone();
+ }
+ if (other.getMatches() == 0) {
+ return this.clone();
+ }
+ // Derive the intersection
+ Match intersection = new Match();
+ for (MatchType type : MatchType.values()) {
+ if (this.isAny(type) && other.isAny(type)) {
+ continue;
+ }
if (this.isAny(type)) {
+ intersection.setField(other.getField(type).clone());
+ continue;
+ } else if (other.isAny(type)) {
+ intersection.setField(this.getField(type).clone());
+ continue;
+ }
+ // Either they are equal or it is about IP address
+ switch (type) {
+ // When it is about IP address, take the wider prefix address
+ // between the twos
+ case NW_SRC:
+ case NW_DST:
+ MatchField thisField = this.getField(type);
+ MatchField otherField = other.getField(type);
+ InetAddress thisAddress = (InetAddress) thisField.getValue();
+ InetAddress otherAddress = (InetAddress) otherField.getValue();
+ InetAddress thisMask = (InetAddress) thisField.getMask();
+ InetAddress otherMask = (InetAddress) otherField.getMask();
+
+ int thisMaskLen = (thisMask == null) ? ((thisAddress instanceof Inet4Address) ? 32 : 128) : NetUtils
+ .getSubnetMaskLength(thisMask);
+ int otherMaskLen = (otherMask == null) ? ((otherAddress instanceof Inet4Address) ? 32 : 128) : NetUtils
+ .getSubnetMaskLength(otherMask);
+ if (thisMaskLen < otherMaskLen) {
+ intersection.setField(new MatchField(type, NetUtils.getSubnetPrefix(otherAddress, otherMaskLen),
+ otherMask));
+ } else {
+ intersection.setField(new MatchField(type, NetUtils.getSubnetPrefix(thisAddress, thisMaskLen),
+ thisMask));
+ }
+ break;
+ default:
+ // this and other match field are equal for this type, pick this
+ // match field
+ intersection.setField(this.getField(type).clone());
+ }
+ }
+ return intersection;
+ }
+
+ /**
+ * Checks whether the intersection of the set of packets described by this
+ * match with the set of packets described by the other match is non empty
+ *
+ * For example, if this match is: DL_SRC = 00:cc:bb:aa:11:22
+ *
+ * and the other match is: DL_TYPE = 0x800 NW_SRC = 1.2.3.4
+ *
+ * then their respective matching packets set intersection is non empty:
+ * DL_SRC = 00:cc:bb:aa:11:22 DL_TYPE = 0x800 NW_SRC = 1.2.3.4
+ *
+ * @param other
+ * the other match with which testing the intersection
+ * @return true if the intersection of the respective matching packets sets
+ * is non empty
+ */
+ public boolean intersetcs(Match other) {
+ // No intersection with the empty set
+ if (other == null) {
+ return false;
+ }
+ // Always intersection with the universal set
+ if (this.getMatches() == 0 || other.getMatches() == 0) {
+ return true;
+ }
+ // Iterate through the MatchType defined in the filter
+ for (MatchType type : MatchType.values()) {
+ if (this.isAny(type) || other.isAny(type)) {
continue;
}
MatchField thisField = this.getField(type);
- MatchField filterField = filter.getField(type);
+ MatchField otherField = other.getField(type);
switch (type) {
case DL_SRC:
case DL_DST:
- if (Arrays.equals((byte[]) thisField.getValue(),
- (byte[]) filterField.getValue())) {
+ if (!Arrays.equals((byte[]) thisField.getValue(), (byte[]) otherField.getValue())) {
return false;
}
break;
case NW_SRC:
case NW_DST:
InetAddress thisAddress = (InetAddress) thisField.getValue();
- InetAddress filterAddress = (InetAddress) filterField
- .getValue();
+ InetAddress otherAddress = (InetAddress) otherField.getValue();
// Validity check
- if (thisAddress instanceof Inet4Address
- && filterAddress instanceof Inet6Address
- || thisAddress instanceof Inet6Address
- && filterAddress instanceof Inet4Address) {
- return true;
+ if (thisAddress instanceof Inet4Address && otherAddress instanceof Inet6Address
+ || thisAddress instanceof Inet6Address && otherAddress instanceof Inet4Address) {
+ return false;
}
InetAddress thisMask = (InetAddress) thisField.getMask();
- InetAddress filterMask = (InetAddress) filterField.getMask();
- // thisAddress has to be in same subnet of filterAddress
- if (NetUtils.inetAddressConflict(thisAddress, filterAddress,
- thisMask, filterMask)) {
- return true;
+ InetAddress otherMask = (InetAddress) otherField.getMask();
+ if (NetUtils.inetAddressConflict(thisAddress, otherAddress, thisMask, otherMask)
+ && NetUtils.inetAddressConflict(otherAddress, thisAddress, otherMask, thisMask)) {
+ return false;
}
break;
default:
- if (!thisField.getValue().equals(filterField.getValue())) {
- return true;
- }
- }
- //TODO: check v4 v6 incompatibility
- }
- return false;
- }
-
- /**
- * Merge the current Match fields with the fields of the filter Match
- * A check is first run to see if this Match is compatible with the
- * filter Match. If it is not, the merge is not attempted.
- *
- *
- * @param filter
- * @return
- */
- public Match mergeWithFilter(Match filter) {
- if (!this.conflictWithFilter(filter)) {
- /*
- * No conflict with the filter
- * We can copy over the fields which this match does not have
- */
- for (MatchType type : filter.getMatchesList()) {
- if (this.isAny(type)) {
- this.setField(filter.getField(type).clone());
+ if (!thisField.getValue().equals(otherField.getValue())) {
+ return false;
}
}
}
- return this;
+ return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
- result = prime * result + ((fields == null) ? 0 : fields.hashCode());
+ if (this.fields == null) {
+ result = prime * result;
+ } else {
+ // use a tree map as the order of hashMap is not guaranteed.
+ // 2 Match objects with fields in different order are still equal.
+ // Hence the hashCode should be the same too.
+ TreeMap<MatchType, MatchField> tm = new TreeMap<MatchType, MatchField>(this.fields);
+ for (MatchType field : tm.keySet()) {
+ MatchField f = tm.get(field);
+ int fieldHashCode = (field==null ? 0 : field.calculateConsistentHashCode()) ^
+ (f==null ? 0 : f.hashCode());
+ result = prime * result + fieldHashCode;
+ }
+ }
result = prime * result + matches;
return result;
}
@Override
public String toString() {
- return "Match[" + fields.values() + "]";
+ StringBuilder builder = new StringBuilder();
+ builder.append("Match [fields=");
+ builder.append(fields);
+ builder.append(", matches=");
+ builder.append(matches);
+ builder.append("]");
+ return builder.toString();
}
+
}