[ONOS-7583]Adding PI flow rules via REST API and unit tests

Change-Id: I15537097e7b6f8138f8ae43676d5dd22d8b9093a
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java
index ec1b061..4be8008 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CriterionCodec.java
@@ -73,6 +73,14 @@
     static final String TRIBUTARY_SLOT_LEN = "tributarySlotLen";
     static final String TRIBUTARY_SLOT_BITMAP = "tributarySlotBitmap";
     static final String ODU_SIGNAL_TYPE = "oduSignalType";
+    static final String PI_MATCHES = "matches";
+    static final String PI_MATCH_FIELD_ID = "field";
+    static final String PI_MATCH_TYPE = "match";
+    static final String PI_MATCH_VALUE = "value";
+    static final String PI_MATCH_PREFIX = "prefixLength";
+    static final String PI_MATCH_MASK = "mask";
+    static final String PI_MATCH_HIGH_VALUE = "highValue";
+    static final String PI_MATCH_LOW_VALUE = "lowValue";
 
     @Override
     public ObjectNode encode(Criterion criterion, CodecContext context) {
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java b/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java
index e8056e2..9b1b7c2 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/DecodeCriterionCodecHelper.java
@@ -33,6 +33,9 @@
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.criteria.Criteria;
 import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchType;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -109,6 +112,7 @@
         decoderMap.put(Criterion.Type.TUNNEL_ID.name(), new TunnelIdDecoder());
         decoderMap.put(Criterion.Type.ODU_SIGID.name(), new OduSigIdDecoder());
         decoderMap.put(Criterion.Type.ODU_SIGTYPE.name(), new OduSigTypeDecoder());
+        decoderMap.put(Criterion.Type.PROTOCOL_INDEPENDENT.name(), new PiDecoder());
     }
 
     private class EthTypeDecoder implements CriterionDecoder {
@@ -582,6 +586,89 @@
         }
     }
 
