Implement support for packetIn through p4Runtime

Change-Id: I92cc1a2bd7edde8916aad61c20d7411f93368612
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
index 394b787..a85d80c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
@@ -17,9 +17,11 @@
 package org.onosproject.net.pi.model;
 
 import com.google.common.annotations.Beta;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.driver.HandlerBehaviour;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
@@ -86,6 +88,17 @@
             throws PiInterpreterException;
 
     /**
+     * Returns a InboundPacket equivalent to the given packet operation.
+     *
+     * @param deviceId          the device that originated the packet-in
+     * @param packetInOperation the packet operation
+     * @return an ONOS inbound packet
+     * @throws PiInterpreterException if the port can't be extracted from the packet metadata
+     */
+    InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetInOperation)
+            throws PiInterpreterException;
+
+    /**
      * Signals that an error was encountered while executing the interpreter.
      */
     @Beta
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java b/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
index 04535aa..f13c1d9 100644
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/MockInterpreter.java
@@ -19,11 +19,13 @@
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableList;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 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.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiAction;
@@ -106,6 +108,12 @@
         return ImmutableList.of();
     }
 
+    @Override
+    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetInOperation)
+            throws PiInterpreterException {
+        return null;
+    }
+
     /**
      * Returns an action instance with no runtime parameters.
      */
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultInterpreter.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultInterpreter.java
index 6b057cc..449f151 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultInterpreter.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DefaultInterpreter.java
@@ -18,7 +18,10 @@
 
 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;
@@ -27,6 +30,8 @@
 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;
@@ -77,6 +82,7 @@
 
     private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
             0, PiTableId.of(TABLE0));
+    public static final String INGRESS_PORT = "ingress_port";
 
 
     @Override
@@ -149,6 +155,39 @@
         return builder.build();
     }
 
+    @Override
+    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
+            throws PiInterpreterException {
+
+        //We are assuming that the packet is ethernet type
+        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()) {
+
+            //Obtaining the ingress port as an immutable byte sequence
+            ImmutableByteSequence portByteSequence = packetMetadata.get().value();
+
+            //Converting immutableByteSequence to short
+            short s = portByteSequence.asReadOnlyBuffer().getShort();
+
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+
+            //FIXME should be optimizable with .asReadOnlyBytebuffer
+            ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
+            return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
+
+        } else {
+            throw new PiInterpreterException("Can't get packet metadata for" + INGRESS_PORT);
+        }
+    }
+
     private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
         //create the metadata
         PiPacketMetadata metadata = createPacketMetadata(portNumber);
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePacketIn.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePacketIn.java
index 338b19a..2fe3ea4 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePacketIn.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimePacketIn.java
@@ -17,10 +17,8 @@
 package org.onosproject.p4runtime.api;
 
 import com.google.common.annotations.Beta;
-import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.DeviceId;
-
-import java.util.List;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
 
 /**
  * Information about a packet-in received from a P4Runtime device.
@@ -36,17 +34,10 @@
     DeviceId deviceId();
 
     /**
-     * Returns the packet raw data.
+     * Returns the packet operation corresponding to this packet-in event.
      *
-     * @return byte sequence
+     * @return pi packet operation
      */
-    ImmutableByteSequence data();
+    PiPacketOperation packetOperation();
 
-    /**
-     * Returns the list of metadata associated with this packet-in, to be parsed by a
-     * {@link org.onosproject.net.pi.model.PiPipelineInterpreter}.
-     *
-     * @return list of byte sequences
-     */
-    List<ImmutableByteSequence> metadata();
 }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DefaultPacketInEvent.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DefaultPacketInEvent.java
index dd2f021..104999b 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DefaultPacketInEvent.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/DefaultPacketInEvent.java
@@ -17,16 +17,14 @@
 package org.onosproject.p4runtime.ctl;
 
 import com.google.common.base.Objects;
