[ONOS-6856] Interpreter implementation for tor.p4

Change-Id: Id5bcb1433e66eafa5ef033670ac0eac0dcd9431b
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
index e6fc8d9..5bd3864 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
@@ -16,38 +16,116 @@
 
 package org.onosproject.pi.demo.app.tor;
 
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
+import org.onlab.packet.Ethernet;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
 import org.onosproject.net.pi.runtime.PiCounterId;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiPacketMetadataId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableId;
 
+import java.nio.ByteBuffer;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Optional;
 
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.ImmutableByteSequence.fit;
+import static org.onosproject.net.PortNumber.CONTROLLER;
+import static org.onosproject.net.PortNumber.FLOOD;
+import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
+import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+
 /**
- * Implementation of a PiPipeline interpreter for TOR configuration.
+ * Implementation of a PiPipeline interpreter for tor.p4.
  */
-//TODO implement
 public class TorInterpreter extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
 
+    private static final boolean PACKET_IO_ENABLED = true;
 
-    @Override
-    public Optional<Integer> mapPiTableId(PiTableId piTableId) {
-        return Optional.empty();
-    }
+    //For this simple interpreter we need just the punt_table defined in punt.p4
+    private static final String PUNT = "punt";
+    private static final PiTableId PUNT_TABLE_ID = PiTableId.of(PUNT, "punt_table");
+    private static final String SEND_TO_CPU = PUNT + ".set_queue_and_send_to_cpu";
+
+    private static final String QUEUE_ID = "queue_id";
+
+    private static final String EGRESS_PORT = "egress_physical_port";
+    private static final String INGRESS_PORT = "ingress_physical_port";
+
+    // Set as per value in headers.p4 in packet_out_header
+    private static final int PORT_FIELD_BITWIDTH = 9;
+
+    private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "ether_type");
+
+    private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
+            // If we use the DefaultSingleTable pipeliner, then we need to map only table ID 0,
+            // which in this case will be the one used by proxyarp, lldpprovider, etc., i.e. the punt table.
+            0, PUNT_TABLE_ID);
+
+    private ImmutableBiMap<Criterion.Type, PiHeaderFieldId> criterionMap =
+            new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+                    .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
+                    .build();
+
+    //FIXME figure out what queque id is, we set as all zeros for now.
+    private static final PiActionParam QUEUE_ID_PARAM = new PiActionParam(PiActionParamId.of(QUEUE_ID),
+                                                                          ImmutableByteSequence.copyFrom((byte) 0));
+    // Only action we need, can be defined statically.
+    private static final PiAction SEND_TO_CPU_ACTION = PiAction.builder()
+            .withId(PiActionId.of(SEND_TO_CPU))
+            .withParameter(QUEUE_ID_PARAM)
+            .build();
 
     @Override
     public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId) throws PiInterpreterException {
-        return null;
+        if (piTableId.equals(PUNT_TABLE_ID)) {
+            if (treatment.allInstructions().size() != 1) {
+                // We understand treatments with only 1 instruction.
+                throw new PiPipelineInterpreter.PiInterpreterException("Treatment must have only 1 instruction");
+            }
+
+            Instruction instruction = treatment.allInstructions().get(0);
+
+            switch (instruction.type()) {
+                case OUTPUT:
+                    Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
+                    PortNumber port = outInstruction.port();
+                    if (port.equals(CONTROLLER)) {
+                        return SEND_TO_CPU_ACTION;
+                    } else {
+                        throw new PiInterpreterException(format("Egress not supported on %s port", port));
+                    }
+                default:
+                    throw new PiInterpreterException(format("Instruction type '%s' not supported", instruction.type()));
+            }
+        } else {
+            throw new PiInterpreterException(format("Table '%s' not supported", piTableId.name()));
+        }
     }
 
     @Override
@@ -56,28 +134,110 @@
     }
 
     @Override
