/* * 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 org.onosproject.store.serializers.KryoNamespaces; import java.nio.ByteBuffer; import java.util.Collections; 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 static final KryoNamespace APP_KRYO = new KryoNamespace.Builder() .register(KryoNamespaces.API) .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 APP_KRYO.serialize(parameterMap); } @Override public void deserialize(byte[] data) { this.parameterMap = APP_KRYO.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() { MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this); parameterMap.forEach((name, param) -> { switch (param.type()) { case EXACT: Bmv2ExactMatchParam e = (Bmv2ExactMatchParam) param; helper.add(name, e.value()); break; case TERNARY: Bmv2TernaryMatchParam t = (Bmv2TernaryMatchParam) param; helper.add(name, t.value() + "&&&" + t.mask()); break; case LPM: Bmv2LpmMatchParam l = (Bmv2LpmMatchParam) param; helper.add(name, l.value() + "/" + String.valueOf(l.prefixLength())); break; case VALID: Bmv2ValidMatchParam v = (Bmv2ValidMatchParam) param; helper.add(name, v.flag() ? "VALID" : "NOT_VALID"); break; default: helper.add(name, param); break; } }); return helper.toString(); } /** * Returns a new, empty BMv2 extension selector. * * @return a BMv2 extension treatment */ public static Bmv2ExtensionSelector empty() { return new Bmv2ExtensionSelector(Collections.emptyMap()); } /** * 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; } } } }