-import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.event.AbstractEvent;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.p4runtime.api.P4RuntimeEvent;
 import org.onosproject.p4runtime.api.P4RuntimeEventListener;
 import org.onosproject.p4runtime.api.P4RuntimeEventSubject;
 import org.onosproject.p4runtime.api.P4RuntimePacketIn;
 
-import java.util.List;
-
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
@@ -36,9 +34,8 @@
         extends AbstractEvent<P4RuntimeEventListener.Type, P4RuntimeEventSubject>
         implements P4RuntimeEvent {
 
-    DefaultPacketInEvent(DeviceId deviceId, ImmutableByteSequence data,
-                                   List<ImmutableByteSequence> metadata) {
-        super(P4RuntimeEventListener.Type.PACKET_IN, new DefaultPacketIn(deviceId, data, metadata));
+    DefaultPacketInEvent(DeviceId deviceId, PiPacketOperation operation) {
+        super(P4RuntimeEventListener.Type.PACKET_IN, new DefaultPacketIn(deviceId, operation));
     }
 
     /**
@@ -47,13 +44,11 @@
     private static final class DefaultPacketIn implements P4RuntimePacketIn {
 
         private final DeviceId deviceId;
-        private final ImmutableByteSequence data;
-        private final List<ImmutableByteSequence> metadata;
+        private final PiPacketOperation operation;
 
-        private DefaultPacketIn(DeviceId deviceId, ImmutableByteSequence data, List<ImmutableByteSequence> metadata) {
+        private DefaultPacketIn(DeviceId deviceId, PiPacketOperation operation) {
             this.deviceId = checkNotNull(deviceId);
-            this.data = checkNotNull(data);
-            this.metadata = checkNotNull(metadata);
+            this.operation = checkNotNull(operation);
         }
 
         @Override
@@ -62,13 +57,8 @@
         }
 
         @Override
-        public ImmutableByteSequence data() {
-            return data;
-        }
-
-        @Override
-        public List<ImmutableByteSequence> metadata() {
-            return metadata;
+        public PiPacketOperation packetOperation() {
+            return operation;
         }
 
         @Override
@@ -81,13 +71,12 @@
             }
             DefaultPacketIn that = (DefaultPacketIn) o;
             return Objects.equal(deviceId, that.deviceId) &&
-                    Objects.equal(data, that.data) &&
-                    Objects.equal(metadata, that.metadata);
+                    Objects.equal(operation, that.operation);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hashCode(deviceId, data, metadata);
+            return Objects.hashCode(deviceId, operation);
         }
     }
 }
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
index 78a0222..a7d7726 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4RuntimeClientImpl.java
@@ -16,7 +16,6 @@
 
 package org.onosproject.p4runtime.ctl;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.protobuf.ByteString;
 import io.grpc.Context;
@@ -24,10 +23,11 @@
 import io.grpc.Status;
 import io.grpc.StatusRuntimeException;
 import io.grpc.stub.StreamObserver;
-import org.onlab.util.ImmutableByteSequence;
+import org.onlab.osgi.DefaultServiceDirectory;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
@@ -68,7 +68,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
 
-import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -407,13 +406,26 @@
                 case PACKET:
                     // Packet-in
                     PacketIn packetIn = message.getPacket();
-                    ImmutableByteSequence data = copyFrom(packetIn.getPayload().asReadOnlyByteBuffer());
-                    ImmutableList.Builder<ImmutableByteSequence> metadataBuilder = ImmutableList.builder();
-                    packetIn.getMetadataList().stream()
-                            .map(m -> m.getValue().asReadOnlyByteBuffer())
-                            .map(ImmutableByteSequence::copyFrom)
-                            .forEach(metadataBuilder::add);
-                    P4RuntimeEvent event = new DefaultPacketInEvent(deviceId, data, metadataBuilder.build());
+
+                    // Retrieve the pipeconf for the specific device
+                    PiPipeconfService pipeconfService = DefaultServiceDirectory.getService(PiPipeconfService.class);
+                    if (pipeconfService == null) {
+                        throw new IllegalStateException("PiPipeconfService is null. Can't handle packet in.");
+                    }
+
+                    final PiPipeconf pipeconf;
+                    if (pipeconfService.ofDevice(deviceId).isPresent() &&
+                            pipeconfService.getPipeconf(pipeconfService.ofDevice(deviceId).get()).isPresent()) {
+                        pipeconf = pipeconfService.getPipeconf(pipeconfService.ofDevice(deviceId).get()).get();
+                    } else {
+                        log.warn("Unable to get the pipeconf of the {}. Can't handle packet in", deviceId);
+                        return;
+                    }
+                    //decode the packet and generate a corresponding p4Runtime event containing the PiPacketOperation
+                    P4RuntimeEvent event =
+                            new DefaultPacketInEvent(deviceId, PacketIOCodec.decodePacketIn(packetIn, pipeconf));
+
+                    //posting the event upwards
                     controller.postEvent(event);
                     return;
 
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
index 0461154..461cde0 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
@@ -17,19 +17,24 @@
 package org.onosproject.p4runtime.ctl;
 
 import com.google.protobuf.ByteString;
+import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiPacketMetadataId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.slf4j.Logger;
-import p4.P4RuntimeOuterClass;
 import p4.config.P4InfoOuterClass;
 
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
-import static org.onosproject.p4runtime.ctl.P4InfoBrowser.*;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.p4runtime.ctl.P4InfoBrowser.NotFoundException;
 import static org.slf4j.LoggerFactory.getLogger;
+import static p4.P4RuntimeOuterClass.PacketIn;
 import static p4.P4RuntimeOuterClass.PacketMetadata;
+import static p4.P4RuntimeOuterClass.PacketOut;
 
 /**
  * Encoder of packet metadata, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
@@ -40,6 +45,8 @@
 
     private static final String PACKET_OUT = "packet_out";
 
+    private static final String PACKET_IN = "packet_in";
+
     // TODO: implement cache of encoded entities.
 
     private PacketIOCodec() {
@@ -54,12 +61,12 @@
      * <p>
      * Please check the log for an explanation of any error that might have occurred.
      *
-     * @param packet   PI pakcet operation
+     * @param packet   PI packet operation
      * @param pipeconf the pipeconf for the program on the switch
      * @return a P4Runtime packet out protobuf message
      * @throws NotFoundException if the browser can't find the packet_out in the given p4Info
      */
-    static P4RuntimeOuterClass.PacketOut encodePacketOut(PiPacketOperation packet, PiPipeconf pipeconf)
+    static PacketOut encodePacketOut(PiPacketOperation packet, PiPipeconf pipeconf)
             throws NotFoundException {
 
         //Get the P4browser
@@ -68,7 +75,7 @@
         //Get the packet out packet metadata
         P4InfoOuterClass.ControllerPacketMetadata controllerPacketMetadata =
                 browser.controllerPacketMetadatas().getByName(PACKET_OUT);
-        P4RuntimeOuterClass.PacketOut.Builder packetOutBuilder = P4RuntimeOuterClass.PacketOut.newBuilder();
+        PacketOut.Builder packetOutBuilder = PacketOut.newBuilder();
 
         //outer controller packet metadata id
         int controllerPacketMetadataId = controllerPacketMetadata.getPreamble().getId();
@@ -102,6 +109,55 @@
         }).filter(Objects::nonNull).collect(Collectors.toList());
     }
 