-    public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet) throws PiInterpreterException {
-        return ImmutableList.of();
+    public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
+            throws PiInterpreterException {
+
+        if (!PACKET_IO_ENABLED) {
+            return Collections.emptyList();
+        }
+
+        TrafficTreatment treatment = packet.treatment();
+
+        // tor.p4 supports only OUTPUT instructions.
+        List<Instructions.OutputInstruction> outInstructions = treatment.allInstructions()
+                .stream()
+                .filter(i -> i.type().equals(OUTPUT))
+                .map(i -> (Instructions.OutputInstruction) i)
+                .collect(toList());
+
+        if (treatment.allInstructions().size() != outInstructions.size()) {
+            // There are other instructions that are not of type OUTPUT.
+            throw new PiInterpreterException("Treatment not supported: " + treatment);
+        }
+
+        ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
+        for (Instructions.OutputInstruction outInst : outInstructions) {
+            if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
+                throw new PiInterpreterException(format("Output on logical port '%s' not supported", outInst.port()));
+            } else if (outInst.port().equals(FLOOD)) {
+                // Since default.p4 does not support flooding, we create a packet operation for each switch port.
+                for (Port port : handler().get(DeviceService.class).getPorts(packet.sendThrough())) {
+                    builder.add(createPiPacketOperation(packet.data(), port.number().toLong()));
+                }
+            } else {
+                builder.add(createPiPacketOperation(packet.data(), outInst.port().toLong()));
+            }
+        }
+        return builder.build();
     }
 
     @Override
-    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetInOperation)
+    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
             throws PiInterpreterException {
-        return null;
+
+        if (!PACKET_IO_ENABLED) {
+            return null;
+        }
+
+        // FIXME Assuming that the packet is ethernet. is it fine ? tor.p4 can deparse also other packets.
+        Ethernet ethPkt = new Ethernet();
+
+        ethPkt.deserialize(packetIn.data().asArray(), 0, packetIn.data().size());
+
+        // Returns the ingress port packet metadata.
+        Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas()
+                .stream().filter(metadata -> metadata.id().name().equals(INGRESS_PORT))
+                .findFirst();
+
+        if (packetMetadata.isPresent()) {
+            ImmutableByteSequence portByteSequence = packetMetadata.get().value();
+            short s = portByteSequence.asReadOnlyBuffer().getShort();
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+            ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
+            return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
+        } else {
+            throw new PiInterpreterException(format(
+                    "Missing metadata '%s' in packet-in received from '%s': %s", INGRESS_PORT, deviceId, packetIn));
+        }
+    }
+
+    private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
+        PiPacketMetadata metadata = createPacketMetadata(portNumber);
+        return PiPacketOperation.builder()
+                .withType(PACKET_OUT)
+                .withData(copyFrom(data))
+                .withMetadatas(ImmutableList.of(metadata))
+                .build();
+    }
+
+    private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+        try {
+            return PiPacketMetadata.builder()
+                    .withId(PiPacketMetadataId.of(EGRESS_PORT))
+                    .withValue(fit(copyFrom(portNumber), PORT_FIELD_BITWIDTH))
+                    .build();
+        } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+            throw new PiInterpreterException(format("Port number %d too big, %s", portNumber, e.getMessage()));
+        }
     }
 
     @Override
     public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
-        return Optional.empty();
+        return Optional.ofNullable(criterionMap.get(type));
     }
 
     @Override
     public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
-        return Optional.empty();
+        return Optional.ofNullable(criterionMap.inverse().get(headerFieldId));
     }
 
     @Override
     public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
-        return Optional.empty();
+        return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
+    }
+
+    @Override
+    public Optional<Integer> mapPiTableId(PiTableId piTableId) {
+        return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
     }
 }
\ No newline at end of file
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
index e9dbba2..d6c635c 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
@@ -18,14 +18,15 @@
 
 import com.google.common.collect.Lists;
 import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
 import org.onosproject.drivers.p4runtime.DefaultP4PortStatisticsDiscovery;
-import org.onosproject.drivers.p4runtime.NetcfgLinkDiscovery;
-import org.onosproject.net.behaviour.LinkDiscovery;
+import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
 
 import java.net.URL;
 import java.util.Collection;