+    private class PiDecoder implements CriterionDecoder {
+        @Override
+        public Criterion decodeCriterion(ObjectNode json) {
+            PiCriterion.Builder builder = PiCriterion.builder();
+            JsonNode matchesNode = nullIsIllegal(json.get(CriterionCodec.PI_MATCHES),
+                                                 CriterionCodec.PI_MATCHES + MISSING_MEMBER_MESSAGE);
+            if (matchesNode.isArray()) {
+                for (JsonNode node : matchesNode) {
+                    String type = nullIsIllegal(node.get(CriterionCodec.PI_MATCH_TYPE),
+                                                CriterionCodec.PI_MATCH_TYPE + MISSING_MEMBER_MESSAGE).asText();
+                    switch (PiMatchType.valueOf(type.toUpperCase())) {
+                        case EXACT:
+                            builder.matchExact(
+                                    PiMatchFieldId.of(
+                                            nullIsIllegal(node.get(CriterionCodec.PI_MATCH_FIELD_ID),
+                                                          CriterionCodec.PI_MATCH_FIELD_ID +
+                                                                  MISSING_MEMBER_MESSAGE).asText()),
+                                    HexString.fromHexString(nullIsIllegal(node.get(CriterionCodec.PI_MATCH_VALUE),
+                                                                    CriterionCodec.PI_MATCH_VALUE +
+                                                                            MISSING_MEMBER_MESSAGE).asText(), null));
+                            break;
+                        case LPM:
+                            builder.matchLpm(
+                                    PiMatchFieldId.of(
+                                            nullIsIllegal(node.get(CriterionCodec.PI_MATCH_FIELD_ID),
+                                                          CriterionCodec.PI_MATCH_FIELD_ID +
+                                                                  MISSING_MEMBER_MESSAGE).asText()),
+                                    HexString.fromHexString(nullIsIllegal(node.get(CriterionCodec.PI_MATCH_VALUE),
+                                                                    CriterionCodec.PI_MATCH_VALUE +
+                                                                            MISSING_MEMBER_MESSAGE).asText(), null),
+                                    nullIsIllegal(node.get(CriterionCodec.PI_MATCH_PREFIX),
+                                                  CriterionCodec.PI_MATCH_PREFIX +
+                                                          MISSING_MEMBER_MESSAGE).asInt());
+                            break;
+                        case TERNARY:
+                            builder.matchTernary(
+                                    PiMatchFieldId.of(
+                                            nullIsIllegal(node.get(CriterionCodec.PI_MATCH_FIELD_ID),
+                                                          CriterionCodec.PI_MATCH_FIELD_ID +
+                                                                  MISSING_MEMBER_MESSAGE).asText()),
+                                    HexString.fromHexString(nullIsIllegal(node.get(CriterionCodec.PI_MATCH_VALUE),
+                                                                    CriterionCodec.PI_MATCH_VALUE +
+                                                                            MISSING_MEMBER_MESSAGE).asText(), null),
+                                    HexString.fromHexString(nullIsIllegal(node.get(CriterionCodec.PI_MATCH_MASK),
+                                                                    CriterionCodec.PI_MATCH_MASK +
+                                                                            MISSING_MEMBER_MESSAGE).asText(), null));
+                            break;
+                        case RANGE:
+                            builder.matchRange(
+                                    PiMatchFieldId.of(
+                                            nullIsIllegal(node.get(CriterionCodec.PI_MATCH_FIELD_ID),
+                                                          CriterionCodec.PI_MATCH_FIELD_ID +
+                                                                  MISSING_MEMBER_MESSAGE).asText()),
+                                    HexString.fromHexString(nullIsIllegal(node.get(CriterionCodec.PI_MATCH_LOW_VALUE),
+                                                                    CriterionCodec.PI_MATCH_LOW_VALUE +
+                                                                            MISSING_MEMBER_MESSAGE).asText(), null),
+                                    HexString.fromHexString(nullIsIllegal(node.get(CriterionCodec.PI_MATCH_HIGH_VALUE),
+                                                                    CriterionCodec.PI_MATCH_HIGH_VALUE +
+                                                                            MISSING_MEMBER_MESSAGE).asText(), null)
+                                    );
+                            break;
+                        case VALID:
+                            builder.matchValid(
+                                    PiMatchFieldId.of(
+                                            nullIsIllegal(node.get(CriterionCodec.PI_MATCH_FIELD_ID),
+                                                          CriterionCodec.PI_MATCH_FIELD_ID +
+                                                                  MISSING_MEMBER_MESSAGE).asText()),
+                                    nullIsIllegal(node.get(CriterionCodec.PI_MATCH_VALUE),
+                                                  CriterionCodec.PI_MATCH_VALUE +
+                                                          MISSING_MEMBER_MESSAGE).asBoolean());
+                            break;
+                        default:
+                            throw new IllegalArgumentException("Type " + type + " is unsupported");
+                    }
+                }
+            } else {
+                throw new IllegalArgumentException("Protocol-independent matches must be in an array.");
+            }
+
+            return builder.build();
+        }
+    }
+
     /**
      * Decodes the JSON into a criterion object.
      *
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 e810274..6753fae 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
@@ -27,6 +27,7 @@
 import org.onlab.packet.TpPort;
 import org.onlab.packet.VlanId;
 import org.onlab.util.HexString;
+import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.net.flow.ExtensionTreatmentCodec;
 import org.onosproject.core.GroupId;
@@ -49,6 +50,13 @@
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
 import org.onosproject.net.flow.instructions.L4ModificationInstruction;
 import org.onosproject.net.meter.MeterId;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiTableAction;
 import org.slf4j.Logger;
 
 import java.util.Map;
@@ -267,6 +275,54 @@
                 + subType + " is not supported");
     }
 
+    /**
+     * Decodes a protocol-independent instruction.
+     *
+     * @return instruction object decoded from the JSON
+     * @throws IllegalArgumentException if the JSON is invalid
+     */
+    private Instruction decodePi() {
+        String subType = nullIsIllegal(json.get(InstructionCodec.SUBTYPE),
+                                       InstructionCodec.SUBTYPE + InstructionCodec.ERROR_MESSAGE).asText();
+
+        if (subType.equals(PiTableAction.Type.ACTION.name())) {
+            PiActionId piActionId = PiActionId.of(nullIsIllegal(
+                    json.get(InstructionCodec.PI_ACTION_ID),
+                    InstructionCodec.PI_ACTION_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asText());
+            JsonNode params = json.get(InstructionCodec.PI_ACTION_PARAMS);
+
+            PiAction.Builder builder = PiAction.builder();
+            PiActionParam piActionParam;
+            PiActionParamId piActionParamId;
+            if (params != null) {
+                for (Map.Entry<String, String> param : ((Map<String, String>)
+                        (context.mapper().convertValue(params, Map.class))).entrySet()) {
+                    piActionParamId = PiActionParamId.of(param.getKey());
+                    piActionParam = new PiActionParam(piActionParamId,
+                                                      ImmutableByteSequence.copyFrom(
+                                                              HexString.fromHexString(param.getValue(), null)));
+                    builder.withParameter(piActionParam);
+                }
+            }
+
+            return Instructions.piTableAction(builder.withId(piActionId).build());
+        } else if (subType.equals(PiTableAction.Type.ACTION_GROUP_ID.name())) {
+            PiActionGroupId piActionGroupId = PiActionGroupId.of(nullIsIllegal(
+                    json.get(InstructionCodec.PI_ACTION_GROUP_ID),
+                    InstructionCodec.PI_ACTION_GROUP_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+
+            return Instructions.piTableAction(piActionGroupId);
+        } else if (subType.equals(PiTableAction.Type.GROUP_MEMBER_ID.name())) {
+            PiActionGroupMemberId piActionGroupMemberId = PiActionGroupMemberId.of(nullIsIllegal(
+                    json.get(InstructionCodec.PI_ACTION_GROUP_MEMBER_ID),
+                    InstructionCodec.PI_ACTION_GROUP_MEMBER_ID + InstructionCodec.MISSING_MEMBER_MESSAGE).asInt());
+
+            return Instructions.piTableAction(piActionGroupMemberId);
+        }
+        throw new IllegalArgumentException("Protocol-independent Instruction subtype "
+                                                   + subType + " is not supported");
+    }
+
     private Instruction decodeStatTrigger() {
         String statTriggerFlag = nullIsIllegal(json.get(InstructionCodec.STAT_TRIGGER_FLAG),
                 InstructionCodec.STAT_TRIGGER_FLAG + InstructionCodec.ERROR_MESSAGE).asText();
@@ -450,8 +506,11 @@
             return decodeExtension();
         } else if (type.equals(Instruction.Type.STAT_TRIGGER.name())) {
             return decodeStatTrigger();
+        } else if (type.equals(Instruction.Type.PROTOCOL_INDEPENDENT.name())) {
+            return decodePi();
         }
+
         throw new IllegalArgumentException("Instruction type "
-                + type + " is not supported");
+                                                   + type + " is not supported");
     }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java b/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java
index 9d8ac65..efdc9ff 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/EncodeCriterionCodecHelper.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.codec.impl;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onlab.util.HexString;
 import org.onosproject.codec.CodecContext;
@@ -42,6 +43,7 @@
 import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
 import org.onosproject.net.flow.criteria.OduSignalIdCriterion;
 import org.onosproject.net.flow.criteria.OduSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
 import org.onosproject.net.flow.criteria.PortCriterion;
 import org.onosproject.net.flow.criteria.SctpPortCriterion;
 import org.onosproject.net.flow.criteria.TcpPortCriterion;
@@ -49,6 +51,13 @@
 import org.onosproject.net.flow.criteria.UdpPortCriterion;
 import org.onosproject.net.flow.criteria.VlanIdCriterion;
 import org.onosproject.net.flow.criteria.VlanPcpCriterion;
+import org.onosproject.net.pi.model.PiMatchType;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.runtime.PiValidFieldMatch;
 
 import java.util.EnumMap;
 
@@ -123,6 +132,7 @@
         formatMap.put(Criterion.Type.DUMMY, new FormatDummyType());
         formatMap.put(Criterion.Type.ODU_SIGID, new FormatOduSignalId());
         formatMap.put(Criterion.Type.ODU_SIGTYPE, new FormatOduSignalType());
+        formatMap.put(Criterion.Type.PROTOCOL_INDEPENDENT, new FormatProtocolIndependent());
         // Currently unimplemented
         formatMap.put(Criterion.Type.ARP_OP, new FormatUnknown());
         formatMap.put(Criterion.Type.ARP_SPA, new FormatUnknown());
@@ -145,7 +155,6 @@
         formatMap.put(Criterion.Type.UDP_DST_MASKED, new FormatUnknown());
         formatMap.put(Criterion.Type.SCTP_SRC_MASKED, new FormatUnknown());
         formatMap.put(Criterion.Type.SCTP_DST_MASKED, new FormatUnknown());
-        formatMap.put(Criterion.Type.PROTOCOL_INDEPENDENT, new FormatUnknown());
 
     }
 
