[ONOS-6809] Implementation for packet out in p4Runtime

Change-Id: I873a1fd18529fe9fd41aa33f862298892ece7d1c
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
index b24bc70..ee1e608 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/P4InfoBrowser.java
@@ -53,6 +53,8 @@
             new EntityBrowser<>("controller packet metadata");
     private final Map<Integer, EntityBrowser<Action.Param>> actionParams = Maps.newHashMap();
     private final Map<Integer, EntityBrowser<MatchField>> matchFields = Maps.newHashMap();
+    private final Map<Integer, EntityBrowser<ControllerPacketMetadata.Metadata>> ctrlPktMetadatasMetadata =
+            Maps.newHashMap();
 
     /**
      * Creates a new browser for the given P4Info.
@@ -107,7 +109,16 @@
                 entity -> directMeters.addWithPreamble(entity.getPreamble(), entity));
 
         p4info.getControllerPacketMetadataList().forEach(
-                entity -> ctrlPktMetadatas.addWithPreamble(entity.getPreamble(), entity));
+                entity -> {
+                    ctrlPktMetadatas.addWithPreamble(entity.getPreamble(), entity);
+                    // Index control packet metadata metadata.
+                    int ctrlPktMetadataId = entity.getPreamble().getId();
+                    String ctrlPktMetadataName = entity.getPreamble().getName();
+                    EntityBrowser<ControllerPacketMetadata.Metadata> metadataBrowser = new EntityBrowser<>(format(
+                            "metadata field for controller packet metadata '%s'", ctrlPktMetadataName));
+                    entity.getMetadataList().forEach(m -> metadataBrowser.add(m.getName(), null, m.getId(), m));
+                    ctrlPktMetadatasMetadata.put(ctrlPktMetadataId, metadataBrowser);
+                });
     }
 
     private String extractMatchFieldSimpleName(String name) {
@@ -212,7 +223,7 @@
      * Returns a browser for match fields of the given table.
      *
      * @param tableId table identifier
-     * @return controller packet metadata browser
+     * @return match field browser
      * @throws NotFoundException if the table cannot be found
      */
     EntityBrowser<MatchField> matchFields(int tableId) throws NotFoundException {
@@ -222,6 +233,20 @@
     }
 
     /**
+     * Returns a browser for metadata fields of the controller packet metadata.
+     *
+     * @param controllerPacketMetadataId controller packet metadata identifier
+     * @return metadata browser
+     * @throws NotFoundException controller packet metadata cannot be foudn
+     */
+    EntityBrowser<ControllerPacketMetadata.Metadata> packetMetadatas(int controllerPacketMetadataId)
+            throws NotFoundException {
+        // Throws exception if controller packet metadata id is not found.
+        ctrlPktMetadatas.getById(controllerPacketMetadataId);
+        return ctrlPktMetadatasMetadata.get(controllerPacketMetadataId);
+    }
+
+    /**
      * Browser of P4Info entities.
      *
      * @param <T> protobuf message type
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 132689a..bb2d16b 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
@@ -47,6 +47,7 @@
 import p4.P4RuntimeOuterClass.Uint128;
 import p4.P4RuntimeOuterClass.Update;
 import p4.P4RuntimeOuterClass.WriteRequest;
+import p4.config.P4InfoOuterClass.P4Info;
 import p4.tmp.P4Config;
 
 import java.io.IOException;
@@ -72,8 +73,8 @@
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType;
 import static org.slf4j.LoggerFactory.getLogger;
 import static p4.P4RuntimeOuterClass.Entity.EntityCase.TABLE_ENTRY;
+import static p4.P4RuntimeOuterClass.PacketOut;
 import static p4.P4RuntimeOuterClass.SetForwardingPipelineConfigRequest.Action.VERIFY_AND_COMMIT;
-import static p4.config.P4InfoOuterClass.P4Info;
 
 /**
  * Implementation of a P4Runtime client.
@@ -162,33 +163,7 @@
 
     @Override
     public CompletableFuture<Boolean> packetOut(PiPacketOperation packet, PiPipeconf pipeconf) {
-        CompletableFuture<Boolean> result = new CompletableFuture<>();
-//        P4InfoBrowser browser = null; //PipeconfHelper.getP4InfoBrowser(pipeconf);
-//        try {
-//            ControllerPacketMetadata controllerPacketMetadata =
-//                    browser.controllerPacketMetadatas().getByName("packet_out");
-//            PacketOut.Builder packetOutBuilder = PacketOut.newBuilder();
-//            packetOutBuilder.addAllMetadata(packet.metadatas().stream().map(metadata -> {
-//                //FIXME we are assuming that there is no more than one metadata per name.
-//                int metadataId = controllerPacketMetadata.getMetadataList().stream().filter(metadataInfo -> {
-//                   return  metadataInfo.getName().equals(metadata.id().name());
-//                }).findFirst().get().getId();
-//                return PacketMetadata.newBuilder()
-//                        .setMetadataId(metadataId)
-//                        .setValue(ByteString.copyFrom(metadata.value().asReadOnlyBuffer()))
-//                        .build();
-//            }).filter(Objects::nonNull).collect(Collectors.toList()));
-//            packetOutBuilder.setPayload(ByteString.copyFrom(packet.data().asReadOnlyBuffer()));
-//            PacketOut packetOut = packetOutBuilder.build();
-//            StreamMessageRequest packetOutRequest = StreamMessageRequest
-//                    .newBuilder().setPacket(packetOut).build();
-//            streamRequestObserver.onNext(packetOutRequest);
-//            result.complete(true);
-//        } catch (P4InfoBrowser.NotFoundException e) {
-//            log.error("Cant find metadata with name \"packet_out\" in p4Info file.");
-//            result.complete(false);
-//        }
-        return result;
+        return supplyInContext(() -> doPacketOut(packet, pipeconf));
     }
 
     /* Blocking method implementations below */