-    //TODO: add decode packets
+    /**
+     * Returns a PiPacketOperation, decoded from the given P4Runtime PacketIn protobuf message
+     * for the given pipeconf. If a PI packet metadata inside the protobuf message cannot be decoded,
+     * it is skipped, hence the returned PiPacketOperation collection of metadatas might have different
+     * size than the input one.
+     * <p>
+     * Please check the log for an explanation of any error that might have occurred.
+     *
+     * @param packetIn the P4Runtime PAcketIn message
+     * @param pipeconf the pipeconf for the program on the switch
+     * @return a PiPacketOperation
+     */
+    static PiPacketOperation decodePacketIn(PacketIn packetIn, PiPipeconf pipeconf) {
+
+        //Get the P4browser
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        //Transform the packetIn data
+        ImmutableByteSequence data = copyFrom(packetIn.getPayload().asReadOnlyByteBuffer());
+
+        //Build the PiPacketOperation with all the metadatas.
+        return PiPacketOperation.builder()
+                .withType(PiPacketOperation.Type.PACKET_IN)
+                .withMetadatas(decodePacketMetadata(packetIn.getMetadataList(), browser))
+                .withData(data)
+                .build();
+    }
+
+    private static List<PiPacketMetadata> decodePacketMetadata(List<PacketMetadata> packetMetadatas,
+                                                               P4InfoBrowser browser) {
+        return packetMetadatas.stream().map(packetMetadata -> {
+            try {
+
+                int controllerPacketMetadataId = packetMetadata.getMetadataId();
+                //convert id to name through p4Info
+                P4InfoOuterClass.ControllerPacketMetadata metadata =
+                        browser.controllerPacketMetadatas().getById(controllerPacketMetadataId);
+                PiPacketMetadataId metadataId = PiPacketMetadataId.of(metadata.getPreamble().getName());
+
+                //Build each metadata.
+                return PiPacketMetadata.builder()
+                        .withId(metadataId)
+                        .withValue(ImmutableByteSequence.copyFrom(packetMetadata.getValue().asReadOnlyByteBuffer()))
+                        .build();
+            } catch (NotFoundException e) {
+                log.error("Cant find metadata with id {} in p4Info file.", packetMetadata.getMetadataId());
+                return null;
+            }
+        }).filter(Objects::nonNull).collect(Collectors.toList());
+    }
 
 }