@@ -479,6 +488,102 @@
         }
     }
 
+    private ObjectNode parsePiMatchExact(PiExactFieldMatch exactFieldMatch) {
+
+        ObjectNode matchExactNode = context.mapper().createObjectNode();
+        matchExactNode.put(CriterionCodec.PI_MATCH_FIELD_ID, exactFieldMatch.fieldId().id());
+        matchExactNode.put(CriterionCodec.PI_MATCH_TYPE, PiMatchType.EXACT.name().toLowerCase());
+        matchExactNode.put(CriterionCodec.PI_MATCH_VALUE,
+                           HexString.toHexString(exactFieldMatch.value().asArray(),
+                                                 null));
+        return matchExactNode;
+    }
+
+    private ObjectNode parsePiMatchLpm(PiLpmFieldMatch lpmFieldMatch) {
+
+        ObjectNode matchLpmNode = context.mapper().createObjectNode();
+        matchLpmNode.put(CriterionCodec.PI_MATCH_FIELD_ID, lpmFieldMatch.fieldId().id());
+        matchLpmNode.put(CriterionCodec.PI_MATCH_TYPE, PiMatchType.LPM.name().toLowerCase());
+        matchLpmNode.put(CriterionCodec.PI_MATCH_VALUE,
+                         HexString.toHexString(lpmFieldMatch.value().asArray(),
+                                               null));
+        matchLpmNode.put(CriterionCodec.PI_MATCH_PREFIX, lpmFieldMatch.prefixLength());
+
+        return matchLpmNode;
+    }
+
+    private ObjectNode parsePiMatchTernary(PiTernaryFieldMatch ternaryFieldMatch) {
+
+        ObjectNode matchTernaryNode = context.mapper().createObjectNode();
+        matchTernaryNode.put(CriterionCodec.PI_MATCH_FIELD_ID, ternaryFieldMatch.fieldId().id());
+        matchTernaryNode.put(CriterionCodec.PI_MATCH_TYPE, PiMatchType.TERNARY.name().toLowerCase());
+        matchTernaryNode.put(CriterionCodec.PI_MATCH_VALUE,
+                             HexString.toHexString(ternaryFieldMatch.value().asArray(),
+                                                   null));
+        matchTernaryNode.put(CriterionCodec.PI_MATCH_MASK,
+                             HexString.toHexString(ternaryFieldMatch.mask().asArray(),
+                                                   null));
+
+        return matchTernaryNode;
+    }
+
+    private ObjectNode parsePiMatchRange(PiRangeFieldMatch rangeFieldMatch) {
+
+        ObjectNode matchRangeNode = context.mapper().createObjectNode();
+        matchRangeNode.put(CriterionCodec.PI_MATCH_FIELD_ID, rangeFieldMatch.fieldId().id());
+        matchRangeNode.put(CriterionCodec.PI_MATCH_TYPE, PiMatchType.RANGE.name().toLowerCase());
+        matchRangeNode.put(CriterionCodec.PI_MATCH_HIGH_VALUE,
+                           HexString.toHexString(rangeFieldMatch.highValue().asArray(),
+                                                 null));
+        matchRangeNode.put(CriterionCodec.PI_MATCH_LOW_VALUE,
+                           HexString.toHexString(rangeFieldMatch.lowValue().asArray(),
+                                                 null));
+
+        return matchRangeNode;
+    }
+
+    private ObjectNode parsePiMatchValid(PiValidFieldMatch validFieldMatch) {
+
+        ObjectNode matchValidNode = context.mapper().createObjectNode();
+        matchValidNode.put(CriterionCodec.PI_MATCH_FIELD_ID, validFieldMatch.fieldId().id());
+        matchValidNode.put(CriterionCodec.PI_MATCH_TYPE, PiMatchType.VALID.name().toLowerCase());
+        matchValidNode.put(CriterionCodec.PI_MATCH_VALUE, validFieldMatch.isValid());
+
+        return matchValidNode;
+    }
+
+    private class FormatProtocolIndependent implements CriterionTypeFormatter {
+        @Override
+        public ObjectNode encodeCriterion(ObjectNode root, Criterion criterion) {
+            final PiCriterion piCriterion = (PiCriterion) criterion;
+            ArrayNode matchNodes = context.mapper().createArrayNode();
+            for (PiFieldMatch fieldMatch : piCriterion.fieldMatches()) {
+                switch (fieldMatch.type()) {
+                    case EXACT:
+                        matchNodes.add(parsePiMatchExact((PiExactFieldMatch) fieldMatch));
+                        break;
+                    case LPM:
+                        matchNodes.add(parsePiMatchLpm((PiLpmFieldMatch) fieldMatch));
+                        break;
+                    case TERNARY:
+                        matchNodes.add(parsePiMatchTernary((PiTernaryFieldMatch) fieldMatch));
+                        break;
+                    case RANGE:
+
+                        matchNodes.add(parsePiMatchRange((PiRangeFieldMatch) fieldMatch));
+                        break;
+                    case VALID:
+
+                        matchNodes.add(parsePiMatchValid((PiValidFieldMatch) fieldMatch));
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Type " + fieldMatch.type().name() + " is unsupported");
+                }
+            }
+            return (ObjectNode) root.set(CriterionCodec.PI_MATCHES, matchNodes);
+        }
+    }
+
     private class FormatDummyType implements CriterionTypeFormatter {
 
         @Override
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 65a6665..7e2f2df 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
@@ -33,6 +33,11 @@
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
 import org.onosproject.net.flow.instructions.L4ModificationInstruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionParam;
 import org.slf4j.Logger;
 
 import static org.onlab.util.Tools.toHexWithPrefix;
@@ -240,6 +245,38 @@
         }
     }
 
