blob: d9d9d0e1621dea3e9258f7f2018e9b89e29de7e7 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* 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
*
* 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 org.onosproject.bmv2.api.runtime;
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.tuple.Pair;
import org.onlab.util.KryoNamespace;
import org.onosproject.bmv2.api.context.Bmv2Configuration;
import org.onosproject.bmv2.api.context.Bmv2FieldTypeModel;
import org.onosproject.bmv2.api.context.Bmv2HeaderModel;
import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
import org.onosproject.net.flow.AbstractExtension;
import org.onosproject.net.flow.criteria.ExtensionSelector;
import org.onosproject.net.flow.criteria.ExtensionSelectorType;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import static com.google.common.base.Preconditions.*;
import static org.onlab.util.ImmutableByteSequence.copyFrom;
import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
/**
* Extension selector for BMv2 used as a wrapper for multiple BMv2 match parameters. Match parameters are
* encoded using a map where the keys are expected to be field names formatted as {@code headerName.fieldName}
* (e.g. {@code ethernet.dstAddr}).
*/
@Beta
public final class Bmv2ExtensionSelector extends AbstractExtension implements ExtensionSelector {
private final KryoNamespace appKryo = new KryoNamespace.Builder()
.register(HashMap.class)
.register(Bmv2MatchParam.class)
.register(Bmv2ExactMatchParam.class)
.register(Bmv2TernaryMatchParam.class)
.register(Bmv2LpmMatchParam.class)
.register(Bmv2ValidMatchParam.class)
.build();
private Map<String, Bmv2MatchParam> parameterMap;
/**
* Creates a new BMv2 extension selector for the given match parameters map.
*
* @param paramMap a map
*/
private Bmv2ExtensionSelector(Map<String, Bmv2MatchParam> paramMap) {
this.parameterMap = paramMap;
}
/**
* Returns the match parameters map of this selector.
*
* @return a match parameter map
*/
public Map<String, Bmv2MatchParam> parameterMap() {
return parameterMap;
}
@Override
public ExtensionSelectorType type() {
return ExtensionSelectorType.ExtensionSelectorTypes.BMV2_MATCH_PARAMS.type();
}
@Override
public byte[] serialize() {
return appKryo.serialize(parameterMap);
}
@Override
public void deserialize(byte[] data) {
this.parameterMap = appKryo.deserialize(data);
}
@Override
public int hashCode() {
return Objects.hashCode(parameterMap);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final Bmv2ExtensionSelector other = (Bmv2ExtensionSelector) obj;
return Objects.equal(this.parameterMap, other.parameterMap);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("parameterMap", parameterMap)
.toString();
}
/**
* Returns a new, empty BMv2 extension selector.
*
* @return a BMv2 extension treatment
*/
public static Bmv2ExtensionSelector empty() {
return new Bmv2ExtensionSelector(null);
}
/**
* Returns a new builder of BMv2 extension selectors.
*
* @return a builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder of BMv2 extension selectors.
* <p>
* Match parameters are built from primitive data types ({@code short}, {@code int}, {@code long} or
* {@code byte[]}) and automatically casted to fixed-length byte sequences according to the given BMv2
* configuration.
*/
public static final class Builder {
private final Map<Pair<String, String>, Bmv2MatchParam> parameterMap = Maps.newHashMap();
private Bmv2Configuration configuration;
private Builder() {
// ban constructor.
}
/**
* Sets the BMv2 configuration to format the match parameters of the selector.
*
* @param config a BMv2 configuration
* @return this
*/
public Builder forConfiguration(Bmv2Configuration config) {
this.configuration = config;
return this;
}
/**
* Adds an exact match parameter for the given header field and value.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a short value
* @return this
*/
public Builder matchExact(String headerName, String fieldName, short value) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
exact(value));
return this;
}
/**
* Adds an exact match parameter for the given header field and value.
*
* @param headerName a string value
* @param fieldName a string value
* @param value an integer value
* @return this
*/
public Builder matchExact(String headerName, String fieldName, int value) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
exact(value));
return this;
}
/**
* Adds an exact match parameter for the given header field and value.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a long value
* @return this
*/
public Builder matchExact(String headerName, String fieldName, long value) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
exact(value));
return this;
}
/**
* Adds an exact match parameter for the given header field and value.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a byte array
* @return this
*/
public Builder matchExact(String headerName, String fieldName, byte[] value) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
exact(value));
return this;
}
/**
* Adds a ternary match parameter for the given header field, value and mask.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a short value
* @param mask a short value
* @return this
*/
public Builder matchTernary(String headerName, String fieldName, short value, short mask) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
ternary(value, mask));
return this;
}
/**
* Adds a ternary match parameter for the given header field, value and mask.
*
* @param headerName a string value
* @param fieldName a string value
* @param value an integer value
* @param mask an integer value
* @return this
*/
public Builder matchTernary(String headerName, String fieldName, int value, int mask) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
ternary(value, mask));
return this;
}
/**
* Adds a ternary match parameter for the given header field, value and mask.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a long value
* @param mask a long value
* @return this
*/
public Builder matchTernary(String headerName, String fieldName, long value, long mask) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
ternary(value, mask));
return this;
}
/**
* Adds a ternary match parameter for the given header field, value and mask.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a byte array
* @param mask a byte array
* @return this
*/
public Builder matchTernary(String headerName, String fieldName, byte[] value, byte[] mask) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
ternary(value, mask));
return this;
}
/**
* Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a short value
* @param prefixLength an integer value
* @return this
*/
public Builder matchLpm(String headerName, String fieldName, short value, int prefixLength) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
lpm(value, prefixLength));
return this;
}
/**
* Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
*
* @param headerName a string value
* @param fieldName a string value
* @param value an integer value
* @param prefixLength an integer value
* @return this
*/
public Builder matchLpm(String headerName, String fieldName, int value, int prefixLength) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
lpm(value, prefixLength));
return this;
}
/**
* Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a long value
* @param prefixLength an integer value
* @return this
*/
public Builder matchLpm(String headerName, String fieldName, long value, int prefixLength) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
lpm(value, prefixLength));
return this;
}
/**
* Adds a longest-prefix match (LPM) parameter for the given header field, value and prefix length.
*
* @param headerName a string value
* @param fieldName a string value
* @param value a byte array
* @param prefixLength an integer value
* @return this
*/
public Builder matchLpm(String headerName, String fieldName, byte[] value, int prefixLength) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
lpm(value, prefixLength));
return this;
}
/**
* Adds a valid match parameter for the given header field.
*
* @param headerName a string value
* @param fieldName a string value
* @param flag a boolean value
* @return this
*/
public Builder matchValid(String headerName, String fieldName, boolean flag) {
parameterMap.put(Pair.of(checkNotNull(headerName, "header name cannot be null"),
checkNotNull(fieldName, "field name cannot be null")),
new Bmv2ValidMatchParam(flag));
return this;
}
/**
* Returns a new BMv2 extension selector.
*
* @return a BMv2 extension selector
* @throws NullPointerException if a given header or field name is not defined in the given configuration
* @throws IllegalArgumentException if a given parameter cannot be casted for the given configuration, e.g.
* when trying to fit an integer value into a smaller, fixed-length parameter
* produces overflow.
*/
public Bmv2ExtensionSelector build() {
checkNotNull(configuration, "configuration cannot be null");
checkState(parameterMap.size() > 0, "parameter map cannot be empty");
final Map<String, Bmv2MatchParam> newParameterMap = Maps.newHashMap();
for (Pair<String, String> key : parameterMap.keySet()) {
String headerName = key.getLeft();
String fieldName = key.getRight();
Bmv2HeaderModel headerModel = configuration.header(headerName);
checkNotNull(headerModel, "no such a header in configuration", headerName);
Bmv2FieldTypeModel fieldModel = headerModel.type().field(fieldName);
checkNotNull(fieldModel, "no such a field in configuration", key);
int bitWidth = fieldModel.bitWidth();
Bmv2MatchParam oldParam = parameterMap.get(key);
Bmv2MatchParam newParam = null;
try {
switch (oldParam.type()) {
case EXACT:
Bmv2ExactMatchParam e = (Bmv2ExactMatchParam) oldParam;
newParam = new Bmv2ExactMatchParam(fitByteSequence(e.value(), bitWidth));
break;
case TERNARY:
Bmv2TernaryMatchParam t = (Bmv2TernaryMatchParam) oldParam;
newParam = new Bmv2TernaryMatchParam(fitByteSequence(t.value(), bitWidth),
fitByteSequence(t.mask(), bitWidth));
break;
case LPM:
Bmv2LpmMatchParam l = (Bmv2LpmMatchParam) oldParam;
checkArgument(l.prefixLength() <= bitWidth, "LPM parameter has prefix length too long",
key);
newParam = new Bmv2LpmMatchParam(fitByteSequence(l.value(), bitWidth),
l.prefixLength());
break;
case VALID:
newParam = oldParam;
break;
default:
throw new RuntimeException("Match parameter type not supported: " + oldParam.type());
}
} catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
throw new IllegalArgumentException(e.getMessage() + " [" + key + "]");
}
// FIXME: should put the pair object instead of building a new string for the key.
newParameterMap.put(headerName + "." + fieldName, newParam);
}
return new Bmv2ExtensionSelector(newParameterMap);
}
private static Bmv2MatchParam exact(Object value) {
return new Bmv2ExactMatchParam(copyFrom(bb(value)));
}
private static Bmv2MatchParam ternary(Object value, Object mask) {
return new Bmv2TernaryMatchParam(copyFrom(bb(value)), copyFrom(bb(mask)));
}
private static Bmv2MatchParam lpm(Object value, int prefixLength) {
return new Bmv2LpmMatchParam(copyFrom(bb(value)), prefixLength);
}
private static ByteBuffer bb(Object value) {
if (value instanceof Short) {
return ByteBuffer.allocate(Short.BYTES).putShort((short) value);
} else if (value instanceof Integer) {
return ByteBuffer.allocate(Integer.BYTES).putInt((int) value);
} else if (value instanceof Long) {
return ByteBuffer.allocate(Long.BYTES).putLong((long) value);
} else if (value instanceof byte[]) {
byte[] bytes = (byte[]) value;
return ByteBuffer.allocate(bytes.length).put(bytes);
} else {
// Never here.
return null;
}
}
}
}