ONOS-4044 Implemented ONOS-to-Bmv2 flow rule translator

In Bmv2, tables, header fields and actions all depend on the packet
processing model configuration (Bmv2Model) currently deployed on the
device. For this reason, translation is needed from protocol-aware ONOS
FlowRule objects into properly formatted, protocol-independent
Bmv2TableEntry objects. Translation is based on a TranslatorConfig that
provides a mapping between ONOS types and Bmv2 model-dependent types.

Change-Id: I620802c2024b5250867dc6b1b988b739177f582a
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/translators/Bmv2DefaultFlowRuleTranslator.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/translators/Bmv2DefaultFlowRuleTranslator.java
new file mode 100644
index 0000000..1d93499
--- /dev/null
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/translators/Bmv2DefaultFlowRuleTranslator.java
@@ -0,0 +1,282 @@
+/*
+ * 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.drivers.bmv2.translators;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.bmv2.api.model.Bmv2Model;
+import org.onosproject.bmv2.api.model.Bmv2ModelField;
+import org.onosproject.bmv2.api.model.Bmv2ModelTable;
+import org.onosproject.bmv2.api.model.Bmv2ModelTableKey;
+import org.onosproject.bmv2.api.runtime.Bmv2Action;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
+import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
+import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
+import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
+import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
+import org.onosproject.bmv2.api.runtime.Bmv2TernaryMatchParam;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.ExtensionCriterion;
+import org.onosproject.net.flow.criteria.ExtensionSelector;
+import org.onosproject.net.flow.criteria.ExtensionSelectorType.ExtensionSelectorTypes;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.ExtensionTreatment;
+import org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.Instructions.ExtensionInstructionWrapper;
+
+/**
+ * Default Bmv2 flow rule translator implementation.
+ * <p>
+ * Flow rules are translated into {@link Bmv2TableEntry BMv2 table entries} according to the following logic:
+ * <ul>
+ * <li> table name: obtained from the Bmv2 model using the flow rule table ID;
+ * <li> match key: if the flow rule selector defines only a criterion of type {@link Criterion.Type#EXTENSION EXTENSION}
+ * , then the latter is expected to contain a {@link Bmv2ExtensionSelector Bmv2ExtensionSelector}, which should provide
+ * a match key already formatted for the given table; otherwise a match key is built using the
+ * {@link TranslatorConfig#fieldToCriterionTypeMap() mapping} defined by this translator configuration.
+ * <li> action: if the flow rule treatment contains only one instruction of type
+ * {@link Instruction.Type#EXTENSION EXTENSION}, then the latter is expected to contain a {@link Bmv2ExtensionTreatment}
+ * , which should provide a {@link Bmv2Action} already formatted for the given table; otherwise, an action is
+ * {@link TranslatorConfig#buildAction(TrafficTreatment) built} using this translator configuration.
+ * <li> priority: the same as the flow rule.
+ * <li> timeout: if the table supports timeout, use the same as the flow rule, otherwise none (i.e. permanent entry).
+ * </ul>
+ */
+@Beta
+public class Bmv2DefaultFlowRuleTranslator implements Bmv2FlowRuleTranslator {
+
+    // TODO: config is harcoded now, instead it should be selected based on device model
+    private final TranslatorConfig config = new Bmv2SimplePipelineTranslatorConfig();
+    private final Bmv2Model model = config.model();
+
+    private static Bmv2TernaryMatchParam buildTernaryParam(Bmv2ModelField field, Criterion criterion, int byteWidth)
+            throws Bmv2FlowRuleTranslatorException {
+
+        // Value and mask will be filled according to criterion type
+        ImmutableByteSequence value;
+        ImmutableByteSequence mask = null;
+
+        switch (criterion.type()) {
+            case IN_PORT:
+                // FIXME: allow port numbers of variable bit length (based on model), truncating when necessary
+                short port = (short) ((PortCriterion) criterion).port().toLong();
+                value = ImmutableByteSequence.copyFrom(port);
+                break;
+            case ETH_DST:
+                EthCriterion c = (EthCriterion) criterion;
+                value = ImmutableByteSequence.copyFrom(c.mac().toBytes());
+                if (c.mask() != null) {
+                    mask = ImmutableByteSequence.copyFrom(c.mask().toBytes());
+                }
+                break;
+            case ETH_SRC:
+                EthCriterion c2 = (EthCriterion) criterion;
+                value = ImmutableByteSequence.copyFrom(c2.mac().toBytes());
+                if (c2.mask() != null) {
+                    mask = ImmutableByteSequence.copyFrom(c2.mask().toBytes());
+                }
+                break;
+            case ETH_TYPE:
+                short ethType = ((EthTypeCriterion) criterion).ethType().toShort();
+                value = ImmutableByteSequence.copyFrom(ethType);
+                break;
+            // TODO: implement building for other criterion types (easy with DefaultCriterion of ONOS-4034)
+            default:
+                throw new Bmv2FlowRuleTranslatorException("Feature not implemented, ternary builder for criterion" +
+                                                                  "type: " + criterion.type().name());
+        }
+
+        if (mask == null) {
+            // no mask, all ones
+            mask = ImmutableByteSequence.ofOnes(byteWidth);
+        }
+
+        return new Bmv2TernaryMatchParam(value, mask);
+    }
+
+    private static Bmv2MatchKey getMatchKeyFromExtension(ExtensionCriterion criterion)
+            throws Bmv2FlowRuleTranslatorException {
+
+        ExtensionSelector extSelector = criterion.extensionSelector();
+
+        if (extSelector.type() == ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type()) {
+            if (extSelector instanceof Bmv2ExtensionSelector) {
+                return ((Bmv2ExtensionSelector) extSelector).matchKey();
+            } else {
+                throw new Bmv2FlowRuleTranslatorException("Unable to decode extension selector " + extSelector);
+            }
+        } else {
+            throw new Bmv2FlowRuleTranslatorException("Unsupported extension selector type " + extSelector.type());
+        }
+    }
+
+    private static Bmv2Action getActionFromExtension(Instructions.ExtensionInstructionWrapper inst)
+            throws Bmv2FlowRuleTranslatorException {
+
+        ExtensionTreatment extTreatment = inst.extensionInstruction();
+
+        if (extTreatment.type() == ExtensionTreatmentTypes.P4_BMV2_ACTION.type()) {
+            if (extTreatment instanceof Bmv2ExtensionTreatment) {
+                return ((Bmv2ExtensionTreatment) extTreatment).getAction();
+            } else {
+                throw new Bmv2FlowRuleTranslatorException("Unable to decode treatment extension: " + extTreatment);
+            }
+        } else {
+            throw new Bmv2FlowRuleTranslatorException("Unsupported treatment extension type: " + extTreatment.type());
+        }
+    }
+
+    private static Bmv2MatchKey buildMatchKey(TranslatorConfig config, TrafficSelector selector, Bmv2ModelTable table)
+            throws Bmv2FlowRuleTranslatorException {
+
+        Bmv2MatchKey.Builder matchKeyBuilder = Bmv2MatchKey.builder();
+
+        for (Bmv2ModelTableKey key : table.keys()) {
+
+            String fieldName = key.field().header().name() + "." + key.field().type().name();
+            int byteWidth = (int) Math.ceil((double) key.field().type().bitWidth() / 8.0);
+            Criterion.Type criterionType = config.fieldToCriterionTypeMap().get(fieldName);
+
+            if (criterionType == null || selector.getCriterion(criterionType) == null) {
+                // A mapping is not available or the selector doesn't have such a type
+                switch (key.matchType()) {
+                    case TERNARY:
+                        // Wildcard field
+                        matchKeyBuilder.withWildcard(byteWidth);
+                        break;
+                    case LPM:
+                        // LPM with prefix 0
+                        matchKeyBuilder.add(new Bmv2LpmMatchParam(ImmutableByteSequence.ofZeros(byteWidth), 0));
+                        break;
+                    default:
+                        throw new Bmv2FlowRuleTranslatorException("Match field not supported: " + fieldName);
+                }
+                // Next key
+                continue;
+            }
+
+            Criterion criterion = selector.getCriterion(criterionType);
+            Bmv2TernaryMatchParam matchParam = null;
+
+            switch (key.matchType()) {
+                case TERNARY:
+                    matchParam = buildTernaryParam(key.field(), criterion, byteWidth);
+                    break;
+                default:
+                    // TODO: implement other match param builders (exact, LPM, etc.)
+                    throw new Bmv2FlowRuleTranslatorException("Feature not implemented, match param builder: "
+                                                                      + key.matchType().name());
+            }
+
+            matchKeyBuilder.add(matchParam);
+        }
+
+        return matchKeyBuilder.build();
+    }
+
+    @Override
+    public Bmv2TableEntry translate(FlowRule rule)
+            throws Bmv2FlowRuleTranslatorException {
+
+        int tableId = rule.tableId();
+
+        Bmv2ModelTable table = model.table(tableId);
+
+        if (table == null) {
+            throw new Bmv2FlowRuleTranslatorException("Unknown table ID: " + tableId);
+        }
+
+        /* Translate selector */
+
+        TrafficSelector selector = rule.selector();
+        Bmv2MatchKey bmv2MatchKey = null;
+
+        // If selector has only 1 criterion of type extension, use that
+        Criterion criterion = selector.getCriterion(Criterion.Type.EXTENSION);
+        if (criterion != null) {
+            if (selector.criteria().size() == 1) {
+                bmv2MatchKey = getMatchKeyFromExtension((ExtensionCriterion) criterion);
+            } else {
+                throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic selector, found multiple " +
+                                                                  "criteria of which one is an extension: " +
+                                                                  selector.toString());
+            }
+        }
+
+        if (bmv2MatchKey == null) {
+            // not an extension
+            bmv2MatchKey = buildMatchKey(config, selector, table);
+        }
+
+        /* Translate treatment */
+
+        TrafficTreatment treatment = rule.treatment();
+        Bmv2Action bmv2Action = null;
+
+        // If treatment has only 1 instruction of type extension, use that
+        for (Instruction inst : treatment.allInstructions()) {
+            if (inst.type() == Instruction.Type.EXTENSION) {
+                if (treatment.allInstructions().size() == 1) {
+                    bmv2Action = getActionFromExtension((ExtensionInstructionWrapper) inst);
+                } else {
+                    throw new Bmv2FlowRuleTranslatorException("Unable to translate traffic treatment, found multiple " +
+                                                                      "instructions of which one is an extension: " +
+                                                                      selector.toString());
+                }
+            }
+        }
+
+        if (bmv2Action == null) {
+            // No extension, use config to build action
+            bmv2Action = config.buildAction(treatment);
+        }
+
+        if (bmv2Action == null) {
+            // Config returned null
+            throw new Bmv2FlowRuleTranslatorException("Unable to translate treatment: " + treatment);
+        }
+
+        Bmv2TableEntry.Builder tableEntryBuilder = Bmv2TableEntry.builder();
+
+        tableEntryBuilder
+                .withTableName(table.name())
+                .withPriority(rule.priority())
+                .withMatchKey(bmv2MatchKey)
+                .withAction(bmv2Action);
+
+        if (!rule.isPermanent()) {
+            if (table.hasTimeouts()) {
+                tableEntryBuilder.withTimeout((double) rule.timeout());
+            }
+            //FIXME: add warn log or exception?
+        }
+
+        return tableEntryBuilder.build();
+    }
+
+    @Override
+    public TranslatorConfig config() {
+        return this.config;
+    }
+}