+    /**
+     * Encode a protocol-independent instruction.
+     *
+     * @param result json node that the instruction attributes are added to
+     */
+    private void encodePi(ObjectNode result) {
+        PiInstruction piInstruction = (PiInstruction) instruction;
+        result.put(InstructionCodec.SUBTYPE, piInstruction.action().type().name());
+        switch (piInstruction.action().type()) {
+            case ACTION:
+                final PiAction piAction = (PiAction) piInstruction.action();
+                result.put(InstructionCodec.PI_ACTION_ID, piAction.id().id());
+                final ObjectNode jsonActionParams = context.mapper().createObjectNode();
+                for (PiActionParam actionParam : piAction.parameters()) {
+                    jsonActionParams.put(actionParam.id().id(),
+                                         HexString.toHexString(actionParam.value().asArray(), null));
+                }
+                result.set(InstructionCodec.PI_ACTION_PARAMS, jsonActionParams);
+                break;
+            case ACTION_GROUP_ID:
+                final PiActionGroupId piActionGroupId = (PiActionGroupId) piInstruction.action();
+                result.put(InstructionCodec.PI_ACTION_GROUP_ID, piActionGroupId.id());
+                break;
+            case GROUP_MEMBER_ID:
+                final PiActionGroupMemberId piActionGroupMemberId = (PiActionGroupMemberId) piInstruction.action();
+                result.put(InstructionCodec.PI_ACTION_GROUP_MEMBER_ID, piActionGroupMemberId.id());
+                break;
+            default:
+                throw new IllegalArgumentException("Cannot convert protocol-independent subtype of" +
+                                                           piInstruction.action().type().name());
+        }
+    }
 
     /**
      * Encodes a extension instruction.
@@ -336,6 +373,10 @@
                 encodeL4(result);
                 break;
 
+            case PROTOCOL_INDEPENDENT:
+                encodePi(result);
+                break;
+
             case EXTENSION:
                 encodeExtension(result);
                 break;
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java
index 4e4fe2b..f0a53e1 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/FlowEntryCodec.java
@@ -43,7 +43,7 @@
 
         final ObjectNode result = context.mapper().createObjectNode()
                 .put("id", Long.toString(flowEntry.id().value()))
-                .put("tableId", flowEntry.tableId())
+                .put("tableId", flowEntry.table().toString())
                 .put("appId", strAppId)
                 .put("groupId", flowEntry.groupId().id())
                 .put("priority", flowEntry.priority())
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 6e36ec0..75192a0 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
@@ -65,6 +65,11 @@
     static final String STAT_PACKET_COUNT = "packetCount";
     static final String STAT_DURATION = "duration";
 
+    static final String PI_ACTION_ID = "actionId";
+    static final String PI_ACTION_GROUP_ID = "groupId";
+    static final String PI_ACTION_GROUP_MEMBER_ID = "memberId";
+    static final String PI_ACTION_PARAMS = "actionParams";
+
     static final String MISSING_MEMBER_MESSAGE =
             " member is required in Instruction";
     static final String ERROR_MESSAGE =
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
index d61f6c1..c748ab6 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/CriterionCodecTest.java
@@ -15,8 +15,14 @@
  */
 package org.onosproject.codec.impl;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
 import java.util.EnumMap;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.packet.Ip6Address;