@@ -354,6 +329,27 @@
         return TableEntryEncoder.decode(tableEntryMsgs, pipeconf);
     }
 
+    private boolean doPacketOut(PiPacketOperation packet, PiPipeconf pipeconf) {
+        try {
+            //encode the PiPacketOperation into a PacketOut
+            PacketOut packetOut = PacketIOCodec.encodePacketOut(packet, pipeconf);
+
+            //Build the request
+            StreamMessageRequest packetOutRequest = StreamMessageRequest
+                    .newBuilder().setPacket(packetOut).build();
+
+            //Send the request
+            streamRequestObserver.onNext(packetOutRequest);
+
+        } catch (P4InfoBrowser.NotFoundException e) {
+            log.error("Cant find expected metadata in p4Info file. {}", e.getMessage());
+            log.debug("Exception", e);
+            return false;
+        }
+        return true;
+    }
+
+
     @Override
     public void shutdown() {
 
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
new file mode 100644
index 0000000..0461154
--- /dev/null
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2017-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.p4runtime.ctl;
+
+import com.google.protobuf.ByteString;
+import org.onosproject.net.pi.model.PiPipeconf;
+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.slf4j.LoggerFactory.getLogger;
+import static p4.P4RuntimeOuterClass.PacketMetadata;
+
+/**
+ * Encoder of packet metadata, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
+ */
+final class PacketIOCodec {
+
+    private static final Logger log = getLogger(PacketIOCodec.class);
+
+    private static final String PACKET_OUT = "packet_out";
+
+    // TODO: implement cache of encoded entities.
+
+    private PacketIOCodec() {
+        // hide.
+    }
+
+    /**
+     * Returns a P4Runtime packet out protobuf message, encoded from the given PiPacketOperation
+     * for the given pipeconf. If a PI packet metadata inside the PacketOperation cannot be encoded,
+     * it is skipped, hence the returned PacketOut 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 packet   PI pakcet 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)
+            throws NotFoundException {
+
+        //Get the P4browser
+        P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
+
+        //Get the packet out packet metadata
+        P4InfoOuterClass.ControllerPacketMetadata controllerPacketMetadata =
+                browser.controllerPacketMetadatas().getByName(PACKET_OUT);
+        P4RuntimeOuterClass.PacketOut.Builder packetOutBuilder = P4RuntimeOuterClass.PacketOut.newBuilder();
+
+        //outer controller packet metadata id
+        int controllerPacketMetadataId = controllerPacketMetadata.getPreamble().getId();
+
+        //Add all its metadata to the packet out
+        packetOutBuilder.addAllMetadata(encodePacketMetadata(packet, browser, controllerPacketMetadataId));
+
+        //Set the packet out payload
+        packetOutBuilder.setPayload(ByteString.copyFrom(packet.data().asReadOnlyBuffer()));
+        return packetOutBuilder.build();
+
+    }
+
+    private static List<PacketMetadata> encodePacketMetadata(PiPacketOperation packet,
+                                                             P4InfoBrowser browser, int controllerPacketMetadataId) {
+        return packet.metadatas().stream().map(metadata -> {
+            try {
+                //get each metadata id
+                int metadataId = browser.packetMetadatas(controllerPacketMetadataId)
+                        .getByName(metadata.id().name()).getId();
+
+                //Add the metadata id and it's data the packet out
+                return PacketMetadata.newBuilder()
+                        .setMetadataId(metadataId)
+                        .setValue(ByteString.copyFrom(metadata.value().asReadOnlyBuffer()))
+                        .build();
+            } catch (NotFoundException e) {
+                log.error("Cant find metadata with name {} in p4Info file.", metadata.id().name());
+                return null;
+            }
+        }).filter(Objects::nonNull).collect(Collectors.toList());
+    }
+
+    //TODO: add decode packets
+
+}
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
index daeb62f..92ad3ca 100644
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/TableEntryEncoderTest.java
@@ -32,6 +32,7 @@
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.slf4j.Logger;
 import p4.P4RuntimeOuterClass.Action;
 import p4.P4RuntimeOuterClass.TableEntry;
 
@@ -48,9 +49,15 @@
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
 import static org.onosproject.p4runtime.ctl.TableEntryEncoder.decode;
 import static org.onosproject.p4runtime.ctl.TableEntryEncoder.encode;
+import static org.slf4j.LoggerFactory.getLogger;
+
+//import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
+//import org.onosproject.drivers.bmv2.Bmv2DefaultInterpreter;
 
 public class TableEntryEncoderTest {
 
+    private final Logger log = getLogger(getClass());
+
     private static final String TABLE_0 = "table0";
     private static final String SET_EGRESS_PORT = "set_egress_port";
     private static final String PORT = "port";
@@ -68,6 +75,7 @@
     private final PiPipeconf defaultPipeconf = DefaultPiPipeconf.builder()
             .withId(new PiPipeconfId("mock"))
             .withPipelineModel(Bmv2PipelineModelParser.parse(jsonUrl))
+//            .addBehaviour(PiPipelineInterpreter.class, Bmv2DefaultInterpreter.class)
             .addExtension(P4_INFO_TEXT, p4InfoUrl)
             .addExtension(BMV2_JSON, jsonUrl)
             .build();
@@ -91,10 +99,10 @@
             .withFieldMatch(new PiTernaryFieldMatch(inPortFieldId, portValue, ImmutableByteSequence.ofOnes(2)))
             .withFieldMatch(new PiTernaryFieldMatch(ethTypeFieldId, portValue, ImmutableByteSequence.ofOnes(2)))
             .withAction(PiAction
-                                .builder()
-                                .withId(outActionId)
-                                .withParameter(new PiActionParam(portParamId, portValue))
-                                .build())
+                    .builder()
+                    .withId(outActionId)
+                    .withParameter(new PiActionParam(portParamId, portValue))
+                    .build())
             .withPriority(1)
             .withCookie(2)
             .build();
@@ -165,19 +173,20 @@
     }
 
 //    @Test
-//    public void testRuntime() throws ExecutionException, InterruptedException {
+//    public void testRuntime() throws ExecutionException, InterruptedException,
+//            PiPipelineInterpreter.PiInterpreterException, IllegalAccessException, InstantiationException {
 //
 //        // FIXME: remove me.
 //
 //        P4RuntimeControllerImpl controller = new P4RuntimeControllerImpl();
 //        GrpcControllerImpl grpcController = new GrpcControllerImpl();
 //        controller.grpcController = grpcController;
-//        GrpcControllerImpl.ENABLE_MESSAGE_LOG = true;
+//        GrpcControllerImpl.enableMessageLog = true;
 //        grpcController.activate();
 //        DeviceId deviceId = DeviceId.deviceId("dummy:1");
 //
 //        ManagedChannelBuilder channelBuilder = NettyChannelBuilder
-//                .forAddress("192.168.56.102", 55044)
+//                .forAddress("192.168.56.102", 59975)
 //                .usePlaintext(true);
 //
 //        assert (controller.createClient(deviceId, 1, channelBuilder));
@@ -188,10 +197,37 @@
 //
 //        assert(client.initStreamChannel().get());
 //
-//        assert(client.dumpTable(PiTableId.of(TABLE_0), defaultPipeconf).get().size() == 0);
+//        log.info("++++++++++++++++++++++++++++");
 //
-//        assert(client.writeTableEntries(Lists.newArrayList(piTableEntry), INSERT, defaultPipeconf).get());
+//        PiPipelineInterpreter interpreter = (PiPipelineInterpreter) defaultPipeconf
+//                .implementation(PiPipelineInterpreter.class)
+//                .orElse(null)
+//                .newInstance();
 //
-//        assert(client.dumpTable(PiTableId.of(TABLE_0), defaultPipeconf).get().size() == 1);
+//        TrafficTreatment t = DefaultTrafficTreatment.builder()
+//                .setOutput(PortNumber.portNumber(830L)).build();
+//        byte[] payload = new byte[1000];
+////        payload[0] = 1;
+//        Arrays.fill( payload, (byte) 1 );
+//
+//        OutboundPacket packet = new DefaultOutboundPacket(
+//                deviceId, t, ByteBuffer.wrap(payload));
+//
+//
+//        Collection<PiPacketOperation> operations = interpreter.mapOutboundPacket(packet,defaultPipeconf);
+//        log.info("{}", operations);
+//        operations.forEach(piPacketOperation -> {
+//            try {
+//                client.packetOut(piPacketOperation, defaultPipeconf).get();
+//            } catch (InterruptedException | ExecutionException e) {
+//               log.error("{}",e);
+//            }
+//        });
+//
+////        assert(client.dumpTable(PiTableId.of(TABLE_0), defaultPipeconf).get().size() == 0);
+//
+////        assert(client.writeTableEntries(Lists.newArrayList(piTableEntry), INSERT, defaultPipeconf).get());
+//
+////        assert(client.dumpTable(PiTableId.of(TABLE_0), defaultPipeconf).get().size() == 1);
 //    }
 }
diff --git a/protocols/p4runtime/ctl/src/test/resources/default.p4info b/protocols/p4runtime/ctl/src/test/resources/default.p4info
index bd3649d..b224b87 100644
--- a/protocols/p4runtime/ctl/src/test/resources/default.p4info
+++ b/protocols/p4runtime/ctl/src/test/resources/default.p4info
@@ -111,3 +111,37 @@
   }
   direct_table_id: 33617813
 }
+controller_packet_metadata {
+  preamble {
+    id: 2868941301
+    name: "packet_in"
+    annotations: "@controller_header(\"packet_in\")"
+  }
+  metadata {
+    id: 1
+    name: "ingress_port"
+    bitwidth: 9
+  }
+  metadata {
+    id: 2
+    name: "other1"
+    bitwidth: 32
+  }
+}
+controller_packet_metadata {
+  preamble {
+    id: 2868916615
+    name: "packet_out"
+    annotations: "@controller_header(\"packet_out\")"
+  }
+  metadata {
+    id: 1
+    name: "egress_port"
+    bitwidth: 9
+  }
+  metadata {
+    id: 2
+    name: "other2"
+    bitwidth: 32
+  }
+}