diff --git a/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java b/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java
index 998e4e6..1b7c280 100644
--- a/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java
+++ b/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java
@@ -30,15 +30,19 @@
 import org.onosproject.net.packet.DefaultPacketContext;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
 import org.onosproject.net.packet.PacketProgrammable;
 import org.onosproject.net.packet.PacketProvider;
 import org.onosproject.net.packet.PacketProviderRegistry;
 import org.onosproject.net.packet.PacketProviderService;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.p4runtime.api.P4RuntimeController;
 import org.onosproject.p4runtime.api.P4RuntimeEvent;
 import org.onosproject.p4runtime.api.P4RuntimeEventListener;
+import org.onosproject.p4runtime.api.P4RuntimePacketIn;
 import org.slf4j.Logger;
 
 import java.nio.ByteBuffer;
@@ -142,12 +146,25 @@
 
         @Override
         public void event(P4RuntimeEvent event) {
-            /**
-             * TODO: handle packet-in event.
-             * The inputport need parse from metadata() of packet_in event,
-             * but support for parsing metadata() is not ready yet.
-             */
-
+            P4RuntimePacketIn eventSubject = (P4RuntimePacketIn) event.subject();
+            if (deviceService.getDevice(eventSubject.deviceId()).is(PiPipelineInterpreter.class)) {
+                PiPacketOperation operation = eventSubject.packetOperation();
+                try {
+                    InboundPacket inPkt = deviceService.getDevice(eventSubject.deviceId())
+                            .as(PiPipelineInterpreter.class)
+                            .mapInboundPacket(eventSubject.deviceId(), operation);
+                    //Creating the corresponding outbound Packet
+                    //FIXME Wrapping of bytebuffer might be optimized with .asReadOnlyByteBuffer()
+                    OutboundPacket outPkt = new DefaultOutboundPacket(eventSubject.deviceId(), null,
+                            ByteBuffer.wrap(operation.data().asArray()));
+                    //Creating PacketContext
+                    PacketContext pktCtx = new P4RuntimePacketContext(System.currentTimeMillis(), inPkt, outPkt, false);
+                    //Sendign the ctx up for processing.
+                    providerService.processPacket(pktCtx);
+                } catch (PiPipelineInterpreter.PiInterpreterException e) {
+                    log.error("Can't properly interpret the packetIn", e);
+                }
+            }
         }
     }
 }