@@ -38,10 +44,20 @@
 import org.onosproject.net.flow.criteria.Criterion;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.runtime.PiValidFieldMatch;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.TestUtils.getField;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onosproject.codec.impl.CriterionJsonMatcher.matchesCriterion;
 
 /**
@@ -460,4 +476,102 @@
         assertThat(result, matchesCriterion(criterion));
     }
 
+    /**
+     * Tests protocol-independent type criterion encoding.
+     */
+    @Test
+    public void matchPiTypeEncodingTest() {
+
+        PiMatchFieldId ethMatchFieldId = PiMatchFieldId.of("ethernet_t.etherType");
+        byte[] matchExactBytes1 = {0x08, 0x00};
+        Criterion exactBytesCriterion = PiCriterion.builder().matchExact(ethMatchFieldId, matchExactBytes1).build();
+        ObjectNode exactResult = criterionCodec.encode(exactBytesCriterion, context);
+        assertThat(exactResult, matchesCriterion(exactBytesCriterion));
+
+        PiMatchFieldId ipv4MatchFieldId = PiMatchFieldId.of("ipv4_t.dstAddr");
+        int mask = 0x00ffffff;
+        byte[] matchLpmBytes1 = {0x0a, 0x01, 0x01, 0x01};
+        Criterion lpmBytesCriterion = PiCriterion.builder().matchLpm(ipv4MatchFieldId, matchLpmBytes1, mask).build();
+        ObjectNode lpmResult = criterionCodec.encode(lpmBytesCriterion, context);
+        assertThat(lpmResult, matchesCriterion(lpmBytesCriterion));
+
+        byte[] matchTernaryBytes1 = {0x0a, 0x01, 0x01, 0x01};
+        byte[] matchTernaryMaskBytes = {0x7f, 0x7f, 0x7f, 0x00};
+        Criterion ternaryBytesCriterion = PiCriterion.builder().matchTernary(ipv4MatchFieldId, matchTernaryBytes1,
+                                                                    matchTernaryMaskBytes).build();
+        ObjectNode ternaryResult = criterionCodec.encode(ternaryBytesCriterion, context);
+        assertThat(ternaryResult, matchesCriterion(ternaryBytesCriterion));
+
+        byte[] matchRangeBytes1 = {0x10};
+        byte[] matchRangeHighBytes = {0x30};
+        Criterion rangeBytesCriterion = PiCriterion.builder()
+                .matchRange(ipv4MatchFieldId, matchRangeBytes1, matchRangeHighBytes).build();
+        ObjectNode rangeResult = criterionCodec.encode(rangeBytesCriterion, context);
+        assertThat(rangeResult, matchesCriterion(rangeBytesCriterion));
+
+        Criterion validCriterion = PiCriterion.builder().matchValid(ipv4MatchFieldId, false).build();
+        ObjectNode validResult = criterionCodec.encode(validCriterion, context);
+        assertThat(validResult, matchesCriterion(validCriterion));
+    }
+
+    /**
+     * Tests protocol-independent type criterion decoding.
+     */
+    @Test
+    public void matchPiTypeDecodingTest() throws IOException {
+        Criterion criterion = getCriterion("PiCriterion.json");
+        Assert.assertThat(criterion.type(), is(Criterion.Type.PROTOCOL_INDEPENDENT));
+        Collection<PiFieldMatch> piFieldMatches = ((PiCriterion) criterion).fieldMatches();
+        for (PiFieldMatch piFieldMatch : piFieldMatches) {
+            switch (piFieldMatch.type()) {
+                case EXACT:
+                    Assert.assertThat(piFieldMatch.fieldId().id(), is("ingress_port"));
+                    Assert.assertThat(((PiExactFieldMatch) piFieldMatch).value(), is(
+                            copyFrom((byte) 0x10)));
+                    break;
+                case LPM:
+                    Assert.assertThat(piFieldMatch.fieldId().id(), is("src_addr"));
+                    Assert.assertThat(((PiLpmFieldMatch) piFieldMatch).value(),
+                                      is(copyFrom(0xa010101)));
+                    Assert.assertThat(((PiLpmFieldMatch) piFieldMatch).prefixLength(), is(24));
+                    break;
+                case TERNARY:
+                    Assert.assertThat(piFieldMatch.fieldId().id(), is("dst_addr"));
+                    Assert.assertThat(((PiTernaryFieldMatch) piFieldMatch).value(),
+                                      is(copyFrom(0xa010101)));
+                    Assert.assertThat(((PiTernaryFieldMatch) piFieldMatch).mask(),
+                                      is(copyFrom(0xfffffff)));
+                    break;
+                case RANGE:
+                    Assert.assertThat(piFieldMatch.fieldId().id(), is("egress_port"));
+                    Assert.assertThat(((PiRangeFieldMatch) piFieldMatch).highValue(),
+                                      is(copyFrom((byte) 0x20)));
+                    Assert.assertThat(((PiRangeFieldMatch) piFieldMatch).lowValue(),
+                                      is(copyFrom((byte) 0x10)));
+                    break;
+                case VALID:
+                    Assert.assertThat(piFieldMatch.fieldId().id(), is("ethernet_t.etherType"));
+                    Assert.assertThat(((PiValidFieldMatch) piFieldMatch).isValid(), is(true));
+                    break;
+                default:
+                    Assert.assertTrue(false);
+            }
+        }
+    }
+
+    /**
+     * Reads in a criterion from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded criterion
+     * @throws IOException if processing the resource fails
+     */
+    private Criterion getCriterion(String resourceName) throws IOException {
+        InputStream jsonStream = CriterionCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        MatcherAssert.assertThat(json, notNullValue());
+        Criterion criterion = criterionCodec.decode((ObjectNode) json, context);
+        Assert.assertThat(criterion, notNullValue());
+        return criterion;
+    }
 }
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
index 5e0b938..0b38729 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/CriterionJsonMatcher.java
@@ -15,6 +15,7 @@
  */
 package org.onosproject.codec.impl;
 
