OutboundPacket: add inPort method

Allows to provide a different input port to be considered when emitting
the packet. It is useful on OpenFlow devices so one can use ALL or FLOOD
actions as output and not have the packet sent back on the input port.

Default is controller port, as it was before.

The required changes were also implemented in OpenFlowPacketProvider

Change-Id: I0a050b983b5de9935254599e8093dc59ad7a4ccf
diff --git a/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java b/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java
index 9747720..e66d15f 100644
--- a/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java
+++ b/core/api/src/main/java/org/onosproject/net/packet/DefaultOutboundPacket.java
@@ -15,13 +15,13 @@
  */
 package org.onosproject.net.packet;
 
-import java.nio.ByteBuffer;
-import java.util.Objects;
-
+import com.google.common.base.MoreObjects;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.TrafficTreatment;
 
-import com.google.common.base.MoreObjects;
+import java.nio.ByteBuffer;
+import java.util.Objects;
 
 /**
  * Default implementation of an immutable outbound packet.
@@ -30,6 +30,7 @@
     private final DeviceId sendThrough;
     private final TrafficTreatment treatment;
     private final ByteBuffer data;
+    private final PortNumber inPort;
 
     /**
      * Creates an immutable outbound packet.
@@ -43,6 +44,24 @@
         this.sendThrough = sendThrough;
         this.treatment = treatment;
         this.data = data;
+        this.inPort = null;
+    }
+
+    /**
+     * Creates an immutable outbound packet.
+     *
+     * @param sendThrough identifier through which to send the packet
+     * @param treatment   list of packet treatments
+     * @param data        raw packet data
+     * @param inPort      input port to be used for the packet
+     */
+    public DefaultOutboundPacket(DeviceId sendThrough,
+                                 TrafficTreatment treatment, ByteBuffer data,
+                                 PortNumber inPort) {
+        this.sendThrough = sendThrough;
+        this.treatment = treatment;
+        this.data = data;
+        this.inPort = inPort;
     }
 
     @Override
@@ -62,8 +81,13 @@
     }
 
     @Override
+    public PortNumber inPort() {
+        return inPort;
+    }
+
+    @Override
     public int hashCode() {
-        return Objects.hash(sendThrough, treatment, data);
+        return Objects.hash(sendThrough, treatment, data, inPort);
     }
 
     @Override
@@ -75,7 +99,8 @@
             final DefaultOutboundPacket other = (DefaultOutboundPacket) obj;
             return Objects.equals(this.sendThrough, other.sendThrough) &&
                     Objects.equals(this.treatment, other.treatment) &&
-                    Objects.equals(this.data, other.data);
+                    Objects.equals(this.data, other.data) &&
+                    Objects.equals(this.inPort, other.inPort);
         }
         return false;
     }
@@ -84,6 +109,7 @@
         return MoreObjects.toStringHelper(this)
                 .add("sendThrough", sendThrough)
                 .add("treatment", treatment)
+                .add("inPort", inPort)
                 .toString();
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java b/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java
index 93d754b..30b32fe 100644
--- a/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java
+++ b/core/api/src/main/java/org/onosproject/net/packet/OutboundPacket.java
@@ -16,6 +16,7 @@
 package org.onosproject.net.packet;
 
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.TrafficTreatment;
 
 import java.nio.ByteBuffer;
@@ -48,4 +49,15 @@
      */
     ByteBuffer data();
 
+    /**
+     * Returns the input port of this packet.
+     *
+     * Defaults to controller port. This is useful for actions that involve the input port
+     * such as ALL or FLOOD.
+     *
+     * @return the input port to be used for this packet.
+     */
+    default PortNumber inPort() {
+        return PortNumber.CONTROLLER;
+    }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java b/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java
index f9deaf7..3f7b299 100644
--- a/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java
+++ b/core/api/src/test/java/org/onosproject/net/packet/DefaultOutboundPacketTest.java
@@ -18,6 +18,7 @@
 import java.nio.ByteBuffer;
 
 import org.junit.Test;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.intent.IntentTestsMocks;
 import org.onlab.packet.Ethernet;
@@ -47,10 +48,17 @@
             new DefaultOutboundPacket(did("d1"),
                     treatment,
                     byteBuffer);
+    private final PortNumber inPort = PortNumber.portNumber(1);
+    final DefaultOutboundPacket packet1WithInPort =
+            new DefaultOutboundPacket(did("d1"),
+                    treatment,
+                    byteBuffer,
+                    inPort);
     final DefaultOutboundPacket packet2 =
             new DefaultOutboundPacket(did("d2"),
                     treatment,
                     byteBuffer);
+
     /**
      * Checks that the DefaultOutboundPacket class is immutable.
      */
@@ -67,6 +75,7 @@
         new EqualsTester()
                 .addEqualityGroup(packet1, sameAsPacket1)
                 .addEqualityGroup(packet2)
