package org.opendaylight.controller.sal.match;
+import java.io.Serializable;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
-import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.opendaylight.controller.sal.utils.EtherTypes;
import org.opendaylight.controller.sal.utils.IPProtocols;
import org.opendaylight.controller.sal.utils.NetUtils;
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
-public class Match implements Cloneable {
- private static final Map<MatchType, MatchType> reversableMatches;
+public class Match implements Cloneable, Serializable {
+ private static final long serialVersionUID = 1L;
+ private static final Map<MatchType, MatchType> reversableMatches;
static {
Map<MatchType, MatchType> map = new HashMap<MatchType, MatchType>();
map.put(MatchType.DL_SRC, MatchType.DL_DST);
* Generic setter for frame/packet/message's header fields against which to match
* Note: For MAC addresses, please pass the cloned value to this function
*
- * @param type packet's header field type
- * @param value field's value to assign to the match
- * @param mask field's bitmask to apply to the match (has to be of the same class type of value)
+ * @param type packet's header field type
+ * @param value field's value to assign to the match
+ * @param mask field's bitmask to apply to the match (has to be of the same class type of value)
*/
public void setField(MatchType type, Object value, Object mask) {
MatchField field = new MatchField(type, value, mask);
* Generic setter for frame/packet/message's header fields against which to match
* Note: For MAC addresses, please pass the cloned value to this function
*
- * @param type packet's header field type
- * @param value field's value to assign to the match
+ * @param type packet's header field type
+ * @param value field's value to assign to the match
*/
public void setField(MatchType type, Object value) {
MatchField field = new MatchField(type, value);
/**
* Generic getter for fields against which the match is programmed
*
- * @param type frame/packet/message's header field type
+ * @param type frame/packet/message's header field type
* @return
*/
public MatchField getField(MatchType type) {
/**
* Returns the list of MatchType fields the match is set for
*
- * @return List of individual MatchType fields.
+ * @return List of individual MatchType fields.
*/
public List<MatchType> getMatchesList() {
return new ArrayList<MatchType>(fields.keySet());
/**
* Returns the list of MatchFields the match is set for
*
- * @return List of individual MatchField values.
+ * @return List of individual MatchField values.
*/
@XmlElement(name="matchField")
public List<MatchField> getMatchFields() {
- return new ArrayList<MatchField>(fields.values());
+ return new ArrayList<MatchField>(fields.values());
}
-
+
/**
* Returns whether this match is for an IPv6 flow
*/
Match cloned = null;
try {
cloned = (Match) super.clone();
+ cloned.matches = matches;
cloned.fields = new HashMap<MatchType, MatchField>();
for (Entry<MatchType, MatchField> entry : this.fields.entrySet()) {
cloned.fields.put(entry.getKey(), entry.getValue().clone());
Match reverse = this.clone();
// Flip symmetric fields
- for (Map.Entry<MatchType, MatchType> entry : Match.reversableMatches
- .entrySet()) {
+ for (Map.Entry<MatchType, MatchType> entry : Match.reversableMatches.entrySet()) {
MatchType from = entry.getKey();
MatchType to = entry.getValue();
if (this.isPresent(from)) {
- reverse.setField(to, this.getField(from).getValue(), this
- .getField(from).getMask());
+ reverse.setField(to, this.getField(from).getValue(), this.getField(from).getMask());
+ if (!this.isPresent(to)) {
+ reverse.clearField(from);
+ }
}
}
/**
* 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) filter.getField(type)
- .getMask();
- InetAddress filterMask = (InetAddress) filter.getField(type)
- .getMask();
- // thisAddress has to be in same subnet of filterAddress
- if (NetUtils.inetAddressConflict(thisAddress, filterAddress,
- thisMask, filterMask)) {
- return true;
+ InetAddress thisMask = (InetAddress) thisField.getMask();
+ 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() {
- return HashCodeBuilder.reflectionHashCode(this);
+ final int prime = 31;
+ int result = 1;
+ if (this.fields == null) {
+ result = prime * result;
+ } else {
+ int sum = 0;
+ for (MatchType field : this.fields.keySet()) {
+ MatchField f = this.fields.get(field);
+ sum = sum + ((field==null ? 0 : field.calculateConsistentHashCode()) ^
+ (f==null ? 0 : f.hashCode()));
+ }
+ result = prime * result + sum;
+ }
+ result = prime * result + matches;
+ return result;
}
@Override
public boolean equals(Object obj) {
- return EqualsBuilder.reflectionEquals(this, obj);
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Match other = (Match) obj;
+ if (fields == null) {
+ if (other.fields != null) {
+ return false;
+ }
+ } else if (!fields.equals(other.fields)) {
+ return false;
+ }
+ if (matches != other.matches) {
+ return false;
+ }
+ return true;
}
@Override