+import java.util.Collection;
 import java.util.Objects;
 
 import org.hamcrest.Description;
@@ -43,6 +44,7 @@
 import org.onosproject.net.flow.criteria.OchSignalTypeCriterion;
 import org.onosproject.net.flow.criteria.OduSignalIdCriterion;
 import org.onosproject.net.flow.criteria.OduSignalTypeCriterion;
+import org.onosproject.net.flow.criteria.PiCriterion;
 import org.onosproject.net.flow.criteria.PortCriterion;
 import org.onosproject.net.flow.criteria.SctpPortCriterion;
 import org.onosproject.net.flow.criteria.TcpPortCriterion;
@@ -52,6 +54,14 @@
 
 import com.fasterxml.jackson.databind.JsonNode;
 import com.google.common.base.Joiner;
+import org.onosproject.net.pi.runtime.PiExactFieldMatch;
+import org.onosproject.net.pi.runtime.PiFieldMatch;
+import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
+import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
+import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
+import org.onosproject.net.pi.runtime.PiValidFieldMatch;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
 
 /**
  * Hamcrest matcher for criterion objects.
@@ -537,6 +547,101 @@
         return true;
     }
 
+    /**
+     * Matches a protocol-independent Type criterion object.
+     *
+     * @param criterion criterion to match
+     * @return true if the JSON matches the criterion, false otherwise.
+     */
+    private boolean matchCriterion(PiCriterion criterion) {
+        Collection<PiFieldMatch> piFieldMatches = criterion.fieldMatches();
+        JsonNode jsonMathes = jsonCriterion.get("matches");
+        if (!jsonMathes.isArray()) {
+            return false;
+        }
+        for (JsonNode matchNode : jsonMathes) {
+            for (PiFieldMatch fieldMatch : piFieldMatches) {
+
+                if (!Objects.equals(matchNode.get("field").textValue(), fieldMatch.fieldId().id())) {
+                    description.appendText("match field was " + fieldMatch.fieldId().id());
+                    return false;
+                }
+
+                if (!Objects.equals(matchNode.get("match").textValue(),
+                                    fieldMatch.type().name().toLowerCase())) {
+                    description.appendText("match type was " + fieldMatch.type().name().toLowerCase());
+                    return false;
+                }
+
+                switch (fieldMatch.type()) {
+                    case EXACT:
+                        if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("value")
+                                                                             .textValue(), null)),
+                                                     ((PiExactFieldMatch) fieldMatch).value())) {
+                            description.appendText("match value was " + ((PiExactFieldMatch) fieldMatch).value());
+                            return false;
+                        }
+                        break;
+                    case LPM:
+                        if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("value")
+                                                                             .textValue(), null)),
+                                            ((PiLpmFieldMatch) fieldMatch).value())) {
+                            description.appendText("match value was " + ((PiLpmFieldMatch) fieldMatch).value());
+                            return false;
+                        }
+                        if (!Objects.equals(matchNode.get("prefixLength").intValue(),
+                                            ((PiLpmFieldMatch) fieldMatch).prefixLength())) {
+                            description.appendText("match prefix was " +
+                                                           ((PiLpmFieldMatch) fieldMatch).prefixLength());
+                            return false;
+                        }
+                        break;
+                    case TERNARY:
+                        if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("value")
+                                                                             .textValue(), null)),
+                                            ((PiTernaryFieldMatch) fieldMatch).value())) {
+                            description.appendText("match value was " + ((PiTernaryFieldMatch) fieldMatch).value());
+                            return false;
+                        }
+                        if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("mask")
+                                                                             .textValue(), null)),
+                                            ((PiTernaryFieldMatch) fieldMatch).mask())) {
+                            description.appendText("match mask was " + ((PiTernaryFieldMatch) fieldMatch).mask());
+                            return false;
+                        }
+                        break;
+                    case RANGE:
+                        if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("highValue")
+                                                                             .textValue(), null)),
+                                            ((PiRangeFieldMatch) fieldMatch).highValue())) {
+                            description.appendText("match high value was " +
+                                                           ((PiRangeFieldMatch) fieldMatch).highValue());
+                            return false;
+                        }
+                        if (!Objects.equals(copyFrom(HexString.fromHexString(matchNode.get("lowValue")
+                                                                             .textValue(), null)),
+                                            ((PiRangeFieldMatch) fieldMatch).lowValue())) {
+                            description.appendText("match low value was " +
+                                                           ((PiRangeFieldMatch) fieldMatch).lowValue());
+                            return false;
+                        }
+                        break;
+                    case VALID:
+                        if (!Objects.equals(matchNode.get("value").asBoolean(),
+                                            ((PiValidFieldMatch) fieldMatch).isValid())) {
+                            description.appendText("match value was " + ((PiValidFieldMatch) fieldMatch).isValid());
+                            return false;
+                        }
+                        break;
+                    default:
+                        description.appendText("match type was " + fieldMatch.type().name().toLowerCase());
+                        return false;
+                }
+            }
+        }
+
+        return true;
+    }
 
     @Override
     public boolean matchesSafely(JsonNode jsonCriterion,
@@ -641,6 +746,8 @@
 
             case ODU_SIGTYPE:
                 return matchCriterion((OduSignalTypeCriterion) criterion);
+            case PROTOCOL_INDEPENDENT:
+                return matchCriterion((PiCriterion) criterion);
 
             default:
                 // Don't know how to format this type
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java b/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
index 307c524..832a420 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/InstructionCodecTest.java
@@ -15,7 +15,10 @@
  */
 package org.onosproject.codec.impl;
 
+import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.MatcherAssert;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.packet.Ip4Address;
@@ -23,6 +26,7 @@
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.MplsLabel;
 import org.onlab.packet.VlanId;
+import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.net.ChannelSpacing;
@@ -36,9 +40,23 @@
 import org.onosproject.net.flow.instructions.L1ModificationInstruction;
 import org.onosproject.net.flow.instructions.L2ModificationInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiTableAction;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onosproject.codec.impl.InstructionJsonMatcher.matchesInstruction;
 
 /**
@@ -232,4 +250,77 @@
         assertThat(instructionJson, matchesInstruction(instruction));
     }
 
+    /**
+     * Tests the encoding of protocol-independent instructions.
+     */
+    @Test
+    public void piInstructionEncodingTest() {
+        PiActionId actionId = PiActionId.of("set_egress_port");
+        PiActionParamId actionParamId = PiActionParamId.of("port");
+        PiActionParam actionParam = new PiActionParam(actionParamId, ImmutableByteSequence.copyFrom(10));
+        PiTableAction action = PiAction.builder().withId(actionId).withParameter(actionParam).build();
+        final PiInstruction actionInstruction = Instructions.piTableAction(action);
+        final ObjectNode actionInstructionJson =
+                instructionCodec.encode(actionInstruction, context);
+        assertThat(actionInstructionJson, matchesInstruction(actionInstruction));
+
+        PiTableAction actionGroupId = PiActionGroupId.of(10);
+        final PiInstruction actionGroupIdInstruction = Instructions.piTableAction(actionGroupId);
+        final ObjectNode actionGroupIdInstructionJson =
+                instructionCodec.encode(actionGroupIdInstruction, context);
+        assertThat(actionGroupIdInstructionJson, matchesInstruction(actionGroupIdInstruction));
+
+        PiTableAction actionGroupMemberId = PiActionGroupMemberId.of(10);
+        final PiInstruction actionGroupMemberIdInstruction = Instructions.piTableAction(actionGroupMemberId);
+        final ObjectNode actionGroupMemberIdInstructionJson =
+                instructionCodec.encode(actionGroupMemberIdInstruction, context);
+        assertThat(actionGroupMemberIdInstructionJson, matchesInstruction(actionGroupMemberIdInstruction));
+    }
+
+    /**
+     * Tests the decoding of protocol-independent instructions.
+     */
+    @Test
+    public void piInstructionDecodingTest() throws IOException {
+
+        Instruction actionInstruction = getInstruction("PiActionInstruction.json");
+        Assert.assertThat(actionInstruction.type(), is(Instruction.Type.PROTOCOL_INDEPENDENT));
+        PiTableAction action = ((PiInstruction) actionInstruction).action();
+        Assert.assertThat(action.type(), is(PiTableAction.Type.ACTION));
+        Assert.assertThat(((PiAction) action).id().id(), is("set_egress_port"));
+        Assert.assertThat(((PiAction) action).parameters().size(), is(1));
+        Collection<PiActionParam> actionParams = ((PiAction) action).parameters();
+        PiActionParam actionParam = actionParams.iterator().next();
+        Assert.assertThat(actionParam.id().id(), is("port"));
+        Assert.assertThat(actionParam.value(), is(copyFrom((byte) 0x1)));
+
+        Instruction actionGroupIdInstruction = getInstruction("PiActionGroupIdInstruction.json");
+        Assert.assertThat(actionInstruction.type(), is(Instruction.Type.PROTOCOL_INDEPENDENT));
+        PiTableAction actionGroupId = ((PiInstruction) actionGroupIdInstruction).action();
+        Assert.assertThat(actionGroupId.type(), is(PiTableAction.Type.ACTION_GROUP_ID));
+        Assert.assertThat(((PiActionGroupId) actionGroupId).id(), is(100));
+
+        Instruction actionMemberIdInstruction = getInstruction("PiActionMemberIdInstruction.json");
+        Assert.assertThat(actionInstruction.type(), is(Instruction.Type.PROTOCOL_INDEPENDENT));
+        PiTableAction actionMemberId = ((PiInstruction) actionMemberIdInstruction).action();
+        Assert.assertThat(actionMemberId.type(), is(PiTableAction.Type.GROUP_MEMBER_ID));
+        Assert.assertThat(((PiActionGroupMemberId) actionMemberId).id(), is(100));
+    }
+
+    /**
+     * Reads in an instruction from the given resource and decodes it.
+     *
+     * @param resourceName resource to use to read the JSON for the rule
+     * @return decoded instruction
+     * @throws IOException if processing the resource fails
+     */
+    private Instruction getInstruction(String resourceName) throws IOException {
+        InputStream jsonStream = InstructionCodecTest.class.getResourceAsStream(resourceName);
+        JsonNode json = context.mapper().readTree(jsonStream);
+        MatcherAssert.assertThat(json, notNullValue());
+        Instruction instruction = instructionCodec.decode((ObjectNode) json, context);
+        Assert.assertThat(instruction, notNullValue());
+        return instruction;
+    }
+
 }