+                .addEqualityGroup(packet1WithInPort)
                 .testEquals();
     }
 
@@ -78,5 +87,7 @@
         assertThat(packet1.sendThrough(), equalTo(did("d1")));
         assertThat(packet1.data(), equalTo(byteBuffer));
         assertThat(packet1.treatment(), equalTo(treatment));
+        assertThat(packet1.inPort(), equalTo(null));
+        assertThat(packet1WithInPort.inPort(), equalTo(inPort));
     }
 }
diff --git a/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java b/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java
index a363339..300ffcb 100644
--- a/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java
+++ b/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java
@@ -111,13 +111,21 @@
             return;
         }
 
+        OFPort inPort;
+        if (packet.inPort() != null) {
+            inPort = portDesc(packet.inPort()).getPortNo();
+        } else {
+            inPort = OFPort.CONTROLLER;
+        }
+
         //Ethernet eth = new Ethernet();
         //eth.deserialize(packet.data().array(), 0, packet.data().array().length);
         OFPortDesc p = null;
         for (Instruction inst : packet.treatment().allInstructions()) {
             if (inst.type().equals(Instruction.Type.OUTPUT)) {
                 p = portDesc(((OutputInstruction) inst).port());
-                OFPacketOut po = packetOut(sw, packet.data().array(), p.getPortNo());
+
+                OFPacketOut po = packetOut(sw, packet.data().array(), p.getPortNo(), inPort);
                 sw.sendMsg(po);
             }
         }
@@ -131,13 +139,14 @@
         return builder.build();
     }
 
-    private OFPacketOut packetOut(OpenFlowSwitch sw, byte[] eth, OFPort out) {
+    private OFPacketOut packetOut(OpenFlowSwitch sw, byte[] eth, OFPort out, OFPort inPort) {
         OFPacketOut.Builder builder = sw.factory().buildPacketOut();
         OFAction act = sw.factory().actions()
                 .buildOutput()
                 .setPort(out)
                 .build();
         builder.setBufferId(OFBufferId.NO_BUFFER)
+                .setInPort(inPort)
                 .setActions(Collections.singletonList(act))
                 .setData(eth);
         if (sw.factory().getVersion().getWireVersion() <= OFVersion.OF_14.getWireVersion()) {
diff --git a/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java b/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java
index 46305a6..9a64611 100644
--- a/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java
+++ b/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java
@@ -83,12 +83,16 @@
     private static final Instruction INST1 = Instructions.createOutput(P1);
     private static final Instruction INST2 = Instructions.createOutput(P2);
     private static final Instruction INST3 = Instructions.createOutput(P3);
+    private static final Instruction INST_ALL = Instructions.createOutput(PortNumber.ALL);
 
     private static final OFPortDesc PD1 = portDesc(PN1);
     private static final OFPortDesc PD2 = portDesc(PN2);
+    private static final OFPortDesc PD_ALL = portDesc((int) PortNumber.ALL.toLong());
 
     private static final List<OFPortDesc> PLIST = Lists.newArrayList(PD1, PD2);
     private static final TrafficTreatment TR = treatment(INST1, INST2);
+    private static final List<OFPortDesc> PLIST_ALL = Lists.newArrayList(PD_ALL);
+    private static final TrafficTreatment TR_ALL = treatment(INST_ALL);
     private static final TrafficTreatment TR_MISSING = treatment(INST1, INST3);
 
     private static final byte[] ANY = new byte[] {0, 0, 0, 0};
@@ -155,6 +159,14 @@
         assertEquals("message not sent", PLIST.size(), sw.sent.size());
         sw.sent.clear();
 
+        //Send with different inPort
+        OutboundPacket inPortPkt = outPacket(DID, TR_ALL, eth, PortNumber.portNumber(1));
+        sw.setRole(RoleState.MASTER);
+        provider.emit(inPortPkt);
+        assertEquals("invalid switch", sw, controller.current);
+        assertEquals("message not sent", PLIST_ALL.size(), sw.sent.size());
+        sw.sent.clear();
+
         //wrong Role
         //sw.setRole(RoleState.SLAVE);
         //provider.emit(passPkt);
@@ -213,6 +225,16 @@
         return new DefaultOutboundPacket(d, t, buf);
     }
 
+    private OutboundPacket outPacket(DeviceId d, TrafficTreatment t, Ethernet e,
+                                     PortNumber inPort) {
+        ByteBuffer buf = null;
+        if (e != null) {
+            buf = ByteBuffer.wrap(e.serialize());
+        }
+        return new DefaultOutboundPacket(d, t, buf, inPort);
+    }
+
+
     private class TestPacketRegistry implements PacketProviderRegistry {
 
         PacketProvider listener = null;