ONOS-2578 Added codec for mod tcp/udp port instructions

Change-Id: Iab1eac26afe47e0b1c13ce94e152826b0828e455
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java b/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
index 568fa76..6a97a07 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/DecodeInstructionCodecHelper.java
@@ -18,19 +18,21 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
 import org.onlab.packet.VlanId;
 import org.onosproject.net.ChannelSpacing;
 import org.onosproject.net.GridType;
 import org.onosproject.net.Lambda;
 import org.onosproject.net.OchSignal;
 import org.onosproject.net.PortNumber;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.flow.instructions.L0ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.flow.instructions.L4ModificationInstruction;
 
 import static org.onlab.util.Tools.nullIsIllegal;
 
@@ -172,6 +174,36 @@
     }
 
     /**
+     * Decodes a Layer 4 instruction.
+     *
+     * @return instruction object decoded from the JSON
+     * @throws IllegalArgumentException if the JSON is invalid
+     */
+    private Instruction decodeL4() {
+        String subType = json.get(InstructionCodec.SUBTYPE).asText();
+
+        if (subType.equals(L4ModificationInstruction.L4SubType.TCP_DST.name())) {
+            TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.TCP_PORT),
+                    InstructionCodec.TCP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+            return Instructions.modTcpDst(tcpPort);
+        } else if (subType.equals(L4ModificationInstruction.L4SubType.TCP_SRC.name())) {
+            TpPort tcpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.TCP_PORT),
+                    InstructionCodec.TCP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+            return Instructions.modTcpSrc(tcpPort);
+        } else if (subType.equals(L4ModificationInstruction.L4SubType.UDP_DST.name())) {
+            TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.UDP_PORT),
+                    InstructionCodec.UDP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+            return Instructions.modUdpDst(udpPort);
+        } else if (subType.equals(L4ModificationInstruction.L4SubType.UDP_SRC.name())) {
+            TpPort udpPort = TpPort.tpPort(nullIsIllegal(json.get(InstructionCodec.UDP_PORT),
+                    InstructionCodec.UDP_PORT + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+            return Instructions.modUdpSrc(udpPort);
+        }
+        throw new IllegalArgumentException("L4 Instruction subtype "
+                + subType + " is not supported");
+    }
+
+    /**
      * Decodes the JSON into an instruction object.
      *
      * @return Criterion object
@@ -193,6 +225,8 @@
             return decodeL2();
         } else if (type.equals(Instruction.Type.L3MODIFICATION.name())) {
             return decodeL3();
+        } else if (type.equals(Instruction.Type.L4MODIFICATION.name())) {
+            return decodeL4();
         }
         throw new IllegalArgumentException("Instruction type "
                 + type + " is not supported");
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java b/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
index 6927680..8a4dfa5 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/EncodeInstructionCodecHelper.java
@@ -22,6 +22,7 @@
 import org.onosproject.net.flow.instructions.L0ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
+import org.onosproject.net.flow.instructions.L4ModificationInstruction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -168,6 +169,36 @@
     }
 
     /**
+     * Encode a L4 modification instruction.
+     *
+     * @param result json node that the instruction attributes are added to
+     */
+    private void encodeL4(ObjectNode result) {
+        L4ModificationInstruction instruction =
+                (L4ModificationInstruction) this.instruction;
+        result.put(InstructionCodec.SUBTYPE, instruction.subtype().name());
+        switch (instruction.subtype()) {
+            case TCP_DST:
+            case TCP_SRC:
+                final L4ModificationInstruction.ModTransportPortInstruction modTcpPortInstruction =
+                        (L4ModificationInstruction.ModTransportPortInstruction) instruction;
+                result.put(InstructionCodec.TCP_PORT, modTcpPortInstruction.port().toInt());
+                break;
+
+            case UDP_DST:
+            case UDP_SRC:
+                final L4ModificationInstruction.ModTransportPortInstruction modUdpPortInstruction =
+                        (L4ModificationInstruction.ModTransportPortInstruction) instruction;
+                result.put(InstructionCodec.UDP_PORT, modUdpPortInstruction.port().toInt());
+                break;
+
+            default:
+                log.info("Cannot convert L4 subtype of {}", instruction.subtype());
+                break;
+        }
+    }
+
+    /**
      * Encodes the given instruction into JSON.
      *
      * @return JSON object node representing the instruction
@@ -198,6 +229,10 @@
                 encodeL3(result);
                 break;
 
+            case L4MODIFICATION:
+                encodeL4(result);
+                break;
+
             default:
                 log.info("Cannot convert instruction type of {}", instruction.type());
                 break;
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
index 6fa4ae0..f4d5008 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/InstructionCodec.java
@@ -48,6 +48,8 @@
     protected static final String SLOT_GRANULARITY = "slotGranularity";
     protected static final String ETHERNET_TYPE = "ethernetType";
     protected static final String TUNNEL_ID = "tunnelId";
+    protected static final String TCP_PORT = "tcpPort";
+    protected static final String UDP_PORT = "udpPort";
 
     protected static final String MISSING_MEMBER_MESSAGE =
             " member is required in Instruction";
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
index b18dc3e..2ae8cb5 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/FlowRuleCodecTest.java
@@ -62,14 +62,15 @@
 import org.onosproject.net.flow.criteria.UdpPortCriterion;
 import org.onosproject.net.flow.criteria.VlanIdCriterion;
 import org.onosproject.net.flow.criteria.VlanPcpCriterion;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onosproject.net.flow.instructions.Instruction;
 import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.flow.instructions.L0ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.flow.instructions.L4ModificationInstruction;
 
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.expect;
@@ -199,6 +200,9 @@
                     } else if (instruction.type() == Instruction.Type.L3MODIFICATION) {
                         subType = ((L3ModificationInstruction) instruction)
                                 .subtype().name();
+                    } else if (instruction.type() == Instruction.Type.L4MODIFICATION) {
+                        subType = ((L4ModificationInstruction) instruction)
+                                .subtype().name();
                     } else {
                         subType = "";
                     }
@@ -206,7 +210,7 @@
                             instruction.type().name() + "/" + subType, instruction);
                 });
 
-        assertThat(rule.treatment().allInstructions().size(), is(20));
+        assertThat(rule.treatment().allInstructions().size(), is(24));
 
         Instruction instruction;
 
@@ -327,6 +331,30 @@
         assertThat(och.lambda().slotGranularity(), is(8));
         assertThat(och.lambda().gridType(), is(GridType.DWDM));
         assertThat(och.lambda().channelSpacing(), is(ChannelSpacing.CHL_100GHZ));
+
+        instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+                L4ModificationInstruction.L4SubType.TCP_DST.name());
+        assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+        assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+                .port().toInt(), is(40001));
+
+        instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+                L4ModificationInstruction.L4SubType.TCP_SRC.name());
+        assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+        assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+                .port().toInt(), is(40002));
+
+        instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+                L4ModificationInstruction.L4SubType.UDP_DST.name());
+        assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+        assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+                .port().toInt(), is(40003));
+
+        instruction = getInstruction(Instruction.Type.L4MODIFICATION,
+                L4ModificationInstruction.L4SubType.UDP_SRC.name());
+        assertThat(instruction.type(), is(Instruction.Type.L4MODIFICATION));
+        assertThat(((L4ModificationInstruction.ModTransportPortInstruction) instruction)
+                .port().toInt(), is(40004));
     }
 
     SortedMap<String, Criterion> criteria = new TreeMap<>();
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json b/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
index b3e4e4c..dbb8f51 100644
--- a/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/instructions-flow.json
@@ -27,7 +27,11 @@
         {"type":"L3MODIFICATION","subtype":"IPV6_FLABEL", "flowLabel":8},
         {"type":"L0MODIFICATION","subtype":"LAMBDA","lambda":7},
         {"type":"L0MODIFICATION","subtype":"OCH","gridType":"DWDM",
-          "channelSpacing":"CHL_100GHZ","spacingMultiplier":4,"slotGranularity":8}
+          "channelSpacing":"CHL_100GHZ","spacingMultiplier":4,"slotGranularity":8},
+        {"type":"L4MODIFICATION","subtype":"TCP_DST","tcpPort":40001},
+        {"type":"L4MODIFICATION","subtype":"TCP_SRC","tcpPort":40002},
+        {"type":"L4MODIFICATION","subtype":"UDP_DST","udpPort":40003},
+        {"type":"L4MODIFICATION","subtype":"UDP_SRC","udpPort":40004}
       ],
       "deferred":[]
   },