diff --git a/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java b/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
index 94278ff..9911a4f 100644
--- a/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
+++ b/core/common/src/test/java/org/onosproject/codec/impl/InstructionJsonMatcher.java
@@ -35,6 +35,16 @@
 import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPInstruction;
 import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPv6FlowLabelInstruction;
+import org.onosproject.net.flow.instructions.PiInstruction;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionGroupId;
+import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+
+import java.util.Collection;
+import java.util.Objects;
+
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
 
 /**
  * Hamcrest matcher for instructions.
@@ -503,6 +513,71 @@
         return true;
     }
 
+    /**
+     * Matches the contents of a protocol-independent instruction.
+     *
+     * @param instructionJson JSON instruction to match
+     * @param description Description object used for recording errors
+     * @return true if contents match, false otherwise
+     */
+    private boolean matchPiInstruction(JsonNode instructionJson,
+                                                 Description description) {
+        PiInstruction instructionToMatch = (PiInstruction) instruction;
+
+        final String jsonSubtype = instructionJson.get("subtype").textValue();
+        if (!instructionToMatch.action().type().name().equals(jsonSubtype)) {
+            description.appendText("subtype was " + jsonSubtype);
+            return false;
+        }
+
+        final String jsonType = instructionJson.get("type").textValue();
+        if (!instructionToMatch.type().name().equals(jsonType)) {
+            description.appendText("type was " + jsonType);
+            return false;
+        }
+
+        switch (instructionToMatch.action().type()) {
+            case ACTION:
+                if (!Objects.equals(instructionJson.get("actionId").textValue(),
+                                    ((PiAction) instructionToMatch.action()).id().id())) {
+                    description.appendText("action was " + ((PiAction) instructionToMatch.action()).id().id());
+                    return false;
+                }
+                Collection<PiActionParam> piActionParams = ((PiAction) instructionToMatch.action()).parameters();
+                JsonNode jsonParams = instructionJson.get("actionParams");
+                for (PiActionParam actionParam : piActionParams) {
+                    if (!Objects.equals(copyFrom(HexString.fromHexString(jsonParams.get(actionParam.id().id())
+                                                                         .textValue(), null)),
+                                        actionParam.value())) {
+                        description.appendText("action param value was " + actionParam.value());
+                        return false;
+                    }
+                }
+                break;
+            case ACTION_GROUP_ID:
+                if (!Objects.equals(instructionJson.get("groupId").asInt(),
+                                    ((PiActionGroupId) instructionToMatch.action()).id())) {
+                    description.appendText("action group id was " +
+                                                   ((PiActionGroupId) instructionToMatch.action()).id());
+                    return false;
+                }
+                break;
+            case GROUP_MEMBER_ID:
+                if (!Objects.equals(instructionJson.get("memberId").asInt(),
+                                    ((PiActionGroupMemberId) instructionToMatch.action()).id())) {
+                    description.appendText("action member id was " +
+                                                   ((PiActionGroupMemberId) instructionToMatch.action()).id());
+                    return false;
+                }
+                break;
+            default:
+                description.appendText("type was " + jsonType);
+                return false;
+        }
+
+        return true;
+    }
+
     @Override
     public boolean matchesSafely(JsonNode jsonInstruction, Description description) {
 
@@ -541,6 +616,8 @@
             return matchModMplsLabelInstruction(jsonInstruction, description);
         } else if (instruction instanceof ModOduSignalIdInstruction) {
             return matchModOduSingalIdInstruction(jsonInstruction, description);
+        } else if (instruction instanceof PiInstruction) {
+            return matchPiInstruction(jsonInstruction, description);
         } else if (instruction instanceof NoActionInstruction) {
             return true;
         }
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/PiActionGroupIdInstruction.json b/core/common/src/test/resources/org/onosproject/codec/impl/PiActionGroupIdInstruction.json
new file mode 100644
index 0000000..50a7d30
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/PiActionGroupIdInstruction.json
@@ -0,0 +1,5 @@
+{
+  "type":"PROTOCOL_INDEPENDENT",
+  "subtype":"ACTION_GROUP_ID",
+  "groupId": 100
+}
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/PiActionInstruction.json b/core/common/src/test/resources/org/onosproject/codec/impl/PiActionInstruction.json
new file mode 100644
index 0000000..b35bda8
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/PiActionInstruction.json
@@ -0,0 +1,8 @@
+{
+    "type":"PROTOCOL_INDEPENDENT",
+    "subtype":"ACTION",
+    "actionId": "set_egress_port",
+    "actionParams":{
+      "port": "1"
+    }
+}
\ No newline at end of file
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/PiActionMemberIdInstruction.json b/core/common/src/test/resources/org/onosproject/codec/impl/PiActionMemberIdInstruction.json
new file mode 100644
index 0000000..c3fce67
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/PiActionMemberIdInstruction.json
@@ -0,0 +1,5 @@
+{
+  "type":"PROTOCOL_INDEPENDENT",
+  "subtype":"GROUP_MEMBER_ID",
+  "memberId": 100
+}
\ No newline at end of file
diff --git a/core/common/src/test/resources/org/onosproject/codec/impl/PiCriterion.json b/core/common/src/test/resources/org/onosproject/codec/impl/PiCriterion.json
new file mode 100644
index 0000000..01d9226
--- /dev/null
+++ b/core/common/src/test/resources/org/onosproject/codec/impl/PiCriterion.json
@@ -0,0 +1,33 @@
+{
+    "type": "PROTOCOL_INDEPENDENT",
+    "matches": [
+        {
+            "field": "ingress_port",
+            "match": "exact",
+            "value": "10"
+        },
+        {
+            "field": "src_addr",
+            "match": "lpm",
+            "value": "0a010101",
+            "prefixLength": 24
+        },
+        {
+            "field": "dst_addr",
+            "match": "ternary",
+            "value": "0a010101",
+            "mask": "0fffffff"
+        },
+        {
+            "field": "egress_port",
+            "match": "range",
+            "highValue": "20",
+            "lowValue": "10"
+        },
+        {
+            "field": "ethernet_t.etherType",
+            "match": "valid",
+            "value": true
+        }
+    ]
+}
\ No newline at end of file