@@ -42,18 +43,19 @@
 final class TorPipeconfFactory {
 
     private static final String BMV2_PIPECONF_ID = "bmv2-tor";
-    private static final URL BMV2_P4INFO_URL = TorApp.class.getResource("/tor.p4info");
-    private static final URL BMV2_JSON_URL = TorApp.class.getResource("/tor.json");
+    private static final URL BMV2_P4INFO_URL = TorApp.class.getResource("/p4c-out/tor.p4info");
+    private static final URL BMV2_JSON_URL = TorApp.class.getResource("/p4c-out/tor.json");
 
     private static final String TOFINO_PIPECONF_ID_BASE = "tofino-tor-%s";
-    private static final String TOFINO_JSON_PATH_BASE = "/tor-tofino/%s/tor.json";
-    private static final String TOFINO_P4INFO_PATH_BASE = "/tor-tofino/%s/tor.p4info";
-    private static final String TOFINO_CONTEXT_JSON_PATH_BASE = "/tor-tofino/%s/context/context.json";
-    private static final String TOFINO_BIN_PATH_BASE = "/tor-tofino/%s/tofino.bin";
+    private static final String TOFINO_P4INFO_PATH_BASE = "/p4c-out/tofino/tor/%s/tor.p4info";
+    private static final String TOFINO_CONTEXT_JSON_PATH_BASE = "/p4c-out/tofino/tor/%s/context.json";
+    private static final String TOFINO_BIN_PATH_BASE = "/p4c-out/tofino/tor/%s/tofino.bin";
 
     private static final String MAVERICKS = "mavericks";
     private static final String MONTARA = "montara";
 
+    private static final PiPipelineModel PIPELINE_MODEL = Bmv2PipelineModelParser.parse(BMV2_JSON_URL);
+
     private static final PiPipeconf BMV2_PIPECONF = buildBmv2Pipeconf();
     private static final PiPipeconf MAVERICKS_PIPECONF = buildTofinoPipeconf(MAVERICKS);
     private static final PiPipeconf MONTARA_PIPECONF = buildTofinoPipeconf(MONTARA);
@@ -66,21 +68,24 @@
         return Lists.newArrayList(BMV2_PIPECONF, MAVERICKS_PIPECONF, MONTARA_PIPECONF);
     }
 
+    private static DefaultPiPipeconf.Builder baseBuilder() {
+        return DefaultPiPipeconf.builder()
+                .withPipelineModel(PIPELINE_MODEL)
+                .addBehaviour(PiPipelineInterpreter.class, TorInterpreter.class)
+                // FIXME: add if Packet I/O is not working
+                // .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
+                // FIXME: change underneath to proper TOR implementation
+                .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+                .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class);
+    }
+
     private static PiPipeconf buildTofinoPipeconf(String system) {
-        final URL bmv2JsonUrl = TorPipeconfFactory.class.getResource(format(TOFINO_JSON_PATH_BASE, system));
         final URL p4InfoUrl = TorPipeconfFactory.class.getResource(format(TOFINO_P4INFO_PATH_BASE, system));
         final URL tofinoUrl = TorPipeconfFactory.class.getResource(format(TOFINO_BIN_PATH_BASE, system));
         final URL contextUrl = TorPipeconfFactory.class.getResource(format(TOFINO_CONTEXT_JSON_PATH_BASE, system));
 
-        return DefaultPiPipeconf.builder()
+        return baseBuilder()
                 .withId(new PiPipeconfId(format(TOFINO_PIPECONF_ID_BASE, system)))
-                .withPipelineModel(Bmv2PipelineModelParser.parse(bmv2JsonUrl))
-                .addBehaviour(PiPipelineInterpreter.class, TorInterpreter.class)
-                //FIXME change underneath to proper TOR implementation
-                //.addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
-                .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class)
-                // FIXME: can remove if Packet I/O is working
-                .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
                 .addExtension(P4_INFO_TEXT, p4InfoUrl)
                 .addExtension(TOFINO_BIN, tofinoUrl)
                 .addExtension(TOFINO_CONTEXT_JSON, contextUrl)
@@ -88,15 +93,8 @@
     }
 
     private static PiPipeconf buildBmv2Pipeconf() {
-        return DefaultPiPipeconf.builder()
+        return baseBuilder()
                 .withId(new PiPipeconfId(BMV2_PIPECONF_ID))
-                .withPipelineModel(Bmv2PipelineModelParser.parse(BMV2_JSON_URL))
-                .addBehaviour(PiPipelineInterpreter.class, TorInterpreter.class)
-                //FIXME change underneath to proper TOR implementation
-                //.addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
-                .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class)
-                // FIXME: can remove if Packet I/O is working
-                .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
                 .addExtension(P4_INFO_TEXT, BMV2_P4INFO_URL)
                 .addExtension(BMV2_JSON, BMV2_JSON_URL)
                 .build();