ONOS-7066 ONOS-7067 PI abstractions refactoring and P4Info model parser

Includes changes previously reviewed in #15607, #15877, and #15955.

Change-Id: Ie2ff62e415f2099832ebfe05961a879b7b188fc3
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
index 8914ee7..cddd23b 100644
--- a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
@@ -35,12 +35,12 @@
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleService;
 import org.onosproject.net.flow.criteria.PiCriterion;
-import org.onosproject.net.pi.model.PiPipelineProgrammable;
+import org.onosproject.net.behaviour.PiPipelineProgrammable;
 import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionId;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.p4tutorial.pipeconf.PipeconfFactory;
 import org.slf4j.Logger;
 
@@ -116,7 +116,7 @@
     }
 
     private void installDropRule(DeviceId deviceId) {
-        PiHeaderFieldId ipv4ProtoFieldId = PiHeaderFieldId.of("ipv4", "protocol");
+        PiMatchFieldId ipv4ProtoFieldId = PiMatchFieldId.of("ipv4.protocol");
         PiActionId dropActionId = PiActionId.of("_drop");
 
         PiCriterion piCriterion = PiCriterion.builder()
diff --git a/apps/p4-tutorial/pipeconf/BUCK b/apps/p4-tutorial/pipeconf/BUCK
index 038736f..6bdb389 100644
--- a/apps/p4-tutorial/pipeconf/BUCK
+++ b/apps/p4-tutorial/pipeconf/BUCK
@@ -1,7 +1,7 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
     '//lib:minimal-json',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
+    '//protocols/p4runtime/model:onos-protocols-p4runtime-model',
     '//drivers/default:onos-drivers-default',
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
 ]
@@ -12,8 +12,6 @@
 
 BUNDLES = [
     '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
-    '//drivers/default:onos-drivers-default',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
 ]
 
 onos_app (
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
index b284eaa..79aa274 100644
--- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
@@ -21,7 +21,6 @@
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
 import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
@@ -31,6 +30,8 @@
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.p4runtime.model.P4InfoParser;
+import org.onosproject.p4runtime.model.P4InfoParserException;
 
 import java.net.URL;
 
@@ -46,18 +47,6 @@
     public static final PiPipeconfId PIPECONF_ID = new PiPipeconfId("p4-tutorial-pipeconf");
     private static final URL P4INFO_URL = PipeconfFactory.class.getResource("/main.p4info");
     private static final URL BMV2_JSON_URL = PipeconfFactory.class.getResource("/main.json");
-    private static final PiPipelineModel PIPELINE_MODEL = Bmv2PipelineModelParser.parse(BMV2_JSON_URL);
-
-    private static final PiPipeconf PIPECONF = DefaultPiPipeconf.builder()
-            .withId(PIPECONF_ID)
-            .withPipelineModel(PIPELINE_MODEL)
-            .addBehaviour(PiPipelineInterpreter.class, PipelineInterpreterImpl.class)
-            .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
-            // Since main.p4 defines only 1 table, we re-use the existing single-table pipeliner.
-            .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
-            .addExtension(P4_INFO_TEXT, P4INFO_URL)
-            .addExtension(BMV2_JSON, BMV2_JSON_URL)
-            .build();
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     private PiPipeconfService piPipeconfService;
@@ -65,11 +54,31 @@
     @Activate
     public void activate() {
         // Registers the pipeconf at component activation.
-        piPipeconfService.register(PIPECONF);
+        piPipeconfService.register(buildPipeconf());
     }
 
     @Deactivate
     public void deactivate() {
-        piPipeconfService.remove(PIPECONF.id());
+        piPipeconfService.remove(PIPECONF_ID);
+    }
+
+    private PiPipeconf buildPipeconf() {
+        final PiPipelineModel pipelineModel;
+        try {
+            pipelineModel = P4InfoParser.parse(P4INFO_URL);
+        } catch (P4InfoParserException e) {
+            throw new RuntimeException(e);
+        }
+
+        return DefaultPiPipeconf.builder()
+                .withId(PIPECONF_ID)
+                .withPipelineModel(pipelineModel)
+                .addBehaviour(PiPipelineInterpreter.class, PipelineInterpreterImpl.class)
+                .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
+                // Since main.p4 defines only 1 table, we re-use the existing single-table pipeliner.
+                .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+                .addExtension(P4_INFO_TEXT, P4INFO_URL)
+                .addExtension(BMV2_JSON, BMV2_JSON_URL)
+                .build();
     }
 }
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
index 4c9e5f3..9147de3 100644
--- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
@@ -23,7 +23,6 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
@@ -35,17 +34,17 @@
 import org.onosproject.net.packet.DefaultInboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
-import org.onosproject.net.pi.runtime.PiPacketMetadata;
-import org.onosproject.net.pi.runtime.PiPacketMetadataId;
+import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
-import org.onosproject.net.pi.runtime.PiTableId;
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
@@ -59,7 +58,7 @@
 import static org.onosproject.net.PortNumber.CONTROLLER;
 import static org.onosproject.net.PortNumber.FLOOD;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
-import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
 
 /**
  * Implementation of a PI interpreter for the main.p4 program.
@@ -78,10 +77,10 @@
     private static final String STANDARD_METADATA = "standard_metadata";
     private static final int PORT_FIELD_BITWIDTH = 9;
 
-    private static final PiHeaderFieldId INGRESS_PORT_ID = PiHeaderFieldId.of(STANDARD_METADATA, "ingress_port");
-    private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of(ETHERNET, "dst_addr");
-    private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of(ETHERNET, "src_addr");
-    private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of(ETHERNET, "ether_type");
+    private static final PiMatchFieldId INGRESS_PORT_ID = PiMatchFieldId.of(STANDARD_METADATA + ".ingress_port");
+    private static final PiMatchFieldId ETH_DST_ID = PiMatchFieldId.of(ETHERNET + ".dst_addr");
+    private static final PiMatchFieldId ETH_SRC_ID = PiMatchFieldId.of(ETHERNET + ".src_addr");
+    private static final PiMatchFieldId ETH_TYPE_ID = PiMatchFieldId.of(ETHERNET + ".ether_type");
     private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
     private static final PiTableId IP_PROTO_FILTER_TABLE_ID = PiTableId.of(IP_PROTO_FILTER_TABLE);
 
@@ -89,8 +88,8 @@
             0, TABLE0_ID,
             1, IP_PROTO_FILTER_TABLE_ID);
 
-    private static final BiMap<Criterion.Type, PiHeaderFieldId> CRITERION_MAP =
-            new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+    private static final BiMap<Criterion.Type, PiMatchFieldId> CRITERION_MAP =
+            new ImmutableBiMap.Builder<Criterion.Type, PiMatchFieldId>()
                     .put(Criterion.Type.IN_PORT, INGRESS_PORT_ID)
                     .put(Criterion.Type.ETH_DST, ETH_DST_ID)
                     .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
@@ -177,7 +176,7 @@
     }
 
     @Override
-    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
+    public InboundPacket mapInboundPacket(PiPacketOperation packetIn)
             throws PiInterpreterException {
         // We assume that the packet is ethernet, which is fine since default.p4 can deparse only ethernet packets.
         Ethernet ethPkt;
@@ -188,34 +187,36 @@
         }
 
         // Returns the ingress port packet metadata.
-        Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas().stream()
-                .filter(metadata -> metadata.id().name().equals(INGRESS_PORT))
+        Optional<PiControlMetadata> packetMetadata = packetIn.metadatas().stream()
+                .filter(metadata -> metadata.id().toString().equals(INGRESS_PORT))
                 .findFirst();
 
         if (packetMetadata.isPresent()) {
             ImmutableByteSequence portByteSequence = packetMetadata.get().value();
             short s = portByteSequence.asReadOnlyBuffer().getShort();
-            ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+            ConnectPoint receivedFrom = new ConnectPoint(packetIn.deviceId(), PortNumber.portNumber(s));
             return new DefaultInboundPacket(receivedFrom, ethPkt, packetIn.data().asReadOnlyBuffer());
         } else {
             throw new PiInterpreterException(format(
-                    "Missing metadata '%s' in packet-in received from '%s': %s", INGRESS_PORT, deviceId, packetIn));
+                    "Missing metadata '%s' in packet-in received from '%s': %s",
+                    INGRESS_PORT, packetIn.deviceId(), packetIn));
         }
     }
 
     private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
-        PiPacketMetadata metadata = createPacketMetadata(portNumber);
+        PiControlMetadata metadata = createControlMetadata(portNumber);
         return PiPacketOperation.builder()
+                .forDevice(this.data().deviceId())
                 .withType(PACKET_OUT)
                 .withData(copyFrom(data))
                 .withMetadatas(ImmutableList.of(metadata))
                 .build();
     }
 
-    private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+    private PiControlMetadata createControlMetadata(long portNumber) throws PiInterpreterException {
         try {
-            return PiPacketMetadata.builder()
-                    .withId(PiPacketMetadataId.of(EGRESS_PORT))
+            return PiControlMetadata.builder()
+                    .withId(PiControlMetadataId.of(EGRESS_PORT))
                     .withValue(fit(copyFrom(portNumber), PORT_FIELD_BITWIDTH))
                     .build();
         } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
@@ -224,12 +225,12 @@
     }
 
     @Override
-    public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
+    public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
         return Optional.ofNullable(CRITERION_MAP.get(type));
     }
 
     @Override
-    public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
+    public Optional<Criterion.Type> mapPiMatchFieldId(PiMatchFieldId headerFieldId) {
         return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
     }
 
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
index 7d715ee1..166958d 100644
--- a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
@@ -24,11 +24,10 @@
 import org.onosproject.net.device.PortStatistics;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.api.P4RuntimeController;
@@ -42,7 +41,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
-import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
+import static org.onosproject.net.pi.model.PiCounterType.INDIRECT;
 
 /**
  * Implementation of the PortStatisticsDiscovery behaviour for the main.p4 program. This behaviour works by using a
@@ -52,8 +51,8 @@
 
     private static final Logger log = LoggerFactory.getLogger(PortStatisticsDiscoveryImpl.class);
 
-    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("igr_port_counter", INDIRECT);
-    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egr_port_counter", INDIRECT);
+    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("igr_port_counter");
+    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egr_port_counter");
 
     @Override
     public Collection<PortStatistics> discoverPortStatistics() {
@@ -91,8 +90,8 @@
         Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
         portStatBuilders.keySet().forEach(p -> {
             // Counter cell/index = port number.
-            counterCellIds.add(PiIndirectCounterCellId.of(INGRESS_COUNTER_ID, p));
-            counterCellIds.add(PiIndirectCounterCellId.of(EGRESS_COUNTER_ID, p));
+            counterCellIds.add(PiCounterCellId.ofIndirect(INGRESS_COUNTER_ID, p));
+            counterCellIds.add(PiCounterCellId.ofIndirect(EGRESS_COUNTER_ID, p));
         });
 
         // Query the device.
@@ -107,16 +106,15 @@
 
         // Process response.
         counterEntryResponse.forEach(counterData -> {
-            if (counterData.cellId().type() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
+            if (counterData.cellId().counterType() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterData.cellId().counterType());
                 return;
             }
-            PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
-            if (!portStatBuilders.containsKey(indCellId.index())) {
+            if (!portStatBuilders.containsKey(counterData.cellId().index())) {
                 log.warn("Unrecognized counter index {}, skipping", counterData);
                 return;
             }
-            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
+            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterData.cellId().index());
             if (counterData.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
                 statsBuilder.setPacketsReceived(counterData.packets());
                 statsBuilder.setBytesReceived(counterData.bytes());
diff --git a/apps/p4runtime-test/BUCK b/apps/p4runtime-test/BUCK
index beb75b4..9ad0926 100644
--- a/apps/p4runtime-test/BUCK
+++ b/apps/p4runtime-test/BUCK
@@ -18,7 +18,7 @@
 TEST_DEPS = [
     '//lib:TEST',
     '//lib:slf4j-jdk14',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
+    '//protocols/p4runtime/model:onos-protocols-p4runtime-model',
     # gRPC dependencies
     '//lib:grpc-protobuf-' + GRPC_VER,
     '//lib:grpc-protobuf-lite-' + GRPC_VER,
diff --git a/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java b/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java
index 7b4c668..bfef373 100644
--- a/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java
+++ b/apps/p4runtime-test/src/test/java/org/onosproject/p4runtime/test/P4RuntimeTest.java
@@ -25,24 +25,25 @@
 import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.grpc.ctl.GrpcControllerImpl;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiActionGroupType;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiMatchKey;
-import org.onosproject.net.pi.runtime.PiPacketMetadata;
-import org.onosproject.net.pi.runtime.PiPacketMetadataId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.ctl.P4RuntimeClientImpl;
@@ -60,8 +61,8 @@
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onlab.util.ImmutableByteSequence.fit;
 import static org.onlab.util.ImmutableByteSequence.ofZeros;
+import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
-import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
 import static org.slf4j.LoggerFactory.getLogger;
 import static p4.P4RuntimeOuterClass.ActionProfileGroup.Type.SELECT;
 import static p4.P4RuntimeOuterClass.Update.Type.INSERT;
@@ -75,6 +76,7 @@
     private static final String GRPC_SERVER_ADDR = "192.168.56.102";
     private static final int GRPC_SERVER_PORT = 55044;
 
+    private static final String DOT = ".";
     private static final String TABLE_0 = "table0";
     private static final String SET_EGRESS_PORT = "set_egress_port";
     private static final String PORT = "port";
@@ -100,10 +102,10 @@
 
     private final ImmutableByteSequence ethAddr = fit(copyFrom(1), 48);
     private final ImmutableByteSequence portValue = copyFrom((short) 1);
-    private final PiHeaderFieldId ethDstAddrFieldId = PiHeaderFieldId.of(ETHERNET, DST_ADDR);
-    private final PiHeaderFieldId ethSrcAddrFieldId = PiHeaderFieldId.of(ETHERNET, SRC_ADDR);
-    private final PiHeaderFieldId inPortFieldId = PiHeaderFieldId.of(STANDARD_METADATA, INGRESS_PORT);
-    private final PiHeaderFieldId ethTypeFieldId = PiHeaderFieldId.of(ETHERNET, ETHER_TYPE);
+    private final PiMatchFieldId ethDstAddrFieldId = PiMatchFieldId.of(ETHERNET + DOT + DST_ADDR);
+    private final PiMatchFieldId ethSrcAddrFieldId = PiMatchFieldId.of(ETHERNET + DOT + SRC_ADDR);
+    private final PiMatchFieldId inPortFieldId = PiMatchFieldId.of(STANDARD_METADATA + DOT + INGRESS_PORT);
+    private final PiMatchFieldId ethTypeFieldId = PiMatchFieldId.of(ETHERNET + DOT + ETHER_TYPE);
     private final PiActionParamId portParamId = PiActionParamId.of(PORT);
     private final PiActionId outActionId = PiActionId.of(SET_EGRESS_PORT);
     private final PiTableId tableId = PiTableId.of(TABLE_0);
@@ -201,8 +203,8 @@
         PiPacketOperation packetOperation = PiPacketOperation.builder()
                 .withData(fit(copyFrom(1), 48 + 48 + 16))
                 .withType(PACKET_OUT)
-                .withMetadata(PiPacketMetadata.builder()
-                                      .withId(PiPacketMetadataId.of("egress_port"))
+                .withMetadata(PiControlMetadata.builder()
+                                      .withId(PiControlMetadataId.of("egress_port"))
                                       .withValue(fit(copyFrom(255), 9))
                                       .build())
                 .build();
@@ -267,7 +269,7 @@
             members.add(member);
         }
         PiActionGroup actionGroup = PiActionGroup.builder()
-                .withType(PiActionGroup.Type.SELECT)
+                .withType(PiActionGroupType.SELECT)
                 .withActionProfileId(actionProfileId)
                 .withId(groupId)
                 .addMembers(members)
diff --git a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
index d3c1a14..145cf50 100644
--- a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
+++ b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
@@ -47,8 +47,8 @@
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.net.topology.TopologyService;
@@ -528,4 +528,4 @@
             super(msg);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineProgrammable.java b/core/api/src/main/java/org/onosproject/net/behaviour/PiPipelineProgrammable.java
similarity index 85%
rename from core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineProgrammable.java
rename to core/api/src/main/java/org/onosproject/net/behaviour/PiPipelineProgrammable.java
index c222fe6..2373af4 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/PiPipelineProgrammable.java
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 
-package org.onosproject.net.pi.model;
+package org.onosproject.net.behaviour;
 
+import com.google.common.annotations.Beta;
 import org.onosproject.net.driver.HandlerBehaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
 
 import java.util.Optional;
 import java.util.concurrent.CompletableFuture;
 
 /**
- * Behavior to program the pipeline of a device.
+ * Behavior to program the pipeline of a device that supports protocol-independence.
  */
+@Beta
 public interface PiPipelineProgrammable extends HandlerBehaviour {
     /**
      * Deploys the given pipeconf to the device.
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
index d0a4732..2ba0bc8 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
@@ -19,9 +19,9 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
+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.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
@@ -40,14 +40,14 @@
 @Beta
 public final class PiCriterion implements Criterion {
 
-    private final ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatchMap;
+    private final ImmutableMap<PiMatchFieldId, PiFieldMatch> fieldMatchMap;
 
     /**
      * Creates a new protocol-independent criterion for the given match fields.
      *
      * @param fieldMatchMap field match map
      */
-    private PiCriterion(ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatchMap) {
+    private PiCriterion(ImmutableMap<PiMatchFieldId, PiFieldMatch> fieldMatchMap) {
         this.fieldMatchMap = fieldMatchMap;
     }
 
@@ -66,7 +66,7 @@
      * @param fieldId field identifier
      * @return optional field match
      */
-    public Optional<PiFieldMatch> fieldMatch(PiHeaderFieldId fieldId) {
+    public Optional<PiFieldMatch> fieldMatch(PiMatchFieldId fieldId) {
         return Optional.ofNullable(fieldMatchMap.get(fieldId));
     }
 
@@ -115,7 +115,7 @@
     public static final class Builder {
 
         // Use map to guarantee that there's only one field match per field id.
-        private final ImmutableMap.Builder<PiHeaderFieldId, PiFieldMatch> fieldMatchMapBuilder = ImmutableMap.builder();
+        private final ImmutableMap.Builder<PiMatchFieldId, PiFieldMatch> fieldMatchMapBuilder = ImmutableMap.builder();
 
         private Builder() {
             // ban constructor.
@@ -128,7 +128,7 @@
          * @param value   exact match value
          * @return this
          */
-        public Builder matchExact(PiHeaderFieldId fieldId, short value) {
+        public Builder matchExact(PiMatchFieldId fieldId, short value) {
             fieldMatchMapBuilder.put(fieldId, new PiExactFieldMatch(fieldId, copyFrom(value)));
             return this;
         }
@@ -140,7 +140,7 @@
          * @param value   exact match value
          * @return this
          */
-        public Builder matchExact(PiHeaderFieldId fieldId, int value) {
+        public Builder matchExact(PiMatchFieldId fieldId, int value) {
             fieldMatchMapBuilder.put(fieldId, new PiExactFieldMatch(fieldId, copyFrom(value)));
             return this;
         }
@@ -152,7 +152,7 @@
          * @param value   exact match value
          * @return this
          */
-        public Builder matchExact(PiHeaderFieldId fieldId, long value) {
+        public Builder matchExact(PiMatchFieldId fieldId, long value) {
             fieldMatchMapBuilder.put(fieldId, new PiExactFieldMatch(fieldId, copyFrom(value)));
             return this;
         }
@@ -164,7 +164,7 @@
          * @param value   exact match value
          * @return this
          */
-        public Builder matchExact(PiHeaderFieldId fieldId, byte[] value) {
+        public Builder matchExact(PiMatchFieldId fieldId, byte[] value) {
             fieldMatchMapBuilder.put(fieldId, new PiExactFieldMatch(fieldId, copyFrom(value)));
             return this;
         }
@@ -177,7 +177,7 @@
          * @param mask    ternary match mask
          * @return this
          */
-        public Builder matchTernary(PiHeaderFieldId fieldId, short value, short mask) {
+        public Builder matchTernary(PiMatchFieldId fieldId, short value, short mask) {
             fieldMatchMapBuilder.put(fieldId, new PiTernaryFieldMatch(fieldId, copyFrom(value), copyFrom(mask)));
             return this;
         }
@@ -190,7 +190,7 @@
          * @param mask    ternary match mask
          * @return this
          */
-        public Builder matchTernary(PiHeaderFieldId fieldId, int value, int mask) {
+        public Builder matchTernary(PiMatchFieldId fieldId, int value, int mask) {
             fieldMatchMapBuilder.put(fieldId, new PiTernaryFieldMatch(fieldId, copyFrom(value), copyFrom(mask)));
             return this;
         }
@@ -203,7 +203,7 @@
          * @param mask    ternary match mask
          * @return this
          */
-        public Builder matchTernary(PiHeaderFieldId fieldId, long value, long mask) {
+        public Builder matchTernary(PiMatchFieldId fieldId, long value, long mask) {
             fieldMatchMapBuilder.put(fieldId, new PiTernaryFieldMatch(fieldId, copyFrom(value), copyFrom(mask)));
             return this;
         }
@@ -216,7 +216,7 @@
          * @param mask    ternary match mask
          * @return this
          */
-        public Builder matchTernary(PiHeaderFieldId fieldId, byte[] value, byte[] mask) {
+        public Builder matchTernary(PiMatchFieldId fieldId, byte[] value, byte[] mask) {
             fieldMatchMapBuilder.put(fieldId, new PiTernaryFieldMatch(fieldId, copyFrom(value), copyFrom(mask)));
             return this;
         }
@@ -229,7 +229,7 @@
          * @param prefixLength lpm match prefix length
          * @return this
          */
-        public Builder matchLpm(PiHeaderFieldId fieldId, short value, int prefixLength) {
+        public Builder matchLpm(PiMatchFieldId fieldId, short value, int prefixLength) {
             fieldMatchMapBuilder.put(fieldId, new PiLpmFieldMatch(fieldId, copyFrom(value), prefixLength));
             return this;
         }
@@ -242,7 +242,7 @@
          * @param prefixLength lpm match prefix length
          * @return this
          */
-        public Builder matchLpm(PiHeaderFieldId fieldId, int value, int prefixLength) {
+        public Builder matchLpm(PiMatchFieldId fieldId, int value, int prefixLength) {
             fieldMatchMapBuilder.put(fieldId, new PiLpmFieldMatch(fieldId, copyFrom(value), prefixLength));
             return this;
         }
@@ -255,7 +255,7 @@
          * @param prefixLength lpm match prefix length
          * @return this
          */
-        public Builder matchLpm(PiHeaderFieldId fieldId, long value, int prefixLength) {
+        public Builder matchLpm(PiMatchFieldId fieldId, long value, int prefixLength) {
             fieldMatchMapBuilder.put(fieldId, new PiLpmFieldMatch(fieldId, copyFrom(value), prefixLength));
             return this;
         }
@@ -268,7 +268,7 @@
          * @param prefixLength lpm match prefix length
          * @return this
          */
-        public Builder matchLpm(PiHeaderFieldId fieldId, byte[] value, int prefixLength) {
+        public Builder matchLpm(PiMatchFieldId fieldId, byte[] value, int prefixLength) {
             fieldMatchMapBuilder.put(fieldId, new PiLpmFieldMatch(fieldId, copyFrom(value), prefixLength));
             return this;
         }
@@ -280,7 +280,7 @@
          * @param flag    a boolean value
          * @return this
          */
-        public Builder matchValid(PiHeaderFieldId fieldId, boolean flag) {
+        public Builder matchValid(PiMatchFieldId fieldId, boolean flag) {
             fieldMatchMapBuilder.put(fieldId, new PiValidFieldMatch(fieldId, flag));
             return this;
         }
@@ -293,7 +293,7 @@
          * @param high    range match high value
          * @return this
          */
-        public Builder matchRange(PiHeaderFieldId fieldId, short low, short high) {
+        public Builder matchRange(PiMatchFieldId fieldId, short low, short high) {
             fieldMatchMapBuilder.put(fieldId, new PiRangeFieldMatch(fieldId, copyFrom(low), copyFrom(high)));
             return this;
         }
@@ -306,7 +306,7 @@
          * @param high    range match high value
          * @return this
          */
-        public Builder matchRange(PiHeaderFieldId fieldId, int low, int high) {
+        public Builder matchRange(PiMatchFieldId fieldId, int low, int high) {
             fieldMatchMapBuilder.put(fieldId, new PiRangeFieldMatch(fieldId, copyFrom(low), copyFrom(high)));
             return this;
         }
@@ -319,7 +319,7 @@
          * @param high    range match high value
          * @return this
          */
-        public Builder matchRange(PiHeaderFieldId fieldId, long low, long high) {
+        public Builder matchRange(PiMatchFieldId fieldId, long low, long high) {
             fieldMatchMapBuilder.put(fieldId, new PiRangeFieldMatch(fieldId, copyFrom(low), copyFrom(high)));
             return this;
         }
@@ -332,7 +332,7 @@
          * @param high    range match high value
          * @return this
          */
-        public Builder matchRange(PiHeaderFieldId fieldId, byte[] low, byte[] high) {
+        public Builder matchRange(PiMatchFieldId fieldId, byte[] low, byte[] high) {
             fieldMatchMapBuilder.put(fieldId, new PiRangeFieldMatch(fieldId, copyFrom(low), copyFrom(high)));
             return this;
         }
@@ -343,9 +343,9 @@
          * @return PiCriterion
          */
         public PiCriterion build() {
-            ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatchMap = fieldMatchMapBuilder.build();
+            ImmutableMap<PiMatchFieldId, PiFieldMatch> fieldMatchMap = fieldMatchMapBuilder.build();
             checkArgument(fieldMatchMap.size() > 0, "Cannot build PI criterion with 0 field matches");
             return new PiCriterion(fieldMatchMap);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/package-info.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionGroupType.java
similarity index 68%
copy from incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/package-info.java
copy to core/api/src/main/java/org/onosproject/net/pi/model/PiActionGroupType.java
index 3dac4a4..dd92888 100644
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/package-info.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionGroupType.java
@@ -14,7 +14,18 @@
  * limitations under the License.
  */
 
+package org.onosproject.net.pi.model;
+
+import com.google.common.annotations.Beta;
+
 /**
- * BMv2 target models.
+ * Type of action group in a protocol-independent pipeline.
  */
-package org.onosproject.bmv2.model;
\ No newline at end of file
+@Beta
+public enum PiActionGroupType {
+
+    /**
+     * Performs load-balancing among different members of the group.
+     */
+    SELECT
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionId.java
similarity index 83%
rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionId.java
rename to core/api/src/main/java/org/onosproject/net/pi/model/PiActionId.java
index 7059fb1..1718726 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionId.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.net.pi.runtime;
+package org.onosproject.net.pi.model;
 
 import com.google.common.annotations.Beta;
 import org.onlab.util.Identifier;
@@ -23,7 +23,8 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Identifier of an action of a match+action table in a protocol-independent pipeline.
+ * Identifier of an action of a match+action table in a protocol-independent pipeline, unique within the scope of a
+ * pipeline model.
  */
 @Beta
 public final class PiActionId extends Identifier<String> {
@@ -38,16 +39,7 @@
     }
 
     /**
-     * Returns the name of the action.
-     *
-     * @return action name
-     */
-    public String name() {
-        return this.identifier;
-    }
-
-    /**
-     * Returns an action identifier with the given name.
+     * Returns an action identifier for the given action name.
      *
      * @param name action name
      * @return action identifier
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiActionModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionModel.java
index 8fd8dc4..4dad230 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiActionModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionModel.java
@@ -18,7 +18,7 @@
 
 import com.google.common.annotations.Beta;
 
-import java.util.List;
+import java.util.Collection;
 import java.util.Optional;
 
 /**
@@ -26,26 +26,27 @@
  */
 @Beta
 public interface PiActionModel {
-    /**
-     * Returns the name of this action.
-     *
-     * @return a string value
-     */
-    String name();
 
     /**
-     * Returns the model of this action's parameter defined by the given name, if present.
+     * Returns the ID of the action.
      *
-     * @param name action name
+     * @return action ID
+     */
+    PiActionId id();
+
+    /**
+     * Returns the model of the action's parameter defined by the given ID, if present.
+     *
+     * @param paramId parameter ID
      * @return action parameter model
      */
-    Optional<PiActionParamModel> param(String name);
+    Optional<PiActionParamModel> param(PiActionParamId paramId);
 
     /**
-     * Returns the list of action parameter models, ordered according to the same action parameters
-     * defined in the pipeline model.
+     * Returns the collection of all parameter models for the action, or an empty collection if this action has no
+     * parameters.
      *
-     * @return list of action parameter models
+     * @return collection of action parameter models
      */
-    List<PiActionParamModel> params();
+    Collection<PiActionParamModel> params();
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParamId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamId.java
similarity index 68%
rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParamId.java
rename to core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamId.java
index 70279ab..143c7bd 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParamId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamId.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.net.pi.runtime;
+package org.onosproject.net.pi.model;
 
 import com.google.common.annotations.Beta;
 import org.onlab.util.Identifier;
@@ -23,32 +23,18 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Identifier of an action's runtime parameter in a match+action table of a protocol-independent pipeline.
+ * Identifier of an action runtime parameter in a match+action table of a protocol-independent pipeline, unique within
+ * the scope an action model.
  */
 @Beta
 public final class PiActionParamId extends Identifier<String> {
 
-    private final String name;
-
-    // TODO: if needed, we might add here support for positional parameters.
-    // E.g. add a second constructor that takes the "position" of a parameter, vs. its name.
-
     private PiActionParamId(String name) {
         super(name);
-        this.name = name;
     }
 
     /**
-     * Returns the name of this parameter.
-     *
-     * @return parameter name
-     */
-    public String name() {
-        return name;
-    }
-
-    /**
-     * Returns a parameter identifier with the given name.
+     * Returns a parameter identifier for the given parameter name.
      *
      * @param name parameter name
      * @return parameter identifier
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamModel.java
index 22ceef6..284d2b2 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionParamModel.java
@@ -19,21 +19,22 @@
 import com.google.common.annotations.Beta;
 
 /**
- * Model of an action parameter in a protocol-independent pipeline.
+ * Model of an action runtime parameter in a protocol-independent pipeline.
  */
 @Beta
 public interface PiActionParamModel {
-    /**
-     * Returns the name of this action parameter.
-     *
-     * @return a string value
-     */
-    String name();
 
     /**
-     * Return the bit width of this action parameter.
+     * Returns the ID of this action parameter.
      *
-     * @return an integer value
+     * @return action parameter ID
+     */
+    PiActionParamId id();
+
+    /**
+     * Return the size in bits of this action parameter.
+     *
+     * @return size in bits
      */
     int bitWidth();
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionProfileId.java
similarity index 70%
rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileId.java
rename to core/api/src/main/java/org/onosproject/net/pi/model/PiActionProfileId.java
index e0158c9..5ff6cae 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionProfileId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionProfileId.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package org.onosproject.net.pi.runtime;
+package org.onosproject.net.pi.model;
 
 import com.google.common.annotations.Beta;
 import org.onlab.util.Identifier;
 
 /**
- * Identifier of an action profile of a protocol-independent pipeline.
+ * Identifier of an action profile in a protocol-independent pipeline, unique withing the scope of a pipeline model.
  */
 @Beta
 public final class PiActionProfileId extends Identifier<String> {
@@ -30,12 +30,12 @@
     }
 
     /**
-     * Returns action profile id with given action profile name.
+     * Returns an identifier for the given action profile name.
      *
-     * @param actionProfileName action profile name
-     * @return action profile id
+     * @param name action profile name
+     * @return action profile ID
      */
-    public static PiActionProfileId of(String actionProfileName) {
-        return new PiActionProfileId(actionProfileName);
+    public static PiActionProfileId of(String name) {
+        return new PiActionProfileId(name);
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiActionProfileModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionProfileModel.java
new file mode 100644
index 0000000..66b8c53
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiActionProfileModel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+
+import java.util.Collection;
+
+/**
+ * Model of an action profile in a protocol-independent pipeline.
+ */
+@Beta
+public interface PiActionProfileModel {
+
+    /**
+     * Returns the ID of this action profile.
+     *
+     * @return action profile ID
+     */
+    PiActionProfileId id();
+
+    /**
+     * Returns the collection of table IDs that use this action profile.
+     *
+     * @return collection of table models
+     */
+    Collection<PiTableId> tables();
+
+    /**
+     * Returns true if this action profile implements dynamic selection, false otherwise.
+     *
+     * @return true if action profile implements dynamic selection, false otherwise
+     */
+    boolean hasSelector();
+
+    /**
+     * Returns the maximum number of member entries of this action profile.
+     *
+     * @return maximum number of member entries
+     */
+    long maxSize();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataId.java
new file mode 100644
index 0000000..b17c766
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataId.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a control metadata in a protocol-independent pipeline, unique within the scope of a pipeline model.
+ */
+@Beta
+public final class PiControlMetadataId extends Identifier<String> {
+
+    private PiControlMetadataId(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns an identifier for the given control metadata name.
+     *
+     * @param name control metadata name
+     * @return control metadata ID
+     */
+    public static PiControlMetadataId of(String name) {
+        checkNotNull(name);
+        checkArgument(!name.isEmpty(), "Name can't be empty");
+        return new PiControlMetadataId(name);
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldTypeModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataModel.java
similarity index 71%
rename from core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldTypeModel.java
rename to core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataModel.java
index 2f4c67b..3748eac 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldTypeModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiControlMetadataModel.java
@@ -19,21 +19,22 @@
 import com.google.common.annotations.Beta;
 
 /**
- * Model of a header's field type in a protocol-independent pipeline.
+ * Model of a control metadata for a protocol-independent pipeline.
  */
 @Beta
-public interface PiHeaderFieldTypeModel {
-    /**
-     * Returns the name of this header type field.
-     *
-     * @return a string value
-     */
-    String name();
+public interface PiControlMetadataModel {
 
     /**
-     * Returns the bit width of this header type field.
+     * Returns the ID of this control metadata.
      *
-     * @return an integer value
+     * @return packet operation metadata ID
+     */
+    PiControlMetadataId id();
+
+    /**
+     * Returns the size in bits of this metadata.
+     *
+     * @return size in bit
      */
     int bitWidth();
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterId.java
new file mode 100644
index 0000000..e9fdc5b
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterId.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a counter in a protocol-independent pipeline, unique within the scope of a pipeline model.
+ */
+@Beta
+public final class PiCounterId extends Identifier<String> {
+
+    private PiCounterId(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns an identifier for the given counter name.
+     *
+     * @param name counter name
+     * @return counter ID
+     */
+    public static PiCounterId of(String name) {
+        checkNotNull(name);
+        checkArgument(!name.isEmpty(), "Name can't be empty");
+        return new PiCounterId(name);
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterModel.java
new file mode 100644
index 0000000..4cfbc92
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterModel.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Model of a counter in protocol-independent pipeline.
+ */
+@Beta
+public interface PiCounterModel {
+
+    /**
+     * Counter unit.
+     */
+    enum Unit {
+        /**
+         * Counts only bytes.
+         */
+        BYTES,
+        /**
+         * Counts only packets.
+         */
+        PACKETS,
+        /**
+         * Counts both packets and bytes.
+         */
+        PACKETS_AND_BYTES
+    }
+
+    /**
+     * Returns the ID of this counter.
+     *
+     * @return counter ID
+     */
+    PiCounterId id();
+
+    /**
+     * Returns the type of counter.
+     *
+     * @return counter type
+     */
+    PiCounterType counterType();
+
+    /**
+     * Returns the unit of this counter.
+     *
+     * @return counter unit
+     */
+    Unit unit();
+
+    /**
+     * Returns the table ID associated with this counter. Meaningful only if the counter type is {@link
+     * PiCounterType#DIRECT}.
+     *
+     * @return table model
+     */
+    PiTableId table();
+
+    /**
+     * Returns the number of cells of this counter. Meaningful only if the counter type is {@link
+     * PiCounterType#INDIRECT}.
+     *
+     * @return size
+     */
+    long size();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterType.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterType.java
similarity index 91%
rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterType.java
rename to core/api/src/main/java/org/onosproject/net/pi/model/PiCounterType.java
index b4a709a..4e73483 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterType.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiCounterType.java
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package org.onosproject.net.pi.runtime;
+package org.onosproject.net.pi.model;
+
+import com.google.common.annotations.Beta;
 
 /**
  * Type of counter in a protocol-independent pipeline.
  */
+@Beta
 public enum PiCounterType {
+
     /**
      * Identifies a counter associated to a match-action table, where cells are directly associated to table entries.
      */
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldModel.java
deleted file mode 100644
index 25e9aa1..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldModel.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.model;
-
-import com.google.common.annotations.Beta;
-
-/**
- * Model of a header's field instance in a protocol-independent pipeline.
- */
-@Beta
-public interface PiHeaderFieldModel {
-    /**
-     * Returns the header instance of this field instance.
-     *
-     * @return a header instance
-     */
-    PiHeaderModel header();
-
-    /**
-     * Returns the type of this header's field instance.
-     *
-     * @return a field type value
-     */
-    PiHeaderFieldTypeModel type();
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderModel.java
deleted file mode 100644
index 22d1a24..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderModel.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.model;
-
-import com.google.common.annotations.Beta;
-
-/**
- * Model of a header instance in a protocol-independent pipeline.
- */
-@Beta
-public interface PiHeaderModel {
-
-    /**
-     * Returns the name of this header instance.
-     *
-     * @return a string value
-     */
-    String name();
-
-    /**
-     * Returns the type of this header instance.
-     *
-     * @return a header type value
-     */
-    PiHeaderTypeModel type();
-
-    /**
-     * Returns true if this header instance is a metadata, false elsewhere.
-     *
-     * @return a boolean value
-     */
-    boolean isMetadata();
-
-    /**
-     * Returns the index of this header w.r.t. to other headers of the same type.
-     * Index 0 points to the first instance of the header, 1 the second one, etc.
-     * Helpful when dealing with stacked headers. e.g. to match on the second MPLS label.
-     *
-     * @return a non-negative integer value
-     */
-    default int index() {
-        return 0;
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderTypeModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderTypeModel.java
deleted file mode 100644
index cfb5ad5..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderTypeModel.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.model;
-
-import com.google.common.annotations.Beta;
-
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Model of a header type in a protocol-independent pipeline.
- */
-@Beta
-public interface PiHeaderTypeModel {
-
-    /**
-     * Returns the name of this header type.
-     *
-     * @return name
-     */
-    String name();
-
-    /**
-     * Returns the field type model defined by the given name, if present.
-     *
-     * @param fieldName field name
-     * @return optional field type model
-     */
-    Optional<PiHeaderFieldTypeModel> field(String fieldName);
-
-    /**
-     * Returns a list of field type models for this header type, ordered according to the same
-     * order of header fields as defined in the pipeline model.
-     *
-     * @return list of field type models
-     */
-    List<PiHeaderFieldTypeModel> fields();
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchFieldId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchFieldId.java
new file mode 100644
index 0000000..ae6f7ca
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchFieldId.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a match field in a protocol-independent pipeline, unique within the scope of a table model.
+ */
+@Beta
+public final class PiMatchFieldId extends Identifier<String> {
+
+    private PiMatchFieldId(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns an identifier for the given match field name.
+     *
+     * @param name match field name
+     * @return match field ID
+     */
+    public static PiMatchFieldId of(String name) {
+        checkNotNull(name);
+        checkArgument(!name.isEmpty(), "Name cannot be empty");
+        return new PiMatchFieldId(name);
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableMatchFieldModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchFieldModel.java
similarity index 73%
rename from core/api/src/main/java/org/onosproject/net/pi/model/PiTableMatchFieldModel.java
rename to core/api/src/main/java/org/onosproject/net/pi/model/PiMatchFieldModel.java
index 4daf96f..57d817a 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableMatchFieldModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchFieldModel.java
@@ -22,18 +22,26 @@
  * Model of a table match field in a protocol-independent pipeline.
  */
 @Beta
-public interface PiTableMatchFieldModel {
+public interface PiMatchFieldModel {
+
     /**
-     * Returns the match type of this key.
+     * Returns the ID of this match field.
+     *
+     * @return match field ID
+     */
+    PiMatchFieldId id();
+
+    /**
+     * Returns the number of bits matched by this field.
+     *
+     * @return number of bits
+     */
+    int bitWidth();
+
+    /**
+     * Returns the type of match applied to this field.
      *
      * @return a match type
      */
     PiMatchType matchType();
-
-    /**
-     * Returns the header field instance matched by this key.
-     *
-     * @return a header field value
-     */
-    PiHeaderFieldModel field();
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchType.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchType.java
index 4f6ab07..1e24fce 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchType.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiMatchType.java
@@ -23,22 +23,27 @@
  */
 @Beta
 public enum PiMatchType {
+
     /**
      * Exact match type.
      */
     EXACT,
+
     /**
      * Ternary match type.
      */
     TERNARY,
+
     /**
      * Longest-prefix match type.
      */
     LPM,
+
     /**
      * Valid match type.
      */
     VALID,
+
     /**
      * Range match type.
      */
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterId.java
new file mode 100644
index 0000000..cc88133
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterId.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a meter in a protocol-independent pipeline, unique within the scope of a pipeline model.
+ */
+@Beta
+public final class PiMeterId extends Identifier<String> {
+
+    private PiMeterId(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns an identifier for the given meter name.
+     *
+     * @param name meter name
+     * @return meter ID
+     */
+    public static PiMeterId of(String name) {
+        checkNotNull(name);
+        checkArgument(!name.isEmpty(), "Name can't be empty");
+        return new PiMeterId(name);
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterModel.java
new file mode 100644
index 0000000..237da05
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterModel.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Model of a meter in a protocol-independent pipeline.
+ */
+@Beta
+public interface PiMeterModel {
+
+    /**
+     * Meter rate unit.
+     */
+    enum Unit {
+        /**
+         * Measures rate of bytes.
+         */
+        BYTES,
+        /**
+         * Measures rate of packets.
+         */
+        PACKETS
+    }
+
+    /**
+     * Returns the ID of this meter.
+     *
+     * @return meter ID
+     */
+    PiMeterId id();
+
+    /**
+     * Returns the type of this meter.
+     *
+     * @return meter type
+     */
+    PiMeterType meterType();
+
+    /**
+     * Returns the unit of this meter.
+     *
+     * @return unit
+     */
+    Unit unit();
+
+    /**
+     * Returns the table model associated with this meter. Meaningful only if the meter type is {@link
+     * PiMeterType#DIRECT}.
+     *
+     * @return table model
+     */
+    PiTableId table();
+
+    /**
+     * Returns the number of cells of this meter. Meaningful only if the meter type is {@link PiMeterType#INDIRECT}.
+     *
+     * @return size
+     */
+    long size();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldTypeModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterType.java
similarity index 69%
copy from core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldTypeModel.java
copy to core/api/src/main/java/org/onosproject/net/pi/model/PiMeterType.java
index 2f4c67b..cb2b57f 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiHeaderFieldTypeModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiMeterType.java
@@ -19,21 +19,19 @@
 import com.google.common.annotations.Beta;
 
 /**
- * Model of a header's field type in a protocol-independent pipeline.
+ * Types of meter in protocol-independent pipeline.
  */
 @Beta
-public interface PiHeaderFieldTypeModel {
-    /**
-     * Returns the name of this header type field.
-     *
-     * @return a string value
-     */
-    String name();
+public enum PiMeterType {
 
     /**
-     * Returns the bit width of this header type field.
-     *
-     * @return an integer value
+     * Identifies a meter associated to a match-action table, where meter cells are directly associated to table
+     * entries.
      */
-    int bitWidth();
+    DIRECT,
+
+    /**
+     * Identifies a meter not associated with any other resource.
+     */
+    INDIRECT
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java
new file mode 100644
index 0000000..70659a6
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationModel.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+
+import java.util.List;
+
+/**
+ * Model of a packet operation in a protocol-independent pipeline.
+ */
+@Beta
+public interface PiPacketOperationModel {
+
+    /**
+     * Returns the type of this packet operation.
+     *
+     * @return packet operation type
+     */
+    PiPacketOperationType type();
+
+    /**
+     * Returns a list of control metadata models for this packet operation. The metadata models are returned in the same
+     * order as they would appear on the control header that is prepended to the packet.
+     *
+     * @return list of packet operation metadata models
+     */
+    List<PiControlMetadataModel> metadatas();
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableMatchFieldModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationType.java
similarity index 60%
copy from core/api/src/main/java/org/onosproject/net/pi/model/PiTableMatchFieldModel.java
copy to core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationType.java
index 4daf96f..360c621 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableMatchFieldModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPacketOperationType.java
@@ -19,21 +19,20 @@
 import com.google.common.annotations.Beta;
 
 /**
- * Model of a table match field in a protocol-independent pipeline.
+ * Type of packet operation in a protocol-independent pipeline.
  */
 @Beta
-public interface PiTableMatchFieldModel {
-    /**
-     * Returns the match type of this key.
-     *
-     * @return a match type
-     */
-    PiMatchType matchType();
+public enum PiPacketOperationType {
 
     /**
-     * Returns the header field instance matched by this key.
-     *
-     * @return a header field value
+     * Represents a packet-out, i.e. a packet generated by the control plane, optionally encapsulated with control
+     * information and sent through a network device port.
      */
-    PiHeaderFieldModel field();
+    PACKET_OUT,
+
+    /**
+     * Represents a packet-in, i.e. a packet originated at the data plane, optionally encapsulated with control
+     * information and sent to the controller for further inspection.
+     */
+    PACKET_IN,
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
index 0185e9b..9b353c8 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconf.java
@@ -24,8 +24,8 @@
 import java.util.Optional;
 
 /**
- * Configuration of a protocol-independent pipeline that includes a pipeline model, a collection of
- * pipeline-specific behaviours implementation, and extensions.
+ * Configuration of a protocol-independent pipeline that includes a pipeline model, a collection of pipeline-specific
+ * behaviour implementations, and extensions.
  */
 @Beta
 public interface PiPipeconf {
@@ -75,13 +75,13 @@
      * @param type extension type
      * @return extension input stream
      */
-    // FIXME: this is a sloppy way of handling extensions.
     Optional<InputStream> extension(ExtensionType type);
 
     /**
      * Type of extension of a protocol-independent pipeline configuration.
      */
     enum ExtensionType {
+
         /**
          * The P4Info as returned by the p4c compiler in text format.
          */
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconfId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconfId.java
index fd1b1bd..4f334e2 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconfId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipeconfId.java
@@ -20,7 +20,7 @@
 import org.onlab.util.Identifier;
 
 /**
- * An identifier of a protocol-independent pipeline configuration.
+ * An identifier of a protocol-independent pipeline configuration, unique within the scope of ONOS.
  */
 @Beta
 public final class PiPipeconfId extends Identifier<String> {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
index 738996f..1068026 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineInterpreter.java
@@ -17,105 +17,99 @@
 package org.onosproject.net.pi.model;
 
 import com.google.common.annotations.Beta;
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.driver.HandlerBehaviour;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
-import org.onosproject.net.pi.runtime.PiTableId;
 
 import java.util.Collection;
 import java.util.Optional;
 
 /**
- * An interpreter of a protocol-independent pipeline model.
+ * An interpreter of a PI pipeline model.
  */
 @Beta
 public interface PiPipelineInterpreter extends HandlerBehaviour {
 
     /**
-     * Returns the protocol-independent header field identifier that is equivalent to the given criterion type, if
-     * present. If not present, it means that the given criterion type is not supported by this interpreter.
+     * Returns a PI match field ID that is equivalent to the given criterion type, if present. If not present, it means
+     * that the given criterion type is not supported by this interpreter.
      *
      * @param type criterion type
-     * @return optional header field identifier
+     * @return optional match field ID
      */
-    Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type);
+    Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type);
 
     /**
-     * Returns the criterion type that is equivalent to the given protocol-independent header field identifier, if
-     * present. If not present, it means that the given field identifier is not supported by this interpreter.
+     * Returns the criterion type that is equivalent to the given PI match field ID, if present. If not present, it
+     * means that the given match field is not supported by this interpreter.
      *
-     * @param headerFieldId header field identifier
+     * @param fieldId match field ID
      * @return optional criterion type
      */
-    Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId);
+    Optional<Criterion.Type> mapPiMatchFieldId(PiMatchFieldId fieldId);
 
     /**
-     * Returns a protocol-independent table id equivalent to the given numeric table id (as in {@link
-     * org.onosproject.net.flow.FlowRule#tableId()}). If not present, it means that the given numeric table id cannot be
+     * Returns a PI table ID equivalent to the given numeric table ID (as in {@link
+     * org.onosproject.net.flow.FlowRule#tableId()}). If not present, it means that the given integer table ID cannot be
      * mapped to any table of the pipeline model.
      *
-     * @param flowRuleTableId a numeric table id
-     * @return a protocol-independent table id
+     * @param flowRuleTableId a numeric table ID
+     * @return PI table ID
      */
     Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId);
 
     /**
-     * Returns a numeric table id (as in {@link org.onosproject.net.flow.FlowRule#tableId()}) equivalent to the given
-     * protocol-independent table id. If not present, it means that the given protocol-independent table id refers to a
-     * table that does not exist, or that cannot be used for flow rule operations.
+     * Returns an integer table ID equivalent to the given PI table ID. If not present, it means that the given PI table
+     * ID refers to a table that does not exist, or that cannot be used for flow rule operations.
      *
-     * @param piTableId protocol-independent table id
-     * @return numeric table id
+     * @param piTableId PI table ID
+     * @return numeric table ID
      */
     Optional<Integer> mapPiTableId(PiTableId piTableId);
 
     /**
-     * Returns an action of a protocol-independent pipeline that is functionally equivalent to the given ONOS traffic
-     * treatment for the given table.
+     * Returns an action of a PI pipeline that is functionally equivalent to the given traffic treatment for the given
+     * table.
      *
-     * @param treatment a ONOS traffic treatment
-     * @param piTableId PI table identifier
-     * @return an action object
-     * @throws PiInterpreterException if the treatment cannot be mapped to any table action
+     * @param treatment traffic treatment
+     * @param piTableId PI table ID
+     * @return action object
+     * @throws PiInterpreterException if the treatment cannot be mapped to any PI action
      */
     PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
             throws PiInterpreterException;
 
     /**
-     * Returns a protocol-independent direct counter identifier for the given table, if present. If not present, it
-     * means that the given table does not support direct counters.
+     * Returns a PI direct counter ID for the given table, if present. If not present, it means that the given table
+     * does not support direct counters.
      *
-     * @param piTableId table identifier
-     * @return optional direct counter identifier
+     * @param piTableId table ID
+     * @return optional direct counter ID
      */
     Optional<PiCounterId> mapTableCounter(PiTableId piTableId);
 
     /**
-     * Returns a collection of packet operations equivalent to the given OutboundPacket.
+     * Returns a collection of PI packet operations equivalent to the given outbound packet instance.
      *
-     * @param packet a ONOS outbound packet
-     * @return a collection of packet operations
-     * @throws PiInterpreterException if the packet treatments cannot be mapped to any metadata
+     * @param packet outbound packet
+     * @return collection of PI packet operations
+     * @throws PiInterpreterException if the packet treatments cannot be executed by this pipeline
      */
     Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
             throws PiInterpreterException;
 
     /**
-     * Returns a InboundPacket equivalent to the given packet operation.
+     * Returns an inbound packet equivalent to the given PI packet operation.
      *
-     * @param deviceId          the device that originated the packet-in
-     * @param packetInOperation the packet operation
-     * @return an ONOS inbound packet
-     * @throws PiInterpreterException if the port can't be extracted from the packet metadata
+     * @param packetOperation packet operation
+     * @return inbound packet
+     * @throws PiInterpreterException if the packet operation cannot be mapped to an inbound packet
      */
-    InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetInOperation)
+    InboundPacket mapInboundPacket(PiPacketOperation packetOperation)
             throws PiInterpreterException;
 
     /**
@@ -127,4 +121,4 @@
             super(message);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineModel.java
index 123388a..c1840d9 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiPipelineModel.java
@@ -28,57 +28,12 @@
 public interface PiPipelineModel {
 
     /**
-     * Returns the header type associated with the given name, if present.
+     * Returns the table model associated with the given ID, if present.
      *
-     * @param name string value
-     * @return optional header type model
-     */
-    Optional<PiHeaderTypeModel> headerType(String name);
-
-    /**
-     * Returns the collection of all header types defined by this pipeline model.
-     *
-     * @return collection of header types
-     */
-    Collection<PiHeaderTypeModel> headerTypes();
-
-    /**
-     * Returns the header instance associated with the given name, if present.
-     *
-     * @param name string value
-     * @return optional header instance model
-     */
-    Optional<PiHeaderModel> header(String name);
-
-    /**
-     * Returns the collection of all header instance models defined by this pipeline model.
-     *
-     * @return collection of header types
-     */
-    Collection<PiHeaderModel> headers();
-
-    /**
-     * Returns the action model associated with the given name, if present.
-     *
-     * @param name string value
-     * @return optional action model
-     */
-    Optional<PiActionModel> action(String name);
-
-    /**
-     * Returns the collection of all action models defined by this pipeline model.
-     *
-     * @return collection of actions
-     */
-    Collection<PiActionModel> actions();
-
-    /**
-     * Returns the table model associated with the given name, if present.
-     *
-     * @param name string value
+     * @param tableId table ID
      * @return optional table model
      */
-    Optional<PiTableModel> table(String name);
+    Optional<PiTableModel> table(PiTableId tableId);
 
     /**
      * Returns the collection of all table models defined by this pipeline model.
@@ -86,4 +41,57 @@
      * @return collection of actions
      */
     Collection<PiTableModel> tables();
+
+    /**
+     * Returns the counter model associated with the given ID, id present.
+     *
+     * @param counterId counter ID
+     * @return optional counter model
+     */
+    Optional<PiCounterModel> counter(PiCounterId counterId);
+
+    /**
+     * Returns all counter models defined by this pipeline model.
+     *
+     * @return collection of counter models
+     */
+    Collection<PiCounterModel> counters();
+
+    /**
+     * Returns the meter model associated with the given ID, id present.
+     *
+     * @param meterId meter ID
+     * @return optional meter model
+     */
+    Optional<PiMeterModel> meter(PiMeterId meterId);
+
+    /**
+     * Returns all meter models defined by this pipeline model.
+     *
+     * @return collection of meter models
+     */
+    Collection<PiMeterModel> meters();
+
+    /**
+     * Returns the action profile model associated with the given ID, id present.
+     *
+     * @param actionProfileId action profile ID
+     * @return optional action profile model
+     */
+    Optional<PiActionProfileModel> actionProfiles(PiActionProfileId actionProfileId);
+
+    /**
+     * Returns all action profile models defined by this pipeline model.
+     *
+     * @return collection of action profile models
+     */
+    Collection<PiActionProfileModel> actionProfiles();
+
+    /**
+     * Returns the packet operation model of the given type, if present.
+     *
+     * @param type packet operation type
+     * @return packet operation model
+     */
+    Optional<PiPacketOperationModel> packetOperationModel(PiPacketOperationType type);
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableId.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableId.java
new file mode 100644
index 0000000..2e799a9
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableId.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.model;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.Identifier;
+import org.onosproject.net.flow.TableId;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Identifier of a table in a protocol-independent pipeline, unique within the scope of a pipeline model.
+ */
+@Beta
+public final class PiTableId extends Identifier<String> implements TableId {
+
+    private PiTableId(String name) {
+        super(name);
+    }
+
+    /**
+     * Returns an identifier for the given table name.
+     *
+     * @param name table name
+     * @return table ID
+     */
+    public static PiTableId of(String name) {
+        checkNotNull(name);
+        checkArgument(!name.isEmpty(), "Name can't be empty");
+        return new PiTableId(name);
+    }
+
+    @Override
+    public Type type() {
+        return Type.PIPELINE_INDEPENDENT;
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java
index 038dadd..e2461a8 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableModel.java
@@ -28,25 +28,47 @@
 public interface PiTableModel {
 
     /**
-     * Returns the name of this table.
+     * Returns the ID of this table.
      *
      * @return a string value
      */
-    String name();
+    PiTableId id();
+
+    /**
+     * Returns the type of this table.
+     *
+     * @return table type
+     */
+    PiTableType tableType();
+
+    /**
+     * Returns the model of the action profile that implements this table. Meaningful if this table is of type {@link
+     * PiTableType#INDIRECT}, otherwise returns null.
+     *
+     * @return action profile ID
+     */
+    PiActionProfileModel actionProfile();
 
     /**
      * Returns the maximum number of entries supported by this table.
      *
      * @return an integer value
      */
-    int maxSize();
+    long maxSize();
 
     /**
-     * Returns true if this table has counters, false otherwise.
+     * Returns a collection of direct counters associated to this table.
      *
-     * @return a boolean value
+     * @return collection of direct counters
      */
-    boolean hasCounters();
+    Collection<PiCounterModel> counters();
+
+    /**
+     * Returns a collection of direct meters associated to this table.
+     *
+     * @return collection of direct meters
+     */
+    Collection<PiMeterModel> meters();
 
     /**
      * Returns true if this table supports aging, false otherwise.
@@ -60,7 +82,7 @@
      *
      * @return a collection of match field models
      */
-    Collection<PiTableMatchFieldModel> matchFields();
+    Collection<PiMatchFieldModel> matchFields();
 
     /**
      * Returns the actions supported by this table.
@@ -70,12 +92,35 @@
     Collection<PiActionModel> actions();
 
     /**
-     * Returns the action model associated with the given name, if present.
-     * If not present, it means that this table does not support such an action.
+     * Returns the model of the default action associated with this table, if any.
      *
-     * @param name string value
+     * @return optional default action model
+     */
+    Optional<PiActionModel> defaultAction();
+
+    /**
+     * Returns true if the default action has mutable parameters that can be changed at runtime, false otherwise.
+     *
+     * @return true if the default action has mutable parameters, false otherwise
+     */
+    boolean hasDefaultMutableParams();
+
+    /**
+     * Returns the action model associated with the given ID, if present. If not present, it means that this table does
+     * not support such an action.
+     *
+     * @param actionId action ID
      * @return optional action model
      */
-    Optional<PiActionModel> action(String name);
+    Optional<PiActionModel> action(PiActionId actionId);
+
+    /**
+     * Returns the match field model associated with the given ID, if present. If not present, it means that this table
+     * does not support such a match field.
+     *
+     * @param matchFieldId match field ID
+     * @return optional match field model
+     */
+    Optional<PiMatchFieldModel> matchField(PiMatchFieldId matchFieldId);
 
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterType.java b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableType.java
similarity index 68%
copy from core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterType.java
copy to core/api/src/main/java/org/onosproject/net/pi/model/PiTableType.java
index b4a709a..9fa31ed 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterType.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/PiTableType.java
@@ -14,19 +14,20 @@
  * limitations under the License.
  */
 
-package org.onosproject.net.pi.runtime;
+package org.onosproject.net.pi.model;
 
 /**
- * Type of counter in a protocol-independent pipeline.
+ * Types of match+action table in a protocol-independent pipeline.
  */
-public enum PiCounterType {
+public enum PiTableType {
+
     /**
-     * Identifies a counter associated to a match-action table, where cells are directly associated to table entries.
+     * Regular match+action table.
      */
     DIRECT,
 
     /**
-     * Identifies a counter not associated with any other resource.
+     * Implementation-based table, e.g. ECMP table where the action executed depends on a selection function.
      */
     INDIRECT
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/model/package-info.java b/core/api/src/main/java/org/onosproject/net/pi/model/package-info.java
index 5d9a05b..a218d7c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/model/package-info.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/model/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * Base abstractions of a protocol-independent packet forwarding pipeline.
+ * Base abstractions of a protocol-independent packet processing pipeline.
  */
-package org.onosproject.net.pi.model;
\ No newline at end of file
+package org.onosproject.net.pi.model;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiAction.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiAction.java
index 6300660..bba3338 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiAction.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiAction.java
@@ -21,6 +21,8 @@
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import java.util.Collection;
 import java.util.Map;
@@ -29,8 +31,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Instance of an action, and its runtime parameters, of a table entry in a protocol-independent
- * pipeline.
+ * Instance of an action, and its runtime parameters, of a table entry in a protocol-independent pipeline.
  */
 @Beta
 public final class PiAction implements PiTableAction {
@@ -64,8 +65,8 @@
     }
 
     /**
-     * Returns all runtime parameters of this action.
-     * Return an empty collection if the action doesn't take any runtime parameters.
+     * Returns all runtime parameters of this action. Return an empty collection if the action doesn't take any runtime
+     * parameters.
      *
      * @return list of byte sequences
      */
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
index 1de4d61..1d9a94b 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
@@ -21,6 +21,8 @@
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import org.onosproject.net.pi.model.PiActionGroupType;
+import org.onosproject.net.pi.model.PiActionProfileId;
 
 import java.util.Collection;
 import java.util.Map;
@@ -29,27 +31,17 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Action group of a protocol-independent pipeline.
+ * Instance of an action group of a protocol-independent pipeline.
  */
 @Beta
 public final class PiActionGroup {
 
-    /**
-     * Type of action group.
-     */
-    public enum Type {
-        /**
-         * Load-balancing among different members in a group.
-         */
-        SELECT
-    }
-
     private final PiActionGroupId id;
-    private final Type type;
+    private final PiActionGroupType type;
     private final ImmutableSet<PiActionGroupMember> members;
     private final PiActionProfileId piActionProfileId;
 
-    private PiActionGroup(PiActionGroupId id, Type type,
+    private PiActionGroup(PiActionGroupId id, PiActionGroupType type,
                           ImmutableSet<PiActionGroupMember> members,
                           PiActionProfileId piActionProfileId) {
         this.id = id;
@@ -72,7 +64,7 @@
      *
      * @return action group type
      */
-    public Type type() {
+    public PiActionGroupType type() {
         return type;
     }
 
@@ -139,7 +131,7 @@
     public static final class Builder {
 
         private PiActionGroupId id;
-        private Type type;
+        private PiActionGroupType type;
         private Map<PiActionGroupMemberId, PiActionGroupMember> members = Maps.newHashMap();
         private PiActionProfileId piActionProfileId;
 
@@ -164,7 +156,7 @@
          * @param type action group type
          * @return this
          */
-        public Builder withType(Type type) {
+        public Builder withType(PiActionGroupType type) {
             this.type = type;
             return this;
         }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupId.java
index 380a4a1..b604237 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupId.java
@@ -20,7 +20,7 @@
 import org.onlab.util.Identifier;
 
 /**
- * Identifier of an action group in a protocol-independent pipeline.
+ * Identifier of an action group in a protocol-independent pipeline, unique within the scope of an action profile.
  */
 @Beta
 public final class PiActionGroupId extends Identifier<Integer> implements PiTableAction {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java
index b5bd542..2905eb9 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMember.java
@@ -23,7 +23,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Member of an action group in a protocol-independent pipeline.
+ * Instance of a member of an action group in a protocol-independent pipeline.
  */
 @Beta
 public final class PiActionGroupMember {
@@ -57,8 +57,8 @@
     }
 
     /**
-     * Returns the weight associated to this member. Valid if the action group is of type {@link
-     * PiActionGroup.Type#SELECT}.
+     * Returns the weight associated to this member. Meaningful if the action group of this member is of type {@link
+     * org.onosproject.net.pi.model.PiActionGroupType#SELECT}.
      *
      * @return weight
      */
@@ -139,7 +139,8 @@
         }
 
         /**
-         * Sets the weight of this member. Valid if the action group is of type {@link PiActionGroup.Type#SELECT}.
+         * Sets the weight of this member. Meaningful only if the action group is of type {@link
+         * org.onosproject.net.pi.model.PiActionGroupType#SELECT}.
          * <p>
          * Default value is 0.
          *
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberId.java
index 98b9d31..3b233a0 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroupMemberId.java
@@ -20,7 +20,8 @@
 import org.onlab.util.Identifier;
 
 /**
- * Identifier of a member of an action group in a protocol-independent pipeline.
+ * Identifier of a member of an action group in a protocol-independent pipeline, unique withing the scope on an action
+ * profile.
  */
 @Beta
 public final class PiActionGroupMemberId extends Identifier<Integer> implements PiTableAction {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java
index 2e4ced3..4498109 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionParam.java
@@ -19,12 +19,13 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Runtime parameter of an action in a match+action table of a protocol-independent pipeline.
+ * Instance of an action runtime parameter in a match+action table of a protocol-independent pipeline.
  */
 @Beta
 public final class PiActionParam {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadata.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiControlMetadata.java
similarity index 70%
rename from core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadata.java
rename to core/api/src/main/java/org/onosproject/net/pi/runtime/PiControlMetadata.java
index 118195b..3caa71c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadata.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiControlMetadata.java
@@ -19,35 +19,36 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiControlMetadataId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Instance of a metadata for a packet I/O operation, with id and value for a protocol-independent pipeline.
+ * Instance of a control metadata for a protocol-independent pipeline.
  */
 @Beta
-public final class PiPacketMetadata {
+public final class PiControlMetadata {
 
-    private final PiPacketMetadataId id;
+    private final PiControlMetadataId id;
     private final ImmutableByteSequence value;
 
     /**
-     * Creates a new packet metadata instance for the given identifier and value.
+     * Creates a new control metadata instance for the given identifier and value.
      *
-     * @param id    packet metadata identifier
+     * @param id    control metadata identifier
      * @param value value for this metadata
      */
-    private PiPacketMetadata(PiPacketMetadataId id, ImmutableByteSequence value) {
+    private PiControlMetadata(PiControlMetadataId id, ImmutableByteSequence value) {
         this.id = id;
         this.value = value;
     }
 
     /**
-     * Return the identifier of this packet metadata.
+     * Return the identifier of this control metadata.
      *
-     * @return packet metadata identifier
+     * @return control metadata identifier
      */
-    public PiPacketMetadataId id() {
+    public PiControlMetadataId id() {
         return id;
     }
 
@@ -68,7 +69,7 @@
         if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        PiPacketMetadata piPacket = (PiPacketMetadata) o;
+        PiControlMetadata piPacket = (PiControlMetadata) o;
         return Objects.equal(id, piPacket.id()) &&
                 Objects.equal(value, piPacket.value());
     }
@@ -84,7 +85,7 @@
     }
 
     /**
-     * Returns a packet metadata builder.
+     * Returns a control metadata builder.
      *
      * @return a new builder
      */
@@ -93,11 +94,11 @@
     }
 
     /**
-     * Builder of protocol-independent packet metadatas.
+     * Builder of protocol-independent control metadatas.
      */
     public static final class Builder {
 
-        private PiPacketMetadataId id;
+        private PiControlMetadataId id;
         private ImmutableByteSequence value;
 
         private Builder() {
@@ -105,12 +106,12 @@
         }
 
         /**
-         * Sets the identifier of this packet metadata.
+         * Sets the identifier of this control metadata.
          *
-         * @param id packet metadata identifier
+         * @param id control metadata identifier
          * @return this
          */
-        public Builder withId(PiPacketMetadataId id) {
+        public Builder withId(PiControlMetadataId id) {
             this.id = id;
             return this;
         }
@@ -127,14 +128,14 @@
         }
 
         /**
-         * Returns a new packet metadata instance.
+         * Returns a new control metadata instance.
          *
-         * @return packet metadata
+         * @return control metadata
          */
-        public PiPacketMetadata build() {
+        public PiControlMetadata build() {
             checkNotNull(id);
             checkNotNull(value);
-            return new PiPacketMetadata(id, value);
+            return new PiControlMetadata(id, value);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java
index cd88d2a..da8a882 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellId.java
@@ -16,22 +16,120 @@
 
 package org.onosproject.net.pi.runtime;
 
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterType;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
  * Identifier of a counter cell in a protocol-independent pipeline.
  */
-public interface PiCounterCellId {
+@Beta
+public final class PiCounterCellId {
+
+    private final PiCounterId counterId;
+    private final PiCounterType counterType;
+    private final long index;
+    private final PiTableEntry tableEntry;
+
+    private PiCounterCellId(PiCounterId counterId, PiCounterType counterType, long index,
+                            PiTableEntry tableEntry) {
+        this.counterId = counterId;
+        this.counterType = counterType;
+        this.index = index;
+        this.tableEntry = tableEntry;
+    }
 
     /**
      * Returns the identifier of the counter instance where this cell is contained.
      *
      * @return counter identifier
      */
-    PiCounterId counterId();
+    public PiCounterId counterId() {
+        return counterId;
+    }
 
     /**
-     * Returns the type of counter identified.
+     * Returns the type of the counter identified.
      *
      * @return counter type
      */
-    PiCounterType type();
+    public PiCounterType counterType() {
+        return counterType;
+    }
+
+    /**
+     * Returns the counter index to which this cell ID is associated. Meaningful only if the counter is of type {@link
+     * PiCounterType#INDIRECT}.
+     *
+     * @return counter index
+     */
+    public long index() {
+        return index;
+    }
+
+    /**
+     * Returns the table entry to which this cell ID is associated. Meaningful only if the counter is of type {@link
+     * PiCounterType#DIRECT}, otherwise returns null.
+     *
+     * @return PI table entry or null
+     */
+    public PiTableEntry tableEntry() {
+        return tableEntry;
+    }
+
+    /**
+     * Return a direct counter cell ID for the given counter ID and table entry.
+     *
+     * @param counterId  counter ID
+     * @param tableEntry table entry
+     * @return counter cell ID
+     */
+    public static PiCounterCellId ofDirect(PiCounterId counterId, PiTableEntry tableEntry) {
+        checkNotNull(counterId);
+        checkNotNull(tableEntry);
+        return new PiCounterCellId(counterId, PiCounterType.DIRECT, -1, tableEntry);
+    }
+
+    /**
+     * Return an indirect counter cell ID for the given counter ID and index.
+     *
+     * @param counterId counter ID
+     * @param index     index
+     * @return counter cell ID
+     */
+    public static PiCounterCellId ofIndirect(PiCounterId counterId, long index) {
+        checkNotNull(counterId);
+        checkArgument(index >= 0, "Index must be a positive number");
+        return new PiCounterCellId(counterId, PiCounterType.INDIRECT, index, null);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final PiCounterCellId other = (PiCounterCellId) obj;
+        return Objects.equal(this.counterId, other.counterId)
+                && Objects.equal(this.counterType, other.counterType)
+                && Objects.equal(this.index, other.index)
+                && Objects.equal(this.tableEntry, other.tableEntry);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(counterId, counterType, index, tableEntry);
+    }
+
+    @Override
+    public String toString() {
+        return counterId.toString() + ':'
+                + (counterType == PiCounterType.DIRECT ? tableEntry.toString() : String.valueOf(index));
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterId.java
deleted file mode 100644
index 015d86b..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterId.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Objects;
-import org.onlab.util.Identifier;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Identifier of a counter of a protocol-independent pipeline.
- */
-@Beta
-public final class PiCounterId extends Identifier<String> {
-
-    private final String scope;
-    private final String name;
-    private final PiCounterType type;
-
-    private PiCounterId(String scope, String name, PiCounterType type) {
-        super((!scope.isEmpty() ? scope + "." : "") + name);
-        this.scope = scope;
-        this.name = name;
-        this.type = type;
-    }
-
-    /**
-     * Returns a counter identifier for the given name and type.
-     *
-     * @param name counter name
-     * @param type counter type
-     * @return counter identifier
-     */
-    public static PiCounterId of(String name, PiCounterType type) {
-        checkNotNull(name);
-        checkNotNull(type);
-        checkArgument(!name.isEmpty(), "Name can't be empty");
-        return new PiCounterId("", name, type);
-    }
-
-    /**
-     * Returns a counter identifier for the given scope, name, and type.
-     *
-     * @param scope counter scope
-     * @param name  counter name
-     * @param type  counter type
-     * @return counter identifier
-     */
-    public static PiCounterId of(String scope, String name, PiCounterType type) {
-        checkNotNull(scope);
-        checkNotNull(name);
-        checkNotNull(type);
-        checkArgument(!scope.isEmpty(), "Scope can't be empty");
-        checkArgument(!name.isEmpty(), "Name can't be empty");
-        return new PiCounterId(scope, name, type);
-    }
-
-    /**
-     * Returns the scope of the counter.
-     *
-     * @return counter scope
-     */
-    public String scope() {
-        return this.scope;
-    }
-
-    /**
-     * Returns the name of the counter.
-     *
-     * @return counter name
-     */
-    public String name() {
-        return this.name;
-    }
-
-    /**
-     * Returns the type of the counter.
-     *
-     * @return counter type
-     */
-    public PiCounterType type() {
-        return this.type;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof PiCounterId)) {
-            return false;
-        }
-        PiCounterId that = (PiCounterId) o;
-        return Objects.equal(id(), that.id()) &&
-                type == that.type;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(id(), type);
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiDirectCounterCellId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiDirectCounterCellId.java
deleted file mode 100644
index 26ae363..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiDirectCounterCellId.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Objects;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.String.format;
-import static org.onosproject.net.pi.runtime.PiCounterType.DIRECT;
-
-/**
- * Identifier of a direct counter cell of a protocol-independent pipeline.
- */
-@Beta
-public final class PiDirectCounterCellId implements PiCounterCellId {
-
-    private final PiCounterId counterId;
-    private final PiTableEntry tableEntry;
-
-    private PiDirectCounterCellId(PiCounterId counterId, PiTableEntry tableEntry) {
-        this.counterId = counterId;
-        this.tableEntry = tableEntry;
-    }
-
-    /**
-     * Returns a direct counter cell identifier for the given counter identifier and table entry.
-     *
-     * @param counterId  counter identifier
-     * @param tableEntry table entry
-     * @return direct counter cell identifier
-     */
-    public static PiDirectCounterCellId of(PiCounterId counterId, PiTableEntry tableEntry) {
-        checkNotNull(counterId);
-        checkNotNull(tableEntry);
-        checkArgument(counterId.type() == DIRECT, "Counter ID must be of type DIRECT");
-        return new PiDirectCounterCellId(counterId, tableEntry);
-    }
-
-    /**
-     * Returns the table entry associated with this cell identifier.
-     *
-     * @return cell table entry
-     */
-    public PiTableEntry tableEntry() {
-        return tableEntry;
-    }
-
-    @Override
-    public PiCounterId counterId() {
-        return counterId;
-    }
-
-    @Override
-    public PiCounterType type() {
-        return DIRECT;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!(o instanceof PiDirectCounterCellId)) {
-            return false;
-        }
-        PiDirectCounterCellId that = (PiDirectCounterCellId) o;
-        return Objects.equal(counterId, that.counterId) &&
-                Objects.equal(tableEntry, that.tableEntry);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(counterId, tableEntry);
-    }
-
-    @Override
-    public String toString() {
-        return format("%s[{%s}]", counterId, tableEntry);
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiExactFieldMatch.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiExactFieldMatch.java
index 946f6c1..d81da99 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiExactFieldMatch.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiExactFieldMatch.java
@@ -19,13 +19,14 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Exact field match in a protocol-independent pipeline.
+ * Instance of an exact field match in a protocol-independent pipeline.
  */
 @Beta
 public final class PiExactFieldMatch extends PiFieldMatch {
@@ -38,7 +39,7 @@
      * @param fieldId field identifier
      * @param value   value
      */
-    public PiExactFieldMatch(PiHeaderFieldId fieldId, ImmutableByteSequence value) {
+    public PiExactFieldMatch(PiMatchFieldId fieldId, ImmutableByteSequence value) {
         super(fieldId);
         this.value = checkNotNull(value);
         checkArgument(value.size() > 0, "Value can't have size 0");
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFieldMatch.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFieldMatch.java
index 8df0452..aa34f22 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFieldMatch.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiFieldMatch.java
@@ -16,23 +16,24 @@
 
 package org.onosproject.net.pi.runtime;
 
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Header's field match in a protocol-independent pipeline.
+ * Instance of a field match in a protocol-independent pipeline.
  */
 public abstract class PiFieldMatch {
 
-    private final PiHeaderFieldId fieldId;
+    private final PiMatchFieldId fieldId;
 
     /**
      * Creates a new field match for the given header field identifier.
      *
      * @param fieldId field identifier.
      */
-    PiFieldMatch(PiHeaderFieldId fieldId) {
+    PiFieldMatch(PiMatchFieldId fieldId) {
         this.fieldId = checkNotNull(fieldId);
     }
 
@@ -42,7 +43,7 @@
      *
      * @return a header field ID value
      */
-    public final PiHeaderFieldId fieldId() {
+    public final PiMatchFieldId fieldId() {
         return fieldId;
     }
 
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java
index 598f3e7..14fec81 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiGroupKey.java
@@ -18,6 +18,8 @@
 
 import com.google.common.base.Objects;
 import org.onosproject.net.group.GroupKey;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiTableId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHeaderFieldId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHeaderFieldId.java
deleted file mode 100644
index 99f7e75..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiHeaderFieldId.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import org.onlab.util.Identifier;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Identifier of a packet's header field.
- */
-public final class PiHeaderFieldId extends Identifier<String> {
-
-    // FIXME: this abstraction is brittle and we should drop any string-composition logic.
-    // e.g. in P4_14 there is no scope for match fields.
-    // In light of ONOS-7066, the best solution seems to have IDs defined as arbitrary
-    // strings equal to the entity names defined in the P4Info.
-    private final String headerName;
-    private final String fieldName;
-    private final int index;
-
-    private PiHeaderFieldId(String headerName, String fieldName, int index) {
-        super(headerName +
-                      (index > 0 ? "[" + String.valueOf(index) + "]" : "") +
-                      "." + fieldName);
-        this.headerName = headerName;
-        this.fieldName = fieldName;
-        this.index = index;
-    }
-
-    /**
-     * Returns an header field identifier for the given header name, field name and index.
-     * <p>
-     * Index represents the position of this header in the packet w.r.t. to other headers of the
-     * same type. Index 0 points to the first instance of the header, 1 the second one, etc. Helpful
-     * when dealing with stacked headers, e.g. to match on the second MPLS label.
-     *
-     * @param headerName header name
-     * @param fieldName  field name
-     * @param index      index
-     * @return header field identifier
-     */
-    public static PiHeaderFieldId of(String headerName, String fieldName, int index) {
-        checkNotNull(headerName);
-        checkNotNull(fieldName);
-        checkArgument(!headerName.isEmpty(), "Header name can't be empty");
-        checkArgument(!fieldName.isEmpty(), "Field name can't be empty");
-        checkArgument(index >= 0, "Index must be a positive integer");
-        return new PiHeaderFieldId(headerName, fieldName, index);
-    }
-
-    /**
-     * Returns an header field identifier for the given header name and field name.
-     * Index is set to default value 0.
-     *
-     * @param headerName header name
-     * @param fieldName  field name
-     * @return header field identifier
-     */
-    public static PiHeaderFieldId of(String headerName, String fieldName) {
-        return of(headerName, fieldName, 0);
-    }
-
-    /**
-     * Returns the name of the header.
-     *
-     * @return a string value
-     */
-    public String headerName() {
-        return headerName;
-    }
-
-    /**
-     * Returns the name of the field.
-     *
-     * @return a string value
-     */
-    public String fieldName() {
-        return fieldName;
-    }
-
-    /**
-     * Returns the index of this header.
-     *
-     * @return an integer value.
-     */
-    public int index() {
-        return index;
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiIndirectCounterCellId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiIndirectCounterCellId.java
deleted file mode 100644
index 6f3f73a..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiIndirectCounterCellId.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.annotations.Beta;
-import org.onlab.util.Identifier;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
-
-/**
- * Identifier of an indirect counter cell in a protocol-independent pipeline.
- */
-@Beta
-public final class PiIndirectCounterCellId extends Identifier<String> implements PiCounterCellId {
-
-    private final PiCounterId counterId;
-    private final long index;
-
-    private PiIndirectCounterCellId(PiCounterId counterId, long index) {
-        super(counterId.toString() + "[" + index + "]");
-        this.counterId = counterId;
-        this.index = index;
-    }
-
-    /**
-     * Returns a counter cell identifier for the given counter identifier and index.
-     *
-     * @param counterId counter identifier
-     * @param index     index
-     * @return counter cell identifier
-     */
-    public static PiIndirectCounterCellId of(PiCounterId counterId, long index) {
-        checkNotNull(counterId);
-        checkArgument(counterId.type() == INDIRECT, "Counter ID must be of type INDIRECT");
-        checkArgument(index >= 0, "Index must be a positive integer");
-        return new PiIndirectCounterCellId(counterId, index);
-    }
-
-    /**
-     * Returns the index of this cell.
-     *
-     * @return cell index
-     */
-    public long index() {
-        return index;
-    }
-
-    @Override
-    public PiCounterId counterId() {
-        return counterId;
-    }
-
-    @Override
-    public PiCounterType type() {
-        return INDIRECT;
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiLpmFieldMatch.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiLpmFieldMatch.java
index 87b8723..24ef957 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiLpmFieldMatch.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiLpmFieldMatch.java
@@ -19,13 +19,14 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Longest-prefix field match in a protocol-independent pipeline.
+ * Instance of a longest-prefix field match in a protocol-independent pipeline.
  */
 @Beta
 public final class PiLpmFieldMatch extends PiFieldMatch {
@@ -40,7 +41,7 @@
      * @param value        value
      * @param prefixLength prefix length
      */
-    public PiLpmFieldMatch(PiHeaderFieldId fieldId, ImmutableByteSequence value, int prefixLength) {
+    public PiLpmFieldMatch(PiMatchFieldId fieldId, ImmutableByteSequence value, int prefixLength) {
         super(fieldId);
         this.value = checkNotNull(value);
         this.prefixLength = prefixLength;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java
index e2e8c3d..a978bfa 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiMatchKey.java
@@ -19,6 +19,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 
 import java.util.Collection;
 import java.util.Optional;
@@ -32,9 +33,9 @@
 
     public static final PiMatchKey EMPTY = builder().build();
 
-    private final ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatches;
+    private final ImmutableMap<PiMatchFieldId, PiFieldMatch> fieldMatches;
 
-    private PiMatchKey(ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatches) {
+    private PiMatchKey(ImmutableMap<PiMatchFieldId, PiFieldMatch> fieldMatches) {
         this.fieldMatches = fieldMatches;
     }
 
@@ -53,7 +54,7 @@
      * @param fieldId field identifier
      * @return optional field match
      */
-    public Optional<PiFieldMatch> fieldMatch(PiHeaderFieldId fieldId) {
+    public Optional<PiFieldMatch> fieldMatch(PiMatchFieldId fieldId) {
         return Optional.ofNullable(fieldMatches.get(fieldId));
     }
 
@@ -95,7 +96,7 @@
      */
     public static final class Builder {
 
-        private final ImmutableMap.Builder<PiHeaderFieldId, PiFieldMatch> fieldMatchesBuilder = ImmutableMap.builder();
+        private final ImmutableMap.Builder<PiMatchFieldId, PiFieldMatch> fieldMatchesBuilder = ImmutableMap.builder();
 
         private Builder() {
             // hides constructor.
@@ -129,7 +130,7 @@
          * @return match key
          */
         public PiMatchKey build() {
-            ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatches = fieldMatchesBuilder.build();
+            ImmutableMap<PiMatchFieldId, PiFieldMatch> fieldMatches = fieldMatchesBuilder.build();
             return new PiMatchKey(fieldMatches);
         }
     }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadataId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadataId.java
deleted file mode 100644
index 55bdf5c..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketMetadataId.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.annotations.Beta;
-import org.onlab.util.Identifier;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Identifier of a metadata for a packet I/O operation in a protocol-independent pipeline.
- */
-@Beta
-public final class PiPacketMetadataId extends Identifier<String> {
-
-    /**
-     * Creates a packet metadata identifier.
-     *
-     * @param name packet metadata name
-     */
-    private PiPacketMetadataId(String name) {
-        super(name);
-    }
-
-    /**
-     * Returns the name of the packet metadata.
-     *
-     * @return packet metadata name
-     */
-    public String name() {
-        return this.identifier;
-    }
-
-    /**
-     * Returns a identifier with the given name.
-     *
-     * @param name packet metadata name
-     * @return packet metadata identifier
-     */
-    public static PiPacketMetadataId of(String name) {
-        checkNotNull(name);
-        checkArgument(!name.isEmpty(), "Name can't be empty");
-        return new PiPacketMetadataId(name);
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java
index 4bbab76..74c68d6 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPacketOperation.java
@@ -21,6 +21,9 @@
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiPacketOperationType;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -30,46 +33,48 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Instance of a packet I/O operation, and its metadatas for a protocol-independent pipeline.
+ * Instance of a packet I/O operation, and its control metadatas, for a protocol-independent pipeline.
  */
 @Beta
 public final class PiPacketOperation {
 
-    public enum Type {
-        /**
-         * Represents a packet out.
-         */
-        PACKET_OUT,
-
-        /**
-         * Represents a packet in.
-         */
-        PACKET_IN,
-    }
-
+    private final DeviceId deviceId;
     private final ImmutableByteSequence data;
-    private final Set<PiPacketMetadata> packetMetadatas;
-    private final PiPacketOperation.Type type;
+    private final Set<PiControlMetadata> packetMetadatas;
+    private final PiPacketOperationType type;
 
     /**
-     * Creates a new packet I/O operation for the given data, metadatas and operation type.
+     * Creates a new packet I/O operation for the given device ID, data, control metadatas and operation type.
      *
+     * @param deviceId        device ID
      * @param data            the packet raw data
-     * @param packetMetadatas set of packet metadata
+     * @param packetMetadatas collection of control metadata
      * @param type            type of this packet operation
      */
-    private PiPacketOperation(ImmutableByteSequence data, Collection<PiPacketMetadata> packetMetadatas, Type type) {
+    private PiPacketOperation(DeviceId deviceId, ImmutableByteSequence data,
+                              Collection<PiControlMetadata> packetMetadatas,
+                              PiPacketOperationType type) {
+        this.deviceId = deviceId;
         this.data = data;
         this.packetMetadatas = ImmutableSet.copyOf(packetMetadatas);
         this.type = type;
     }
 
     /**
+     * Returns the device ID of this packet operation.
+     *
+     * @return device ID
+     */
+    public DeviceId deviceId() {
+        return deviceId;
+    }
+
+    /**
      * Return the type of this packet.
      *
      * @return packet type
      */
-    public Type type() {
+    public PiPacketOperationType type() {
         return type;
     }
 
@@ -83,12 +88,11 @@
     }
 
     /**
-     * Returns all metadatas of this packet.
-     * Returns an empty collection if the packet doesn't have any metadata.
+     * Returns all metadatas of this packet. Returns an empty collection if the packet doesn't have any metadata.
      *
      * @return collection of metadatas
      */
-    public Collection<PiPacketMetadata> metadatas() {
+    public Collection<PiControlMetadata> metadatas() {
         return packetMetadatas;
     }
 
@@ -102,25 +106,27 @@
         }
         PiPacketOperation that = (PiPacketOperation) o;
         return Objects.equal(packetMetadatas, that.packetMetadatas) &&
+                Objects.equal(deviceId, that.deviceId) &&
                 Objects.equal(data, that.data()) &&
                 Objects.equal(type, that.type());
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(data, packetMetadatas, type);
+        return Objects.hashCode(deviceId, data, packetMetadatas, type);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-                .addValue(packetMetadatas)
+                .add("deviceId", deviceId)
                 .addValue(type.toString())
+                .addValue(packetMetadatas)
                 .toString();
     }
 
     /**
-     * Returns an packet builder.
+     * Returns a new builder of packet operations.
      *
      * @return a new builder
      */
@@ -129,12 +135,13 @@
     }
 
     /**
-     * Builder of protocol-independent packets.
+     * Builder of packet operations.
      */
     public static final class Builder {
 
-        private Map<PiPacketMetadataId, PiPacketMetadata> packetMetadatas = new HashMap<>();
-        private PiPacketOperation.Type type;
+        private DeviceId deviceId;
+        private Map<PiControlMetadataId, PiControlMetadata> packetMetadatas = new HashMap<>();
+        private PiPacketOperationType type;
         private ImmutableByteSequence data;
 
         private Builder() {
@@ -142,7 +149,19 @@
         }
 
         /**
-         * Adds the raw packet data.
+         * Sets the device ID.
+         *
+         * @param deviceId device ID
+         * @return this
+         */
+        public Builder forDevice(DeviceId deviceId) {
+            checkNotNull(deviceId);
+            this.deviceId = deviceId;
+            return this;
+        }
+
+        /**
+         * Sets the raw packet data.
          *
          * @param data the packet raw data
          * @return this
@@ -154,14 +173,13 @@
         }
 
         /**
-         * Adds a metadata.
-         * Only one metadata is allowed for a given metadata id.
-         * If a metadata with same id already exists it will be replaced by the given one.
+         * Adds a control metadata. Only one metadata is allowed for a given metadata id. If a metadata with same id
+         * already exists it will be replaced by the given one.
          *
          * @param metadata packet metadata
          * @return this
          */
-        public Builder withMetadata(PiPacketMetadata metadata) {
+        public Builder withMetadata(PiControlMetadata metadata) {
             checkNotNull(metadata);
             packetMetadatas.put(metadata.id(), metadata);
 
@@ -174,7 +192,7 @@
          * @param metadatas collection of metadata
          * @return this
          */
-        public Builder withMetadatas(Collection<PiPacketMetadata> metadatas) {
+        public Builder withMetadatas(Collection<PiControlMetadata> metadatas) {
             checkNotNull(metadatas);
             metadatas.forEach(this::withMetadata);
             return this;
@@ -186,21 +204,22 @@
          * @param type type of the packet
          * @return this
          */
-        public Builder withType(Type type) {
+        public Builder withType(PiPacketOperationType type) {
             this.type = type;
             return this;
         }
 
         /**
-         * Returns a new packet instance.
+         * Builds a new instance of a packet operation.
          *
-         * @return packet
+         * @return packet operation
          */
         public PiPacketOperation build() {
+            checkNotNull(deviceId);
             checkNotNull(data);
             checkNotNull(packetMetadatas);
             checkNotNull(type);
-            return new PiPacketOperation(data, packetMetadatas.values(), type);
+            return new PiPacketOperation(deviceId, data, packetMetadatas.values(), type);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfConfig.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfConfig.java
index 0dfaad9..a04dbf2 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfConfig.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfConfig.java
@@ -21,7 +21,7 @@
 import org.onosproject.net.pi.model.PiPipeconfId;
 
 /**
- * Configuration fot the PiPipeconf susbystem.
+ * Configuration for the PiPipeconf susbystem.
  */
 @Beta
 public class PiPipeconfConfig extends Config<DeviceId> {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfService.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfService.java
index 79bc624..2c6c66c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfService.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiPipeconfService.java
@@ -41,10 +41,9 @@
     void register(PiPipeconf pipeconf) throws IllegalStateException;
 
     /**
-     * Unregisters the Pipeconf identified by the given PiPipeconfId.
-     * Unregistering a Pipeconf removes it from the ONOS controller, thus making it un-capable
-     * of controlling (e.g installing flow rules) the devices that have the pipeconf's p4 program deployed.
-     * For now this method DOES NOT remove the p4 program from the devices.
+     * Unregisters the Pipeconf identified by the given PiPipeconfId. Unregistering a Pipeconf removes it from the ONOS
+     * controller, thus making it un-capable of controlling (e.g installing flow rules) the devices that have the
+     * pipeconf's P4 program deployed. For now this method DOES NOT remove the P4 program from the devices.
      *
      * @param pipeconfId a pipeconfId
      * @throws IllegalStateException if the same pipeconf identifier is already registered.
@@ -59,8 +58,8 @@
     Iterable<PiPipeconf> getPipeconfs();
 
     /**
-     * Returns the pipeconf instance associated with the given identifier, if present.
-     * If not present, it means that no pipeconf with such identifier has been registered so far.
+     * Returns the pipeconf instance associated with the given identifier, if present. If not present, it means that no
+     * pipeconf with such identifier has been registered so far.
      *
      * @param id a pipeconf identifier
      * @return an optional pipeconf
@@ -68,10 +67,9 @@
     Optional<PiPipeconf> getPipeconf(PiPipeconfId id);
 
     /**
-     * Binds the given pipeconf to the given infrastructure device. As a result of this method call,
-     * if the given pipeconf exposes any pipeline-specific behaviours, those will be merged to the
-     * device's driver. Returns a completable future to provide async methods with a boolean if the merge
-     * of the drivers succeeded.
+     * Binds the given pipeconf to the given infrastructure device. As a result of this method call, if the given
+     * pipeconf exposes any pipeline-specific behaviours, those will be merged to the device's driver. Returns a
+     * completable future to provide async methods with a boolean if the merge of the drivers succeeded.
      *
      * @param deviceId   a device identifier
      * @param pipeconfId a pipeconf identifier
@@ -82,8 +80,8 @@
     CompletableFuture<Boolean> bindToDevice(PiPipeconfId pipeconfId, DeviceId deviceId);
 
     /**
-     * Returns the pipeconf identifier currently associated with the given device identifier, if
-     * present. If not present, it means no pipeconf has been associated with that device so far.
+     * Returns the pipeconf identifier currently associated with the given device identifier, if present. If not
+     * present, it means no pipeconf has been associated with that device so far.
      *
      * @param deviceId device identifier
      * @return an optional pipeconf identifier
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRangeFieldMatch.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRangeFieldMatch.java
index b6c39aa..0905d9c 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRangeFieldMatch.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiRangeFieldMatch.java
@@ -19,13 +19,14 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Range field match in a protocol-independent pipeline.
+ * Instance of a range field match in a protocol-independent pipeline.
  */
 @Beta
 public final class PiRangeFieldMatch extends PiFieldMatch {
@@ -40,7 +41,7 @@
      * @param lowValue  low value
      * @param highValue high value
      */
-    public PiRangeFieldMatch(PiHeaderFieldId fieldId, ImmutableByteSequence lowValue,
+    public PiRangeFieldMatch(PiMatchFieldId fieldId, ImmutableByteSequence lowValue,
                              ImmutableByteSequence highValue) {
         super(fieldId);
         this.lowValue = checkNotNull(lowValue);
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableAction.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableAction.java
index cacb49e..7d25e1a 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableAction.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableAction.java
@@ -19,19 +19,15 @@
 import com.google.common.annotations.Beta;
 
 /**
- * An action that can be executed as a consequence of a match in a match+action table of a protocol-independent
- * pipeline.
+ * Instance of an action that can be executed as a consequence of a match in a match+action table of a
+ * protocol-independent pipeline.
  */
 @Beta
 public interface PiTableAction {
 
     /**
-     * Type of this action.
-     *
-     * @return a type
+     * Types of table action.
      */
-    Type type();
-
     enum Type {
         /**
          * Simple action with runtime parameters set by the control plane.
@@ -48,4 +44,11 @@
          */
         GROUP_MEMBER_ID
     }
+
+    /**
+     * Type of this action.
+     *
+     * @return a type
+     */
+    Type type();
 }
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
index 6a5f75b..cd7e493 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
@@ -19,6 +19,7 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
+import org.onosproject.net.pi.model.PiTableId;
 
 import java.util.Optional;
 
@@ -26,7 +27,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Table entry in a protocol-independent pipeline.
+ * Instance of a table entry in a protocol-independent pipeline.
  */
 @Beta
 public final class PiTableEntry {
@@ -99,8 +100,8 @@
     }
 
     /**
-     * Returns the priority of this table entry, if present.
-     * If the priority value is not present, then this table entry has no explicit priority.
+     * Returns the priority of this table entry, if present. If the priority value is not present, then this table entry
+     * has no explicit priority.
      *
      * @return optional priority
      */
@@ -109,8 +110,8 @@
     }
 
     /**
-     * Returns the timeout in seconds of this table entry, if present.
-     * If the timeout value is not present, then this table entry is meant to be permanent.
+     * Returns the timeout in seconds of this table entry, if present. If the timeout value is not present, then this
+     * table entry is meant to be permanent.
      *
      * @return optional timeout value in seconds
      */
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableId.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableId.java
deleted file mode 100644
index 7b988b1..0000000
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableId.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.annotations.Beta;
-import org.onlab.util.Identifier;
-import org.onosproject.net.flow.TableId;
-
-import java.util.Optional;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * Identifier of a table in a protocol-independent pipeline.
- */
-@Beta
-public final class PiTableId extends Identifier<String> implements TableId  {
-
-    private final String scope;
-    private final String name;
-
-    private PiTableId(String scope, String name) {
-        super((scope != null ? scope + "." : "") + name);
-        this.scope = scope;
-        this.name = name;
-    }
-
-    /**
-     * Returns a table identifier for the given table scope and name.
-     *
-     * @param scope table scope
-     * @param name  table name
-     * @return table identifier
-     */
-    public static PiTableId of(String scope, String name) {
-        checkNotNull(name);
-        checkNotNull(scope);
-        checkArgument(!name.isEmpty(), "Name can't be empty");
-        checkArgument(!scope.isEmpty(), "Scope can't be empty");
-        return new PiTableId(scope, name);
-    }
-
-    /**
-     * Returns a table identifier for the given table name.
-     *
-     * @param name table name
-     * @return table identifier
-     */
-    public static PiTableId of(String name) {
-        checkNotNull(name);
-        checkArgument(!name.isEmpty(), "Name can't be empty");
-        return new PiTableId(null, name);
-    }
-
-
-    /**
-     * Returns the name of this table.
-     *
-     * @return table name
-     */
-    public String name() {
-        return name;
-    }
-
-    /**
-     * Returns the scope of this table, if present.
-     *
-     * @return optional scope
-     */
-    public Optional<String> scope() {
-        return Optional.ofNullable(scope);
-    }
-
-    @Override
-    public Type type() {
-        return Type.PIPELINE_INDEPENDENT;
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatch.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatch.java
index 29679ba..8406aed 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatch.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatch.java
@@ -19,13 +19,14 @@
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * Ternary field match in a protocol-independent pipeline.
+ * Instance of a ternary field match in a protocol-independent pipeline.
  */
 @Beta
 public final class PiTernaryFieldMatch extends PiFieldMatch {
@@ -37,10 +38,10 @@
      * Creates a new ternary field match.
      *
      * @param fieldId field identifier
-     * @param value value
-     * @param mask  mask
+     * @param value   value
+     * @param mask    mask
      */
-    public PiTernaryFieldMatch(PiHeaderFieldId fieldId, ImmutableByteSequence value,
+    public PiTernaryFieldMatch(PiMatchFieldId fieldId, ImmutableByteSequence value,
                                ImmutableByteSequence mask) {
         super(fieldId);
         this.value = checkNotNull(value);
@@ -49,11 +50,6 @@
                       "Value and mask must have same non-zero size");
     }
 
-    @Override
-    public PiMatchType type() {
-        return PiMatchType.TERNARY;
-    }
-
     /**
      * Returns the value matched by this field.
      *
@@ -73,6 +69,11 @@
     }
 
     @Override
+    public PiMatchType type() {
+        return PiMatchType.TERNARY;
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) {
             return true;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java
index a0c1101..d099583 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTranslationService.java
@@ -22,7 +22,7 @@
 import org.onosproject.net.pi.model.PiPipeconf;
 
 /**
- * A service to translate ONOS entities to protocol-independent ones.
+ * A service to translate protocol-dependent entities to protocol-independent ones.
  */
 @Beta
 public interface PiTranslationService {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiValidFieldMatch.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiValidFieldMatch.java
index 8b172de..7894ba82 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiValidFieldMatch.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiValidFieldMatch.java
@@ -18,10 +18,11 @@
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Objects;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 /**
- * A valid field match in a protocol-independent pipeline.
+ * Instance of a valid field match in a protocol-independent pipeline.
  */
 @Beta
 public final class PiValidFieldMatch extends PiFieldMatch {
@@ -34,7 +35,7 @@
      * @param fieldId field identifier
      * @param isValid validity flag
      */
-    public PiValidFieldMatch(PiHeaderFieldId fieldId, boolean isValid) {
+    public PiValidFieldMatch(PiMatchFieldId fieldId, boolean isValid) {
         super(fieldId);
         this.isValid = isValid;
     }
diff --git a/core/api/src/test/java/org/onosproject/net/flow/criteria/PiCriteriaTest.java b/core/api/src/test/java/org/onosproject/net/flow/criteria/PiCriteriaTest.java
index f05dfaf..ab0446d 100644
--- a/core/api/src/test/java/org/onosproject/net/flow/criteria/PiCriteriaTest.java
+++ b/core/api/src/test/java/org/onosproject/net/flow/criteria/PiCriteriaTest.java
@@ -18,9 +18,9 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+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.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
@@ -38,195 +38,168 @@
  */
 public class PiCriteriaTest {
 
-    PiHeaderFieldId piEthHeaderFieldId = PiHeaderFieldId.of("ethernet_t", "etherType");
-    byte[] matchExactBytes1 = {0x08, 0x00};
-    byte[] matchExactBytes2 = {0x08, 0x06};
-    Criterion matchPiExactByte1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactBytes1).build();
-    Criterion sameAsMatchPiExactByte1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactBytes1).build();
-    Criterion matchPiExactByte2 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactBytes2).build();
+    private PiMatchFieldId ethMatchFieldId = PiMatchFieldId.of("ethernet_t.etherType");
+    private byte[] matchExactBytes1 = {0x08, 0x00};
+    private byte[] matchExactBytes2 = {0x08, 0x06};
+    private Criterion matchPiExactByte1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactBytes1).build();
+    private Criterion sameAsMatchPiExactByte1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactBytes1).build();
+    private Criterion matchPiExactByte2 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactBytes2).build();
 
-    short matchExactShort1 = 0x800;
-    short matchExactShort2 = 0x806;
-    Criterion matchPiExactShort1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactShort1).build();
-    Criterion sameAsMatchPiExactShort1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactShort1).build();
-    Criterion matchPiExactShort2 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactShort2).build();
+    private short matchExactShort1 = 0x800;
+    private short matchExactShort2 = 0x806;
+    private Criterion matchPiExactShort1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactShort1).build();
+    private Criterion sameAsMatchPiExactShort1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactShort1).build();
+    private Criterion matchPiExactShort2 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactShort2).build();
 
-    int matchExactInt1 = 0x800;
-    int matchExactInt2 = 0x806;
-    Criterion matchPiExactInt1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactInt1).build();
-    Criterion sameAsMatchPiExactInt1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactInt1).build();
-    Criterion matchPiExactInt2 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactInt2).build();
+    private int matchExactInt1 = 0x800;
+    private int matchExactInt2 = 0x806;
+    private Criterion matchPiExactInt1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactInt1).build();
+    private Criterion sameAsMatchPiExactInt1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactInt1).build();
+    private Criterion matchPiExactInt2 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactInt2).build();
 
-    long matchExactLong1 = 0x800;
-    long matchExactLong2 = 0x806;
-    Criterion matchPiExactLong1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactLong1).build();
-    Criterion sameAsMatchPiExactLong1 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactLong1).build();
-    Criterion matchPiExactLong2 = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactLong2).build();
+    private long matchExactLong1 = 0x800;
+    private long matchExactLong2 = 0x806;
+    private Criterion matchPiExactLong1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactLong1).build();
+    private Criterion sameAsMatchPiExactLong1 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactLong1).build();
+    private Criterion matchPiExactLong2 = PiCriterion.builder()
+            .matchExact(ethMatchFieldId, matchExactLong2).build();
 
-    PiHeaderFieldId piIpv4HeaderFieldId = PiHeaderFieldId.of("ipv4_t", "dstAddr");
-    int mask = 0x00ffffff;
-    byte[] matchLpmBytes1 = {0x0a, 0x01, 0x01, 0x01};
-    byte[] matchLpmBytes2 = {0x0a, 0x01, 0x01, 0x02};
-    Criterion matchPiLpmByte1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmBytes1, mask).build();
-    Criterion sameAsMatchPiLpmByte1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmBytes1, mask).build();
-    Criterion matchPiLpmByte2 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmBytes2, mask).build();
+    private PiMatchFieldId ipv4MatchFieldId = PiMatchFieldId.of("ipv4_t.dstAddr");
+    private int mask = 0x00ffffff;
+    private byte[] matchLpmBytes1 = {0x0a, 0x01, 0x01, 0x01};
+    private byte[] matchLpmBytes2 = {0x0a, 0x01, 0x01, 0x02};
+    private Criterion matchPiLpmByte1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmBytes1, mask).build();
+    private Criterion sameAsMatchPiLpmByte1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmBytes1, mask).build();
+    private Criterion matchPiLpmByte2 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmBytes2, mask).build();
 
-    short matchLpmShort1 = 0x0a0a;
-    short matchLpmShort2 = 0x0a0b;
-    Criterion matchPiLpmShort1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmShort1, mask).build();
-    Criterion sameAsMatchPiLpmShort1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId,
-            matchLpmShort1, mask)
-            .build();
-    Criterion matchPiLpmShort2 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmShort2, mask).build();
+    private short matchLpmShort1 = 0x0a0a;
+    private short matchLpmShort2 = 0x0a0b;
+    private Criterion matchPiLpmShort1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmShort1, mask).build();
+    private Criterion sameAsMatchPiLpmShort1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmShort1, mask).build();
+    private Criterion matchPiLpmShort2 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmShort2, mask).build();
 
-    int matchLpmInt1 = 0x0a010101;
-    int matchLpmInt2 = 0x0a010102;
-    Criterion matchPiLpmInt1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmInt1, mask).build();
-    Criterion sameAsMatchPiLpmInt1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmInt1, mask).build();
-    Criterion matchPiLpmInt2 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmInt2, mask).build();
+    private int matchLpmInt1 = 0x0a010101;
+    private int matchLpmInt2 = 0x0a010102;
+    private Criterion matchPiLpmInt1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmInt1, mask).build();
+    private Criterion sameAsMatchPiLpmInt1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmInt1, mask).build();
+    private Criterion matchPiLpmInt2 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmInt2, mask).build();
 
-    long matchLpmLong1 = 0x0a010101;
-    long matchLpmLong2 = 0x0a010102;
-    Criterion matchPiLpmLong1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmLong1, mask).build();
-    Criterion sameAsMatchPiLpmLong1 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmLong1, mask).build();
-    Criterion matchPiLpmLong2 = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmLong2, mask).build();
+    private long matchLpmLong1 = 0x0a010101;
+    private long matchLpmLong2 = 0x0a010102;
+    private Criterion matchPiLpmLong1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmLong1, mask).build();
+    private Criterion sameAsMatchPiLpmLong1 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmLong1, mask).build();
+    private Criterion matchPiLpmLong2 = PiCriterion.builder()
+            .matchLpm(ipv4MatchFieldId, matchLpmLong2, mask).build();
 
 
-    byte[] matchTernaryBytes1 = {0x0a, 0x01, 0x01, 0x01};
-    byte[] matchTernaryBytes2 = {0x0a, 0x01, 0x01, 0x02};
-    byte[] matchTernaryMaskBytes = {0x7f, 0x7f, 0x7f, 0x00};
-    Criterion matchPiTernaryByte1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryBytes1,
-            matchTernaryMaskBytes)
-            .build();
-    Criterion sameAsMatchPiTernaryByte1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryBytes1,
-            matchTernaryMaskBytes)
-            .build();
-    Criterion matchPiTernaryByte2 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryBytes2,
-            matchTernaryMaskBytes)
-            .build();
+    private byte[] matchTernaryBytes1 = {0x0a, 0x01, 0x01, 0x01};
+    private byte[] matchTernaryBytes2 = {0x0a, 0x01, 0x01, 0x02};
+    private byte[] matchTernaryMaskBytes = {0x7f, 0x7f, 0x7f, 0x00};
+    private Criterion matchPiTernaryByte1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryBytes1, matchTernaryMaskBytes).build();
+    private Criterion sameAsMatchPiTernaryByte1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryBytes1, matchTernaryMaskBytes).build();
+    private Criterion matchPiTernaryByte2 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryBytes2, matchTernaryMaskBytes).build();
 
-    short matchTernaryShort1 = 0x0a0a;
-    short matchTernaryShort2 = 0x0a0b;
-    short matchTernaryMaskShort = 0xff0;
-    Criterion matchPiTernaryShort1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryShort1,
-            matchTernaryMaskShort)
-            .build();
-    Criterion sameAsMatchPiTernaryShort1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryShort1,
-            matchTernaryMaskShort)
-            .build();
-    Criterion matchPiTernaryShort2 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryShort2,
-            matchTernaryMaskShort)
-            .build();
+    private short matchTernaryShort1 = 0x0a0a;
+    private short matchTernaryShort2 = 0x0a0b;
+    private short matchTernaryMaskShort = 0xff0;
+    private Criterion matchPiTernaryShort1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryShort1, matchTernaryMaskShort).build();
+    private Criterion sameAsMatchPiTernaryShort1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryShort1, matchTernaryMaskShort).build();
+    private Criterion matchPiTernaryShort2 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryShort2, matchTernaryMaskShort).build();
 
-    int matchTernaryInt1 = 0x0a010101;
-    int matchTernaryInt2 = 0x0a010102;
-    int matchTernaryMaskInt = 0xffff;
-    Criterion matchPiTernaryInt1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryInt1,
-            matchTernaryMaskInt)
-            .build();
-    Criterion sameAsMatchPiTernaryInt1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryInt1,
-            matchTernaryMaskInt)
-            .build();
-    Criterion matchPiTernaryInt2 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryInt2,
-            matchTernaryMaskInt)
-            .build();
+    private int matchTernaryInt1 = 0x0a010101;
+    private int matchTernaryInt2 = 0x0a010102;
+    private int matchTernaryMaskInt = 0xffff;
+    private Criterion matchPiTernaryInt1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryInt1, matchTernaryMaskInt).build();
+    private Criterion sameAsMatchPiTernaryInt1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryInt1, matchTernaryMaskInt).build();
+    private Criterion matchPiTernaryInt2 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryInt2, matchTernaryMaskInt).build();
 
-    long matchTernaryLong1 = 0x0a010101;
-    long matchTernaryLong2 = 0x0a010102;
-    long matchTernaryMaskLong = 0xffff;
-    Criterion matchPiTernaryLong1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryLong1,
-            matchTernaryMaskLong)
-            .build();
-    Criterion sameAsMatchPiTernaryLong1 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryLong1,
-            matchTernaryMaskLong)
-            .build();
-    Criterion matchPiTernaryLong2 = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId,
-            matchTernaryLong2,
-            matchTernaryMaskLong)
-            .build();
+    private long matchTernaryLong1 = 0x0a010101;
+    private long matchTernaryLong2 = 0x0a010102;
+    private long matchTernaryMaskLong = 0xffff;
+    private Criterion matchPiTernaryLong1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryLong1, matchTernaryMaskLong).build();
+    private Criterion sameAsMatchPiTernaryLong1 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryLong1, matchTernaryMaskLong).build();
+    private Criterion matchPiTernaryLong2 = PiCriterion.builder()
+            .matchTernary(ipv4MatchFieldId, matchTernaryLong2, matchTernaryMaskLong).build();
 
-    Criterion matchPiValid1 = PiCriterion.builder().matchValid(piIpv4HeaderFieldId, false).build();
-    Criterion sameAsMatchPiValid1 = PiCriterion.builder().matchValid(piIpv4HeaderFieldId, false).build();
-    Criterion matchPiValid2 = PiCriterion.builder().matchValid(piIpv4HeaderFieldId, true).build();
+    private Criterion matchPiValid1 = PiCriterion.builder().matchValid(ipv4MatchFieldId, false).build();
+    private Criterion sameAsMatchPiValid1 = PiCriterion.builder().matchValid(ipv4MatchFieldId, false).build();
+    private Criterion matchPiValid2 = PiCriterion.builder().matchValid(ipv4MatchFieldId, true).build();
 
-    byte[] matchRangeBytes1 = {0x10};
-    byte[] matchRangeBytes2 = {0x20};
-    byte[] matchRangeHighBytes = {0x30};
-    Criterion matchPiRangeByte1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeBytes1,
-            matchRangeHighBytes)
-            .build();
-    Criterion sameAsMatchPiRangeByte1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeBytes1,
-            matchRangeHighBytes)
-            .build();
-    Criterion matchPiRangeByte2 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeBytes2,
-            matchRangeHighBytes)
-            .build();
+    private byte[] matchRangeBytes1 = {0x10};
+    private byte[] matchRangeBytes2 = {0x20};
+    private byte[] matchRangeHighBytes = {0x30};
+    private Criterion matchPiRangeByte1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeBytes1, matchRangeHighBytes).build();
+    private Criterion sameAsMatchPiRangeByte1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeBytes1, matchRangeHighBytes).build();
+    private Criterion matchPiRangeByte2 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeBytes2, matchRangeHighBytes).build();
 
-    short matchRangeShort1 = 0x100;
-    short matchRangeShort2 = 0x200;
-    short matchRangeHighShort = 0x300;
-    Criterion matchPiRangeShort1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeShort1,
-            matchRangeHighShort)
-            .build();
-    Criterion sameAsMatchPiRangeShort1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeShort1,
-            matchRangeHighShort)
-            .build();
-    Criterion matchPiRangeShort2 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeShort2,
-            matchRangeHighShort)
-            .build();
+    private short matchRangeShort1 = 0x100;
+    private short matchRangeShort2 = 0x200;
+    private short matchRangeHighShort = 0x300;
+    private Criterion matchPiRangeShort1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeShort1, matchRangeHighShort).build();
+    private Criterion sameAsMatchPiRangeShort1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeShort1, matchRangeHighShort).build();
+    private Criterion matchPiRangeShort2 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeShort2, matchRangeHighShort).build();
 
-    int matchRangeInt1 = 0x100;
-    int matchRangeInt2 = 0x200;
-    int matchRangeHighInt = 0x300;
-    Criterion matchPiRangeInt1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeInt1,
-            matchRangeHighInt)
-            .build();
-    Criterion sameAsMatchPiRangeInt1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeInt1,
-            matchRangeHighInt)
-            .build();
-    Criterion matchPiRangeInt2 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeInt2,
-            matchRangeHighInt)
-            .build();
+    private int matchRangeInt1 = 0x100;
+    private int matchRangeInt2 = 0x200;
+    private int matchRangeHighInt = 0x300;
+    private Criterion matchPiRangeInt1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeInt1, matchRangeHighInt).build();
+    private Criterion sameAsMatchPiRangeInt1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeInt1, matchRangeHighInt).build();
+    private Criterion matchPiRangeInt2 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeInt2, matchRangeHighInt).build();
 
-    long matchRangeLong1 = 0x100;
-    long matchRangeLong2 = 0x200;
-    long matchRangeHighLong = 0x300;
-    Criterion matchPiRangeLong1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeLong1,
-            matchRangeHighLong)
-            .build();
-    Criterion sameAsMatchPiRangeLong1 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeLong1,
-            matchRangeHighLong)
-            .build();
-    Criterion matchPiRangeLong2 = PiCriterion.builder().matchRange(piIpv4HeaderFieldId,
-            matchRangeLong2,
-            matchRangeHighLong)
-            .build();
+    private long matchRangeLong1 = 0x100;
+    private long matchRangeLong2 = 0x200;
+    private long matchRangeHighLong = 0x300;
+    private Criterion matchPiRangeLong1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeLong1, matchRangeHighLong).build();
+    private Criterion sameAsMatchPiRangeLong1 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeLong1, matchRangeHighLong).build();
+    private Criterion matchPiRangeLong2 = PiCriterion.builder()
+            .matchRange(ipv4MatchFieldId, matchRangeLong2, matchRangeHighLong).build();
 
     /**
-     * Checks that a Criterion object has the proper type, and then converts
-     * it to the proper type.
+     * Checks that a Criterion object has the proper type, and then converts it to the proper type.
      *
      * @param criterion Criterion object to convert
      * @param type      Enumerated type value for the Criterion class
@@ -248,28 +221,28 @@
     @Test
     public void testExactMatchPiMethod() {
 
-        Criterion matchPiBytes = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactBytes1).build();
+        Criterion matchPiBytes = PiCriterion.builder().matchExact(ethMatchFieldId, matchExactBytes1).build();
         PiCriterion piCriterionBytes = checkAndConvert(matchPiBytes, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchBytes = new PiExactFieldMatch(piEthHeaderFieldId, copyFrom(matchExactBytes1));
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchBytes = new PiExactFieldMatch(ethMatchFieldId, copyFrom(matchExactBytes1));
         assertThat(piCriterionBytes.fieldMatches().iterator().next(), is(expectedMatchBytes));
 
-        Criterion matchPiShort = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactShort1).build();
+        Criterion matchPiShort = PiCriterion.builder().matchExact(ethMatchFieldId, matchExactShort1).build();
         PiCriterion piCriterionShort = checkAndConvert(matchPiShort, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchShort = new PiExactFieldMatch(piEthHeaderFieldId, copyFrom(matchExactShort1));
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchShort = new PiExactFieldMatch(ethMatchFieldId, copyFrom(matchExactShort1));
         assertThat(piCriterionShort.fieldMatches().iterator().next(), is(expectedMatchShort));
 
-        Criterion matchPiInt = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactInt1).build();
+        Criterion matchPiInt = PiCriterion.builder().matchExact(ethMatchFieldId, matchExactInt1).build();
         PiCriterion piCriterionInt = checkAndConvert(matchPiInt, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchInt = new PiExactFieldMatch(piEthHeaderFieldId, copyFrom(matchExactInt1));
+                                                     PiCriterion.class);
+        PiFieldMatch expectedMatchInt = new PiExactFieldMatch(ethMatchFieldId, copyFrom(matchExactInt1));
         assertThat(piCriterionInt.fieldMatches().iterator().next(), is(expectedMatchInt));
 
-        Criterion matchPiLong = PiCriterion.builder().matchExact(piEthHeaderFieldId, matchExactLong1).build();
+        Criterion matchPiLong = PiCriterion.builder().matchExact(ethMatchFieldId, matchExactLong1).build();
         PiCriterion piCriterionLong = checkAndConvert(matchPiLong, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchLong = new PiExactFieldMatch(piEthHeaderFieldId, copyFrom(matchExactLong1));
+                                                      PiCriterion.class);
+        PiFieldMatch expectedMatchLong = new PiExactFieldMatch(ethMatchFieldId, copyFrom(matchExactLong1));
         assertThat(piCriterionLong.fieldMatches().iterator().next(), is(expectedMatchLong));
     }
 
@@ -279,28 +252,28 @@
     @Test
     public void testLpmMatchPiMethod() {
 
-        Criterion matchPiBytes = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmBytes1, mask).build();
+        Criterion matchPiBytes = PiCriterion.builder().matchLpm(ipv4MatchFieldId, matchLpmBytes1, mask).build();
         PiCriterion piCriterionBytes = checkAndConvert(matchPiBytes, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchBytes = new PiLpmFieldMatch(piIpv4HeaderFieldId, copyFrom(matchLpmBytes1), mask);
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchBytes = new PiLpmFieldMatch(ipv4MatchFieldId, copyFrom(matchLpmBytes1), mask);
         assertThat(piCriterionBytes.fieldMatches().iterator().next(), is(expectedMatchBytes));
 
-        Criterion matchPiShort = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmShort1, mask).build();
+        Criterion matchPiShort = PiCriterion.builder().matchLpm(ipv4MatchFieldId, matchLpmShort1, mask).build();
         PiCriterion piCriterionShort = checkAndConvert(matchPiShort, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchShort = new PiLpmFieldMatch(piIpv4HeaderFieldId, copyFrom(matchLpmShort1), mask);
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchShort = new PiLpmFieldMatch(ipv4MatchFieldId, copyFrom(matchLpmShort1), mask);
         assertThat(piCriterionShort.fieldMatches().iterator().next(), is(expectedMatchShort));
 
-        Criterion matchPiInt = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmInt1, mask).build();
+        Criterion matchPiInt = PiCriterion.builder().matchLpm(ipv4MatchFieldId, matchLpmInt1, mask).build();
         PiCriterion piCriterionInt = checkAndConvert(matchPiInt, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchInt = new PiLpmFieldMatch(piIpv4HeaderFieldId, copyFrom(matchLpmInt1), mask);
+                                                     PiCriterion.class);
+        PiFieldMatch expectedMatchInt = new PiLpmFieldMatch(ipv4MatchFieldId, copyFrom(matchLpmInt1), mask);
         assertThat(piCriterionInt.fieldMatches().iterator().next(), is(expectedMatchInt));
 
-        Criterion matchPiLong = PiCriterion.builder().matchLpm(piIpv4HeaderFieldId, matchLpmLong1, mask).build();
+        Criterion matchPiLong = PiCriterion.builder().matchLpm(ipv4MatchFieldId, matchLpmLong1, mask).build();
         PiCriterion piCriterionLong = checkAndConvert(matchPiLong, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchLong = new PiLpmFieldMatch(piIpv4HeaderFieldId, copyFrom(matchLpmLong1), mask);
+                                                      PiCriterion.class);
+        PiFieldMatch expectedMatchLong = new PiLpmFieldMatch(ipv4MatchFieldId, copyFrom(matchLpmLong1), mask);
         assertThat(piCriterionLong.fieldMatches().iterator().next(), is(expectedMatchLong));
     }
 
@@ -310,40 +283,40 @@
     @Test
     public void testTernaryMatchPiMethod() {
 
-        Criterion matchPiBytes = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId, matchTernaryBytes1,
-                matchTernaryMaskBytes)
+        Criterion matchPiBytes = PiCriterion.builder().matchTernary(ipv4MatchFieldId, matchTernaryBytes1,
+                                                                    matchTernaryMaskBytes)
                 .build();
         PiCriterion piCriterionBytes = checkAndConvert(matchPiBytes, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchBytes = new PiTernaryFieldMatch(piIpv4HeaderFieldId, copyFrom(matchTernaryBytes1),
-                copyFrom(matchTernaryMaskBytes));
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchBytes = new PiTernaryFieldMatch(ipv4MatchFieldId, copyFrom(matchTernaryBytes1),
+                                                                  copyFrom(matchTernaryMaskBytes));
         assertThat(piCriterionBytes.fieldMatches().iterator().next(), is(expectedMatchBytes));
 
-        Criterion matchPiShort = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId, matchTernaryShort1,
-                matchTernaryMaskShort)
+        Criterion matchPiShort = PiCriterion.builder().matchTernary(ipv4MatchFieldId, matchTernaryShort1,
+                                                                    matchTernaryMaskShort)
                 .build();
         PiCriterion piCriterionShort = checkAndConvert(matchPiShort, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchShort = new PiTernaryFieldMatch(piIpv4HeaderFieldId, copyFrom(matchTernaryShort1),
-                copyFrom(matchTernaryMaskShort));
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchShort = new PiTernaryFieldMatch(ipv4MatchFieldId, copyFrom(matchTernaryShort1),
+                                                                  copyFrom(matchTernaryMaskShort));
         assertThat(piCriterionShort.fieldMatches().iterator().next(), is(expectedMatchShort));
 
-        Criterion matchPiInt = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId, matchTernaryInt1,
-                matchTernaryMaskInt)
+        Criterion matchPiInt = PiCriterion.builder().matchTernary(ipv4MatchFieldId, matchTernaryInt1,
+                                                                  matchTernaryMaskInt)
                 .build();
         PiCriterion piCriterionInt = checkAndConvert(matchPiInt, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchInt = new PiTernaryFieldMatch(piIpv4HeaderFieldId, copyFrom(matchTernaryInt1),
-                copyFrom(matchTernaryMaskInt));
+                                                     PiCriterion.class);
+        PiFieldMatch expectedMatchInt = new PiTernaryFieldMatch(ipv4MatchFieldId, copyFrom(matchTernaryInt1),
+                                                                copyFrom(matchTernaryMaskInt));
         assertThat(piCriterionInt.fieldMatches().iterator().next(), is(expectedMatchInt));
 
-        Criterion matchPiLong = PiCriterion.builder().matchTernary(piIpv4HeaderFieldId, matchTernaryLong1,
-                matchTernaryMaskLong)
+        Criterion matchPiLong = PiCriterion.builder().matchTernary(ipv4MatchFieldId, matchTernaryLong1,
+                                                                   matchTernaryMaskLong)
                 .build();
         PiCriterion piCriterionLong = checkAndConvert(matchPiLong, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchLong = new PiTernaryFieldMatch(piIpv4HeaderFieldId, copyFrom(matchTernaryLong1),
-                copyFrom(matchTernaryMaskLong));
+                                                      PiCriterion.class);
+        PiFieldMatch expectedMatchLong = new PiTernaryFieldMatch(ipv4MatchFieldId, copyFrom(matchTernaryLong1),
+                                                                 copyFrom(matchTernaryMaskLong));
         assertThat(piCriterionLong.fieldMatches().iterator().next(), is(expectedMatchLong));
     }
 
@@ -353,10 +326,10 @@
     @Test
     public void testValidMatchPiMethod() {
 
-        Criterion matchPiBytes = PiCriterion.builder().matchValid(piIpv4HeaderFieldId, true).build();
+        Criterion matchPiBytes = PiCriterion.builder().matchValid(ipv4MatchFieldId, true).build();
         PiCriterion piCriterionBytes = checkAndConvert(matchPiBytes, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatch = new PiValidFieldMatch(piIpv4HeaderFieldId, true);
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatch = new PiValidFieldMatch(ipv4MatchFieldId, true);
         assertThat(piCriterionBytes.fieldMatches().iterator().next(), is(expectedMatch));
     }
 
@@ -366,40 +339,40 @@
     @Test
     public void testRangeMatchPiMethod() {
 
-        Criterion matchPiBytes = PiCriterion.builder().matchRange(piIpv4HeaderFieldId, matchRangeBytes1,
-                matchRangeHighBytes)
+        Criterion matchPiBytes = PiCriterion.builder().matchRange(ipv4MatchFieldId, matchRangeBytes1,
+                                                                  matchRangeHighBytes)
                 .build();
         PiCriterion piCriterionBytes = checkAndConvert(matchPiBytes, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchBytes = new PiRangeFieldMatch(piIpv4HeaderFieldId, copyFrom(matchRangeBytes1),
-                copyFrom(matchRangeHighBytes));
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchBytes = new PiRangeFieldMatch(ipv4MatchFieldId, copyFrom(matchRangeBytes1),
+                                                                copyFrom(matchRangeHighBytes));
         assertThat(piCriterionBytes.fieldMatches().iterator().next(), is(expectedMatchBytes));
 
-        Criterion matchPiShort = PiCriterion.builder().matchRange(piIpv4HeaderFieldId, matchRangeShort1,
-                matchRangeHighShort)
+        Criterion matchPiShort = PiCriterion.builder().matchRange(ipv4MatchFieldId, matchRangeShort1,
+                                                                  matchRangeHighShort)
                 .build();
         PiCriterion piCriterionShort = checkAndConvert(matchPiShort, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchShort = new PiRangeFieldMatch(piIpv4HeaderFieldId, copyFrom(matchRangeShort1),
-                copyFrom(matchRangeHighShort));
+                                                       PiCriterion.class);
+        PiFieldMatch expectedMatchShort = new PiRangeFieldMatch(ipv4MatchFieldId, copyFrom(matchRangeShort1),
+                                                                copyFrom(matchRangeHighShort));
         assertThat(piCriterionShort.fieldMatches().iterator().next(), is(expectedMatchShort));
 
-        Criterion matchPiInt = PiCriterion.builder().matchRange(piIpv4HeaderFieldId, matchRangeInt1,
-                matchRangeHighInt)
+        Criterion matchPiInt = PiCriterion.builder().matchRange(ipv4MatchFieldId, matchRangeInt1,
+                                                                matchRangeHighInt)
                 .build();
         PiCriterion piCriterionInt = checkAndConvert(matchPiInt, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchInt = new PiRangeFieldMatch(piIpv4HeaderFieldId, copyFrom(matchRangeInt1),
-                copyFrom(matchRangeHighInt));
+                                                     PiCriterion.class);
+        PiFieldMatch expectedMatchInt = new PiRangeFieldMatch(ipv4MatchFieldId, copyFrom(matchRangeInt1),
+                                                              copyFrom(matchRangeHighInt));
         assertThat(piCriterionInt.fieldMatches().iterator().next(), is(expectedMatchInt));
 
-        Criterion matchPiLong = PiCriterion.builder().matchRange(piIpv4HeaderFieldId, matchRangeLong1,
-                matchRangeHighLong)
+        Criterion matchPiLong = PiCriterion.builder().matchRange(ipv4MatchFieldId, matchRangeLong1,
+                                                                 matchRangeHighLong)
                 .build();
         PiCriterion piCriterionLong = checkAndConvert(matchPiLong, Criterion.Type.PROTOCOL_INDEPENDENT,
-                PiCriterion.class);
-        PiFieldMatch expectedMatchLong = new PiRangeFieldMatch(piIpv4HeaderFieldId, copyFrom(matchRangeLong1),
-                copyFrom(matchRangeHighLong));
+                                                      PiCriterion.class);
+        PiFieldMatch expectedMatchLong = new PiRangeFieldMatch(ipv4MatchFieldId, copyFrom(matchRangeLong1),
+                                                               copyFrom(matchRangeHighLong));
         assertThat(piCriterionLong.fieldMatches().iterator().next(), is(expectedMatchLong));
     }
 
diff --git a/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java b/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
index ddf6500..68dc489 100644
--- a/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
+++ b/core/api/src/test/java/org/onosproject/net/flow/instructions/InstructionsTest.java
@@ -34,10 +34,10 @@
 import org.onosproject.net.flow.StatTriggerField;
 import org.onosproject.net.flow.StatTriggerFlag;
 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.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
 import org.onosproject.net.pi.runtime.PiTableAction;
 
 import java.util.EnumMap;
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java
index c9e53ee..020d575 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupMemberTest.java
@@ -18,6 +18,8 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -32,22 +34,22 @@
  */
 public class PiActionGroupMemberTest {
 
-    final PiActionGroupMemberId piActionGroupMemberId = PiActionGroupMemberId.of(10);
-    final PiAction piAction = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
+    private final PiActionGroupMemberId piActionGroupMemberId = PiActionGroupMemberId.of(10);
+    private final PiAction piAction = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
             .withParameter(new PiActionParam(PiActionParamId.of(DST_ADDR), copyFrom(0x0a010101)))
             .build();
 
-    final PiActionGroupMember piActionGroupMember1 = PiActionGroupMember.builder()
+    private final PiActionGroupMember piActionGroupMember1 = PiActionGroupMember.builder()
             .withId(piActionGroupMemberId)
             .withAction(piAction)
             .withWeight(10)
             .build();
-    final PiActionGroupMember sameAsPiActionGroupMember1 = PiActionGroupMember.builder()
+    private final PiActionGroupMember sameAsPiActionGroupMember1 = PiActionGroupMember.builder()
             .withId(piActionGroupMemberId)
             .withAction(piAction)
             .withWeight(10)
             .build();
-    final PiActionGroupMember piActionGroupMember2 = PiActionGroupMember.builder()
+    private final PiActionGroupMember piActionGroupMember2 = PiActionGroupMember.builder()
             .withId(piActionGroupMemberId)
             .withAction(piAction)
             .withWeight(20)
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
index 0da15e4..6672d91 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionGroupTest.java
@@ -20,6 +20,9 @@
 import com.google.common.testing.EqualsTester;
 import org.apache.commons.collections.CollectionUtils;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiActionGroupType;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import java.util.Collection;
 
@@ -37,36 +40,36 @@
  */
 public class PiActionGroupTest {
 
-    final PiActionGroupMemberId piActionGroupMemberId = PiActionGroupMemberId.of(10);
-    final PiAction piAction = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
+    private final PiActionGroupMemberId piActionGroupMemberId = PiActionGroupMemberId.of(10);
+    private final PiAction piAction = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
             .withParameter(new PiActionParam(PiActionParamId.of(DST_ADDR), copyFrom(0x0a010101)))
             .build();
 
-    final PiActionGroupMember piActionGroupMember = PiActionGroupMember.builder()
+    private final PiActionGroupMember piActionGroupMember = PiActionGroupMember.builder()
             .withId(piActionGroupMemberId)
             .withAction(piAction)
             .withWeight(10)
             .build();
-    PiActionGroupId piActionGroupId = PiActionGroupId.of(10);
-    PiActionGroup piActionGroup1 = PiActionGroup.builder()
+    private PiActionGroupId piActionGroupId = PiActionGroupId.of(10);
+    private PiActionGroup piActionGroup1 = PiActionGroup.builder()
             .addMember(piActionGroupMember)
             .withId(piActionGroupId)
-            .withType(PiActionGroup.Type.SELECT)
+            .withType(PiActionGroupType.SELECT)
             .withActionProfileId(ACTION_PROF_ID)
             .build();
 
-    PiActionGroup sameAsPiActionGroup1 = PiActionGroup.builder()
+    private PiActionGroup sameAsPiActionGroup1 = PiActionGroup.builder()
             .addMember(piActionGroupMember)
             .withId(piActionGroupId)
-            .withType(PiActionGroup.Type.SELECT)
+            .withType(PiActionGroupType.SELECT)
             .withActionProfileId(ACTION_PROF_ID)
             .build();
 
-    PiActionGroupId piActionGroupId2 = PiActionGroupId.of(20);
-    PiActionGroup piActionGroup2 = PiActionGroup.builder()
+    private PiActionGroupId piActionGroupId2 = PiActionGroupId.of(20);
+    private PiActionGroup piActionGroup2 = PiActionGroup.builder()
             .addMember(piActionGroupMember)
             .withId(piActionGroupId2)
-            .withType(PiActionGroup.Type.SELECT)
+            .withType(PiActionGroupType.SELECT)
             .withActionProfileId(ACTION_PROF_ID)
             .build();
 
@@ -102,7 +105,7 @@
         piActionGroupMembers.add(piActionGroupMember);
         assertThat(piActionGroup1, is(notNullValue()));
         assertThat(piActionGroup1.id(), is(piActionGroupId));
-        assertThat(piActionGroup1.type(), is(PiActionGroup.Type.SELECT));
+        assertThat(piActionGroup1.type(), is(PiActionGroupType.SELECT));
         assertThat("Incorrect members value",
                    CollectionUtils.isEqualCollection(piActionGroup1.members(), piActionGroupMembers));
     }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionIdTest.java
index 72e27af..262a50f 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionIdTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionIdTest.java
@@ -18,6 +18,7 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiActionId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -60,9 +61,8 @@
      */
     @Test
     public void testConstruction() {
-        final String id = DEC_TTL;
-        final PiActionId actionId = PiActionId.of(id);
+        final PiActionId actionId = PiActionId.of(DEC_TTL);
         assertThat(actionId, is(notNullValue()));
-        assertThat(actionId.name(), is(id));
+        assertThat(actionId.id(), is(DEC_TTL));
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamIdTest.java
index b24adf0..c2793e7 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamIdTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamIdTest.java
@@ -18,6 +18,7 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -60,8 +61,8 @@
     @Test
     public void testConstruction() {
         final String param = SRC_ADDR;
-        final PiActionParamId actionParamId = PiActionParamId.of(SRC_ADDR);
+        final PiActionParamId actionParamId = PiActionParamId.of(param);
         assertThat(actionParamId, is(notNullValue()));
-        assertThat(actionParamId.name(), is(param));
+        assertThat(actionParamId.id(), is(param));
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamTest.java
index e8181b4..993c5e2 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionParamTest.java
@@ -19,6 +19,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -32,11 +33,11 @@
  * Unit tests for PiActionParam class.
  */
 public class PiActionParamTest {
-    ImmutableByteSequence value1 = copyFrom(0x0a010101);
-    ImmutableByteSequence value2 = copyFrom(0x0a010102);
-    final PiActionParam piActionParam1 = new PiActionParam(PiActionParamId.of(DST_ADDR), value1);
-    final PiActionParam sameAsPiActionParam1 = new PiActionParam(PiActionParamId.of(DST_ADDR), value1);
-    final PiActionParam piActionParam2 = new PiActionParam(PiActionParamId.of(DST_ADDR), value2);
+    private ImmutableByteSequence value1 = copyFrom(0x0a010101);
+    private ImmutableByteSequence value2 = copyFrom(0x0a010102);
+    private final PiActionParam piActionParam1 = new PiActionParam(PiActionParamId.of(DST_ADDR), value1);
+    private final PiActionParam sameAsPiActionParam1 = new PiActionParam(PiActionParamId.of(DST_ADDR), value1);
+    private final PiActionParam piActionParam2 = new PiActionParam(PiActionParamId.of(DST_ADDR), value2);
 
     /**
      * Checks that the PiActionParam class is immutable.
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionTest.java
index 86f8686..9a69ab3 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiActionTest.java
@@ -19,7 +19,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
-
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
 
 import java.util.Collection;
 
@@ -35,13 +36,13 @@
  * Unit tests for PiAction class.
  */
 public class PiActionTest {
-    final PiAction piAction1 = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
+    private final PiAction piAction1 = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
             .withParameter(new PiActionParam(PiActionParamId.of(DST_ADDR), copyFrom(0x0a010101)))
             .build();
-    final PiAction sameAsPiAction1 = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
+    private final PiAction sameAsPiAction1 = PiAction.builder().withId(PiActionId.of(MOD_NW_DST))
             .withParameter(new PiActionParam(PiActionParamId.of(DST_ADDR), copyFrom(0x0a010101)))
             .build();
-    final PiAction piAction2 = PiAction.builder().withId(PiActionId.of("set_egress_port_0")).build();
+    private final PiAction piAction2 = PiAction.builder().withId(PiActionId.of("set_egress_port_0")).build();
 
     /**
      * Checks that the PiAction class is immutable.
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java
index 86f713d..158baab 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiConstantsTest.java
@@ -15,30 +15,36 @@
  */
 package org.onosproject.net.pi.runtime;
 
+import org.onosproject.net.pi.model.PiActionProfileId;
+
 /**
- * Unit tests for PiActionId class.
+ * Constants for Pi* unit tests.
  */
 public final class PiConstantsTest {
-    private PiConstantsTest() {
-    }
-    public static final String MOD_NW_DST = "mod_nw_dst";
-    public static final String DEC_TTL = "dec_ttl";
-    public static final String MOD_VLAN_VID = "mod_vlan_vid";
+
+    static final String DOT = ".";
+    static final String MOD_NW_DST = "mod_nw_dst";
+    static final String DEC_TTL = "dec_ttl";
+    static final String MOD_VLAN_VID = "mod_vlan_vid";
     public static final String DROP = "drop";
 
-    public static final String IPV4_HEADER_NAME = "ipv4_t";
-    public static final String ETH_HEADER_NAME = "ethernet_t";
-    public static final String VLAN_HEADER_NAME = "vlan_tag_t";
+    static final String IPV4_HEADER_NAME = "ipv4_t";
+    static final String ETH_HEADER_NAME = "ethernet_t";
+    static final String VLAN_HEADER_NAME = "vlan_tag_t";
 
     public static final String ETH_TYPE = "etherType";
     public static final String DST_ADDR = "dstAddr";
-    public static final String SRC_ADDR = "srcAddr";
-    public static final String VID = "vid";
+    static final String SRC_ADDR = "srcAddr";
+    static final String VID = "vid";
     public static final String PORT = "port";
 
-    public static final String EGRESS_PORT = "egress_port";
-    public static final String INGRESS_PORT = "ingress_port";
+    static final String EGRESS_PORT = "egress_port";
+    static final String INGRESS_PORT = "ingress_port";
 
-    public static final PiActionProfileId ACTION_PROF_ID =
+    static final PiActionProfileId ACTION_PROF_ID =
             PiActionProfileId.of("Test action profile");
+
+    private PiConstantsTest() {
+        // Hides constructor.
+    }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataIdTest.java
similarity index 62%
rename from core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataIdTest.java
rename to core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataIdTest.java
index a2ee50f..d65ef2e 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataIdTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataIdTest.java
@@ -18,6 +18,7 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiControlMetadataId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -27,21 +28,21 @@
 import static org.onosproject.net.pi.runtime.PiConstantsTest.INGRESS_PORT;
 
 /**
- * Unit tests for PiPacketMetadataId class.
+ * Unit tests for PiControlMetadataId class.
  */
-public class PiPacketMetadataIdTest {
+public class PiControlMetadataIdTest {
 
-    final PiPacketMetadataId piPacketMetadataId1 = PiPacketMetadataId.of(EGRESS_PORT);
-    final PiPacketMetadataId sameAsPiPacketMetadataId1 = PiPacketMetadataId.of(EGRESS_PORT);
-    final PiPacketMetadataId piPacketMetadataId2 = PiPacketMetadataId.of(INGRESS_PORT);
+    final PiControlMetadataId piControlMetadataId1 = PiControlMetadataId.of(EGRESS_PORT);
+    final PiControlMetadataId sameAsPiControlMetadataId1 = PiControlMetadataId.of(EGRESS_PORT);
+    final PiControlMetadataId piControlMetadataId2 = PiControlMetadataId.of(INGRESS_PORT);
 
     /**
-     * Checks that the PiPacketMetadataId class is immutable.
+     * Checks that the PiControlMetadataId class is immutable.
      */
     @Test
     public void testImmutability() {
 
-        assertThatClassIsImmutable(PiPacketMetadataId.class);
+        assertThatClassIsImmutable(PiControlMetadataId.class);
     }
 
     /**
@@ -51,18 +52,17 @@
     public void testEquals() {
 
         new EqualsTester()
-                .addEqualityGroup(piPacketMetadataId1, sameAsPiPacketMetadataId1)
-                .addEqualityGroup(piPacketMetadataId2)
+                .addEqualityGroup(piControlMetadataId1, sameAsPiControlMetadataId1)
+                .addEqualityGroup(piControlMetadataId2)
                 .testEquals();
     }
 
     /**
-     * Checks the methods of PiPacketMetadataId.
+     * Checks the methods of PiControlMetadataId.
      */
     @Test
     public void testMethods() {
-
-        assertThat(piPacketMetadataId1, is(notNullValue()));
-        assertThat(piPacketMetadataId1.name(), is(EGRESS_PORT));
+        assertThat(piControlMetadataId1, is(notNullValue()));
+        assertThat(piControlMetadataId1.id(), is(EGRESS_PORT));
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataTest.java
new file mode 100644
index 0000000..ab80fd6
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiControlMetadataTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.runtime;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.EGRESS_PORT;
+
+/**
+ * Unit tests for PiControlMetadata class.
+ */
+public class PiControlMetadataTest {
+
+    final PiControlMetadataId piControlMetadataId = PiControlMetadataId.of(EGRESS_PORT);
+
+    final PiControlMetadata piControlMetadata1 = PiControlMetadata.builder()
+            .withId(piControlMetadataId)
+            .withValue(copyFrom(0x10))
+            .build();
+    final PiControlMetadata sameAsPiControlMetadata1 = PiControlMetadata.builder()
+            .withId(piControlMetadataId)
+            .withValue(copyFrom(0x10))
+            .build();
+    final PiControlMetadata piControlMetadata2 = PiControlMetadata.builder()
+            .withId(piControlMetadataId)
+            .withValue(copyFrom(0x20))
+            .build();
+
+    /**
+     * Checks that the PiControlMetadata class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+
+        assertThatClassIsImmutable(PiControlMetadata.class);
+    }
+
+    /**
+     * Checks the operation of equals(), hashCode() and toString() methods.
+     */
+    @Test
+    public void testEquals() {
+
+        new EqualsTester()
+                .addEqualityGroup(piControlMetadata1, sameAsPiControlMetadata1)
+                .addEqualityGroup(piControlMetadata2)
+                .testEquals();
+    }
+
+    /**
+     * Checks the methods of PiControlMetadata.
+     */
+    @Test
+    public void testMethods() {
+
+        assertThat(piControlMetadata1, is(notNullValue()));
+        assertThat(piControlMetadata1.id(), is(PiControlMetadataId.of(EGRESS_PORT)));
+        assertThat(piControlMetadata1.value(), is(copyFrom(0x10)));
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiExactFieldMatchTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiExactFieldMatchTest.java
index af331c6..38b6765 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiExactFieldMatchTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiExactFieldMatchTest.java
@@ -19,6 +19,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -26,6 +27,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_HEADER_NAME;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_TYPE;
 
@@ -33,12 +35,13 @@
  * Unit tests for PiExactFieldMatch class.
  */
 public class PiExactFieldMatchTest {
-    final ImmutableByteSequence value1 = copyFrom(0x0800);
-    final ImmutableByteSequence value2 = copyFrom(0x0806);
-    final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(ETH_HEADER_NAME, ETH_TYPE);
-    PiExactFieldMatch piExactFieldMatch1 = new PiExactFieldMatch(piHeaderField, value1);
-    PiExactFieldMatch sameAsPiExactFieldMatch1 = new PiExactFieldMatch(piHeaderField, value1);
-    PiExactFieldMatch piExactFieldMatch2 = new PiExactFieldMatch(piHeaderField, value2);
+
+    private final ImmutableByteSequence value1 = copyFrom(0x0800);
+    private final ImmutableByteSequence value2 = copyFrom(0x0806);
+    private final PiMatchFieldId piMatchField = PiMatchFieldId.of(ETH_HEADER_NAME + DOT + ETH_TYPE);
+    private PiExactFieldMatch piExactFieldMatch1 = new PiExactFieldMatch(piMatchField, value1);
+    private PiExactFieldMatch sameAsPiExactFieldMatch1 = new PiExactFieldMatch(piMatchField, value1);
+    private PiExactFieldMatch piExactFieldMatch2 = new PiExactFieldMatch(piMatchField, value2);
 
     /**
      * Checks that the PiExactFieldMatch class is immutable.
@@ -65,8 +68,8 @@
     @Test
     public void testConstruction() {
         final ImmutableByteSequence value = copyFrom(0x0806);
-        final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(ETH_HEADER_NAME, ETH_TYPE);
-        PiExactFieldMatch piExactFieldMatch = new PiExactFieldMatch(piHeaderField, value);
+        final PiMatchFieldId piMatchField = PiMatchFieldId.of(ETH_HEADER_NAME + DOT + ETH_TYPE);
+        PiExactFieldMatch piExactFieldMatch = new PiExactFieldMatch(piMatchField, value);
         assertThat(piExactFieldMatch, is(notNullValue()));
         assertThat(piExactFieldMatch.value(), is(value));
         assertThat(piExactFieldMatch.type(), is(PiMatchType.EXACT));
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiFieldMatchTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiFieldMatchTest.java
index 89f9442..2e63d45 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiFieldMatchTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiFieldMatchTest.java
@@ -17,11 +17,13 @@
 package org.onosproject.net.pi.runtime;
 
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static org.junit.Assert.assertEquals;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutableBaseClass;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_HEADER_NAME;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_TYPE;
 
@@ -41,10 +43,10 @@
 
     @Test
     public void basics() {
-        final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(ETH_HEADER_NAME, ETH_TYPE);
-        PiFieldMatch piFieldMatch = new PiExactFieldMatch(piHeaderField, copyFrom(0x0806));
+        final PiMatchFieldId piMatchField = PiMatchFieldId.of(ETH_HEADER_NAME + DOT + ETH_TYPE);
+        PiFieldMatch piFieldMatch = new PiExactFieldMatch(piMatchField, copyFrom(0x0806));
 
-        assertEquals(piFieldMatch.fieldId(), piHeaderField);
+        assertEquals(piFieldMatch.fieldId(), piMatchField);
         assertEquals(piFieldMatch.type(), PiMatchType.EXACT);
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiHeaderFieldIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiHeaderFieldIdTest.java
deleted file mode 100644
index 8521d5c..0000000
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiHeaderFieldIdTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.testing.EqualsTester;
-import org.junit.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_HEADER_NAME;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_TYPE;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.IPV4_HEADER_NAME;
-
-/**
- * Unit tests for PiHeaderFieldId class.
- */
-public class PiHeaderFieldIdTest {
-    final String headerName = ETH_HEADER_NAME;
-    final String dstAddr = DST_ADDR;
-    final String etherType = ETH_TYPE;
-
-    final PiHeaderFieldId piHeaderFieldId1 = PiHeaderFieldId.of(headerName, dstAddr);
-    final PiHeaderFieldId sameAsPiHeaderFieldId1 = PiHeaderFieldId.of(headerName, dstAddr);
-    final PiHeaderFieldId piHeaderFieldId2 = PiHeaderFieldId.of(headerName, etherType);
-
-    int index = 10;
-    final PiHeaderFieldId piHeaderFieldId1WithIndex = PiHeaderFieldId.of(headerName, dstAddr, index);
-    final PiHeaderFieldId sameAsPiHeaderFieldId1WithIndex = PiHeaderFieldId.of(headerName, dstAddr, index);
-    final PiHeaderFieldId piHeaderFieldId2WithIndex = PiHeaderFieldId.of(headerName, etherType, index);
-
-    /**
-     * Checks that the PiHeaderFieldId class is immutable.
-     */
-    @Test
-    public void testImmutability() {
-        assertThatClassIsImmutable(PiHeaderFieldId.class);
-    }
-
-    /**
-     * Checks the operation of equals(), hashCode() and toString() methods.
-     */
-    @Test
-    public void testEquals() {
-        new EqualsTester()
-                .addEqualityGroup(piHeaderFieldId1, sameAsPiHeaderFieldId1)
-                .addEqualityGroup(piHeaderFieldId2)
-                .testEquals();
-    }
-
-    /**
-     * Checks the operation of equals(), hashCode() and toString() methods.
-     */
-    @Test
-    public void testEqualsWithIndex() {
-        new EqualsTester()
-                .addEqualityGroup(piHeaderFieldId1WithIndex, sameAsPiHeaderFieldId1WithIndex)
-                .addEqualityGroup(piHeaderFieldId2WithIndex)
-                .testEquals();
-    }
-
-    /**
-     * Checks the construction of a PiHeaderFieldId object.
-     */
-    @Test
-    public void testConstruction() {
-        final String name = IPV4_HEADER_NAME;
-        final String field = DST_ADDR;
-
-        final PiHeaderFieldId piHeaderFieldId = PiHeaderFieldId.of(name, field);
-        assertThat(piHeaderFieldId, is(notNullValue()));
-        assertThat(piHeaderFieldId.headerName(), is(name));
-        assertThat(piHeaderFieldId.fieldName(), is(field));
-    }
-
-    /**
-     * Checks the construction of a PiHeaderFieldId object with index.
-     */
-    @Test
-    public void testConstructionWithIndex() {
-        final String name = IPV4_HEADER_NAME;
-        final String field = DST_ADDR;
-        final int index = 1;
-        final PiHeaderFieldId piHeaderFieldId = PiHeaderFieldId.of(name, field, index);
-        assertThat(piHeaderFieldId, is(notNullValue()));
-        assertThat(piHeaderFieldId.headerName(), is(name));
-        assertThat(piHeaderFieldId.fieldName(), is(field));
-        assertThat(piHeaderFieldId.index(), is(index));
-    }
-}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiLpmFieldMatchTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiLpmFieldMatchTest.java
index a25c434..8257fcc 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiLpmFieldMatchTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiLpmFieldMatchTest.java
@@ -19,6 +19,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -26,6 +27,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.IPV4_HEADER_NAME;
 
@@ -33,13 +35,13 @@
  * Unit tests for PiLpmFieldMatch class.
  */
 public class PiLpmFieldMatchTest {
-    final ImmutableByteSequence value1 = copyFrom(0x0a010101);
-    final ImmutableByteSequence value2 = copyFrom(0x0a010102);
-    int prefixLength = 24;
-    final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(IPV4_HEADER_NAME, DST_ADDR);
-    PiLpmFieldMatch piLpmFieldMatch1 = new PiLpmFieldMatch(piHeaderField, value1, prefixLength);
-    PiLpmFieldMatch sameAsPiLpmFieldMatch1 = new PiLpmFieldMatch(piHeaderField, value1, prefixLength);
-    PiLpmFieldMatch piLpmFieldMatch2 = new PiLpmFieldMatch(piHeaderField, value2, prefixLength);
+    private final ImmutableByteSequence value1 = copyFrom(0x0a010101);
+    private final ImmutableByteSequence value2 = copyFrom(0x0a010102);
+    private int prefixLength = 24;
+    private final PiMatchFieldId piMatchField = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + DST_ADDR);
+    private PiLpmFieldMatch piLpmFieldMatch1 = new PiLpmFieldMatch(piMatchField, value1, prefixLength);
+    private PiLpmFieldMatch sameAsPiLpmFieldMatch1 = new PiLpmFieldMatch(piMatchField, value1, prefixLength);
+    private PiLpmFieldMatch piLpmFieldMatch2 = new PiLpmFieldMatch(piMatchField, value2, prefixLength);
 
     /**
      * Checks that the PiLpmFieldMatch class is immutable.
@@ -67,8 +69,8 @@
     public void testConstruction() {
         final ImmutableByteSequence value = copyFrom(0x0a01010a);
         int prefix = 24;
-        final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(IPV4_HEADER_NAME, DST_ADDR);
-        PiLpmFieldMatch piLpmFieldMatch = new PiLpmFieldMatch(piHeaderField, value, prefix);
+        final PiMatchFieldId piMatchField = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + DST_ADDR);
+        PiLpmFieldMatch piLpmFieldMatch = new PiLpmFieldMatch(piMatchField, value, prefix);
         assertThat(piLpmFieldMatch, is(notNullValue()));
         assertThat(piLpmFieldMatch.value(), is(value));
         assertThat(piLpmFieldMatch.prefixLength(), is(prefix));
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchFieldIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchFieldIdTest.java
new file mode 100644
index 0000000..2724da0
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchFieldIdTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.net.pi.runtime;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_HEADER_NAME;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.ETH_TYPE;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.IPV4_HEADER_NAME;
+
+/**
+ * Unit tests for PiMatchFieldId class.
+ */
+public class PiMatchFieldIdTest {
+    private final String headerName = ETH_HEADER_NAME;
+    private final String dstAddr = DST_ADDR;
+    private final String etherType = ETH_TYPE;
+
+    private final PiMatchFieldId piMatchFieldId1 = PiMatchFieldId.of(headerName + DOT + dstAddr);
+    private final PiMatchFieldId sameAsPiMatchFieldId1 = PiMatchFieldId.of(headerName + DOT + dstAddr);
+    private final PiMatchFieldId piMatchFieldId2 = PiMatchFieldId.of(headerName + DOT + etherType);
+
+    private int index = 10;
+    private final PiMatchFieldId piMatchFieldId1WithIndex = PiMatchFieldId
+            .of(headerName + DOT + dstAddr + "[" + index + "]");
+    private final PiMatchFieldId sameAsPiMatchFieldId1WithIndex = PiMatchFieldId
+            .of(headerName + DOT + dstAddr + "[" + index + "]");
+    private final PiMatchFieldId piMatchFieldId2WithIndex = PiMatchFieldId
+            .of(headerName + DOT + etherType + "[" + index + "]");
+
+    /**
+     * Checks that the PiMatchFieldId class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(PiMatchFieldId.class);
+    }
+
+    /**
+     * Checks the operation of equals(), hashCode() and toString() methods.
+     */
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(piMatchFieldId1, sameAsPiMatchFieldId1)
+                .addEqualityGroup(piMatchFieldId2)
+                .testEquals();
+    }
+
+    /**
+     * Checks the operation of equals(), hashCode() and toString() methods.
+     */
+    @Test
+    public void testEqualsWithIndex() {
+        new EqualsTester()
+                .addEqualityGroup(piMatchFieldId1WithIndex, sameAsPiMatchFieldId1WithIndex)
+                .addEqualityGroup(piMatchFieldId2WithIndex)
+                .testEquals();
+    }
+
+    /**
+     * Checks the construction of a PiMatchFieldId object.
+     */
+    @Test
+    public void testConstruction() {
+        final String name = IPV4_HEADER_NAME  + DOT + DST_ADDR;
+        final PiMatchFieldId piMatchFieldId = PiMatchFieldId.of(name);
+        assertThat(piMatchFieldId, is(notNullValue()));
+        assertThat(piMatchFieldId.id(), is(name));
+    }
+
+    /**
+     * Checks the construction of a PiMatchFieldId object with index.
+     */
+    @Test
+    public void testConstructionWithIndex() {
+        final String name = IPV4_HEADER_NAME + DOT + DST_ADDR + "[1]";
+        final PiMatchFieldId piMatchFieldId = PiMatchFieldId.of(name);
+        assertThat(piMatchFieldId, is(notNullValue()));
+        assertThat(piMatchFieldId.id(), is(name));
+    }
+}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchKeyTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchKeyTest.java
index ea866fa..a6d62af 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchKeyTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiMatchKeyTest.java
@@ -21,6 +21,7 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 
 import java.util.Collection;
 
@@ -29,6 +30,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.IPV4_HEADER_NAME;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.SRC_ADDR;
@@ -38,21 +40,21 @@
  */
 public class PiMatchKeyTest {
 
-    final ImmutableByteSequence value1 = copyFrom(0x0a010101);
-    final ImmutableByteSequence value2 = copyFrom(0x0a010102);
-    int prefixLength = 24;
-    final PiHeaderFieldId piHeaderField1 = PiHeaderFieldId.of(IPV4_HEADER_NAME, SRC_ADDR);
-    final PiHeaderFieldId piHeaderField2 = PiHeaderFieldId.of(IPV4_HEADER_NAME, DST_ADDR);
-    PiLpmFieldMatch piLpmFieldMatch1 = new PiLpmFieldMatch(piHeaderField1, value1, prefixLength);
-    PiLpmFieldMatch piLpmFieldMatch2 = new PiLpmFieldMatch(piHeaderField2, value2, prefixLength);
+    private final ImmutableByteSequence value1 = copyFrom(0x0a010101);
+    private final ImmutableByteSequence value2 = copyFrom(0x0a010102);
+    private int prefixLength = 24;
+    private final PiMatchFieldId piMatchField1 = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + SRC_ADDR);
+    private final PiMatchFieldId piMatchField2 = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + DST_ADDR);
+    private PiLpmFieldMatch piLpmFieldMatch1 = new PiLpmFieldMatch(piMatchField1, value1, prefixLength);
+    private PiLpmFieldMatch piLpmFieldMatch2 = new PiLpmFieldMatch(piMatchField2, value2, prefixLength);
 
-    final PiMatchKey piMatchKey1 = PiMatchKey.builder()
+    private final PiMatchKey piMatchKey1 = PiMatchKey.builder()
             .addFieldMatch(piLpmFieldMatch1)
             .build();
-    final PiMatchKey sameAsPiMatchKey1 = PiMatchKey.builder()
+    private final PiMatchKey sameAsPiMatchKey1 = PiMatchKey.builder()
             .addFieldMatch(piLpmFieldMatch1)
             .build();
-    final PiMatchKey piMatchKey2 = PiMatchKey.builder()
+    private final PiMatchKey piMatchKey2 = PiMatchKey.builder()
             .addFieldMatch(piLpmFieldMatch2)
             .build();
 
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataTest.java
deleted file mode 100644
index 8ba4d4e..0000000
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketMetadataTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.net.pi.runtime;
-
-import com.google.common.testing.EqualsTester;
-import org.junit.Test;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
-import static org.onlab.util.ImmutableByteSequence.copyFrom;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.EGRESS_PORT;
-
-/**
- * Unit tests for PiPacketMetadata class.
- */
-public class PiPacketMetadataTest {
-
-    final PiPacketMetadataId piPacketMetadataId = PiPacketMetadataId.of(EGRESS_PORT);
-
-    final PiPacketMetadata piPacketMetadata1 = PiPacketMetadata.builder()
-            .withId(piPacketMetadataId)
-            .withValue(copyFrom(0x10))
-            .build();
-    final PiPacketMetadata sameAsPiPacketMetadata1 = PiPacketMetadata.builder()
-            .withId(piPacketMetadataId)
-            .withValue(copyFrom(0x10))
-            .build();
-    final PiPacketMetadata piPacketMetadata2 = PiPacketMetadata.builder()
-            .withId(piPacketMetadataId)
-            .withValue(copyFrom(0x20))
-            .build();
-
-    /**
-     * Checks that the PiPacketMetadata class is immutable.
-     */
-    @Test
-    public void testImmutability() {
-
-        assertThatClassIsImmutable(PiPacketMetadata.class);
-    }
-
-    /**
-     * Checks the operation of equals(), hashCode() and toString() methods.
-     */
-    @Test
-    public void testEquals() {
-
-        new EqualsTester()
-                .addEqualityGroup(piPacketMetadata1, sameAsPiPacketMetadata1)
-                .addEqualityGroup(piPacketMetadata2)
-                .testEquals();
-    }
-
-    /**
-     * Checks the methods of PiPacketMetadata.
-     */
-    @Test
-    public void testMethods() {
-
-        assertThat(piPacketMetadata1, is(notNullValue()));
-        assertThat(piPacketMetadata1.id(), is(PiPacketMetadataId.of(EGRESS_PORT)));
-        assertThat(piPacketMetadata1.value(), is(copyFrom(0x10)));
-    }
-}
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java
index 619099f..d675b51 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiPacketOperationTest.java
@@ -21,43 +21,50 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.EGRESS_PORT;
-import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
 
 /**
  * Unit tests for PiPacketOperation class.
  */
 public class PiPacketOperationTest {
 
-    final PiPacketOperation piPacketOperation1 = PiPacketOperation.builder()
+    private final DeviceId deviceId = DeviceId.deviceId("dummy");
+
+    private final PiPacketOperation piPacketOperation1 = PiPacketOperation.builder()
+            .forDevice(deviceId)
             .withData(ImmutableByteSequence.ofOnes(512))
             .withType(PACKET_OUT)
-            .withMetadata(PiPacketMetadata.builder()
-                                  .withId(PiPacketMetadataId.of(EGRESS_PORT))
+            .withMetadata(PiControlMetadata.builder()
+                                  .withId(PiControlMetadataId.of(EGRESS_PORT))
                                   .withValue(copyFrom((short) 255))
                                   .build())
             .build();
 
-    final PiPacketOperation sameAsPiPacketOperation1 = PiPacketOperation.builder()
+    private final PiPacketOperation sameAsPiPacketOperation1 = PiPacketOperation.builder()
+            .forDevice(deviceId)
             .withData(ImmutableByteSequence.ofOnes(512))
             .withType(PACKET_OUT)
-            .withMetadata(PiPacketMetadata.builder()
-                                  .withId(PiPacketMetadataId.of(EGRESS_PORT))
+            .withMetadata(PiControlMetadata.builder()
+                                  .withId(PiControlMetadataId.of(EGRESS_PORT))
                                   .withValue(copyFrom((short) 255))
                                   .build())
             .build();
 
-    final PiPacketOperation piPacketOperation2 = PiPacketOperation.builder()
+    private final PiPacketOperation piPacketOperation2 = PiPacketOperation.builder()
+            .forDevice(deviceId)
             .withData(ImmutableByteSequence.ofOnes(512))
             .withType(PACKET_OUT)
-            .withMetadata(PiPacketMetadata.builder()
-                                  .withId(PiPacketMetadataId.of(EGRESS_PORT))
+            .withMetadata(PiControlMetadata.builder()
+                                  .withId(PiControlMetadataId.of(EGRESS_PORT))
                                   .withValue(copyFrom((short) 200))
                                   .build())
             .build();
@@ -90,21 +97,23 @@
     public void testMethods() {
 
         final PiPacketOperation piPacketOperation = PiPacketOperation.builder()
+                .forDevice(deviceId)
                 .withData(ImmutableByteSequence.ofOnes(512))
                 .withType(PACKET_OUT)
-                .withMetadata(PiPacketMetadata.builder()
-                                      .withId(PiPacketMetadataId.of(EGRESS_PORT))
+                .withMetadata(PiControlMetadata.builder()
+                                      .withId(PiControlMetadataId.of(EGRESS_PORT))
                                       .withValue(copyFrom((short) 10))
                                       .build())
                 .build();
 
         assertThat(piPacketOperation, is(notNullValue()));
+        assertThat(piPacketOperation.deviceId(), is(deviceId));
         assertThat(piPacketOperation.type(), is(PACKET_OUT));
         assertThat(piPacketOperation.data(), is(ImmutableByteSequence.ofOnes(512)));
         assertThat("Incorrect metadatas value",
                    CollectionUtils.isEqualCollection(piPacketOperation.metadatas(),
-                                                     ImmutableList.of(PiPacketMetadata.builder()
-                                                                              .withId(PiPacketMetadataId
+                                                     ImmutableList.of(PiControlMetadata.builder()
+                                                                              .withId(PiControlMetadataId
                                                                                               .of(EGRESS_PORT))
                                                                               .withValue(copyFrom((short) 10))
                                                                               .build())));
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiRangeFieldMatchTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiRangeFieldMatchTest.java
index 27c551c..7fb28b9 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiRangeFieldMatchTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiRangeFieldMatchTest.java
@@ -19,6 +19,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -26,6 +27,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.VID;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.VLAN_HEADER_NAME;
 
@@ -33,15 +35,15 @@
  * Unit tests for PiRangeFieldMatch class.
  */
 public class PiRangeFieldMatchTest {
-    final ImmutableByteSequence high1 = copyFrom(0x10);
-    final ImmutableByteSequence low1 = copyFrom(0x00);
-    final ImmutableByteSequence high2 = copyFrom(0x30);
-    final ImmutableByteSequence low2 = copyFrom(0x40);
+    private final ImmutableByteSequence high1 = copyFrom(0x10);
+    private final ImmutableByteSequence low1 = copyFrom(0x00);
+    private final ImmutableByteSequence high2 = copyFrom(0x30);
+    private final ImmutableByteSequence low2 = copyFrom(0x40);
 
-    final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(VLAN_HEADER_NAME, VID);
-    PiRangeFieldMatch piRangeFieldMatch1 = new PiRangeFieldMatch(piHeaderField, low1, high1);
-    PiRangeFieldMatch sameAsPiRangeFieldMatch1 = new PiRangeFieldMatch(piHeaderField, low1, high1);
-    PiRangeFieldMatch piRangeFieldMatch2 = new PiRangeFieldMatch(piHeaderField, low2, high2);
+    private final PiMatchFieldId piMatchField = PiMatchFieldId.of(VLAN_HEADER_NAME + DOT + VID);
+    private PiRangeFieldMatch piRangeFieldMatch1 = new PiRangeFieldMatch(piMatchField, low1, high1);
+    private PiRangeFieldMatch sameAsPiRangeFieldMatch1 = new PiRangeFieldMatch(piMatchField, low1, high1);
+    private PiRangeFieldMatch piRangeFieldMatch2 = new PiRangeFieldMatch(piMatchField, low2, high2);
 
     /**
      * Checks that the PiRangeFieldMatch class is immutable.
@@ -69,8 +71,8 @@
     public void testConstruction() {
         final ImmutableByteSequence high = copyFrom(0x50);
         final ImmutableByteSequence low = copyFrom(0x00);
-        final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(VLAN_HEADER_NAME, VID);
-        PiRangeFieldMatch piRangeFieldMatch = new PiRangeFieldMatch(piHeaderField, low, high);
+        final PiMatchFieldId piMatchField = PiMatchFieldId.of(VLAN_HEADER_NAME + DOT + VID);
+        PiRangeFieldMatch piRangeFieldMatch = new PiRangeFieldMatch(piMatchField, low, high);
         assertThat(piRangeFieldMatch, is(notNullValue()));
         assertThat(piRangeFieldMatch.lowValue(), is(low));
         assertThat(piRangeFieldMatch.highValue(), is(high));
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java
index 6caa63c..0b451e8 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableEntryTest.java
@@ -21,13 +21,19 @@
 import org.apache.commons.collections.CollectionUtils;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiTableId;
 
 import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.*;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DROP;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.IPV4_HEADER_NAME;
 
 /**
  * Unit tests for PiTableEntry class.
@@ -94,11 +100,11 @@
         long cookie = 0xfff0323;
         int priority = 100;
         double timeout = 1000;
-        PiHeaderFieldId piHeaderFieldId = PiHeaderFieldId.of(IPV4_HEADER_NAME, DST_ADDR);
-        PiFieldMatch piFieldMatch = new PiExactFieldMatch(piHeaderFieldId, ImmutableByteSequence.copyFrom(0x0a010101));
+        PiMatchFieldId piMatchFieldId = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + DST_ADDR);
+        PiFieldMatch piFieldMatch = new PiExactFieldMatch(piMatchFieldId, ImmutableByteSequence.copyFrom(0x0a010101));
         PiAction piAction = PiAction.builder().withId(PiActionId.of(DROP)).build();
-        final Map<PiHeaderFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
-        fieldMatches.put(piHeaderFieldId, piFieldMatch);
+        final Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
+        fieldMatches.put(piMatchFieldId, piFieldMatch);
         final PiTableEntry piTableEntry = PiTableEntry.builder()
                 .forTable(piTableId)
                 .withMatchKey(PiMatchKey.builder()
@@ -112,6 +118,8 @@
 
         assertThat(piTableEntry.table(), is(piTableId));
         assertThat(piTableEntry.cookie(), is(cookie));
+        assertThat("Priority must be set", piTableEntry.priority().isPresent());
+        assertThat("Timeout must be set", piTableEntry.timeout().isPresent());
         assertThat(piTableEntry.priority().get(), is(priority));
         assertThat(piTableEntry.timeout().get(), is(timeout));
         assertThat("Incorrect match param value",
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableIdTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableIdTest.java
index d343cd7..eb1de71 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableIdTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTableIdTest.java
@@ -18,6 +18,7 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiTableId;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
@@ -28,16 +29,12 @@
  * Unit tests for PiTableId class.
  */
 public class PiTableIdTest {
-    final String table10 = "table10";
-    final String table20 = "table20";
-    final PiTableId piTableId1 = PiTableId.of(table10);
-    final PiTableId sameAsPiTableId1 = PiTableId.of(table10);
-    final PiTableId piTableId2 = PiTableId.of(table20);
+    private final String table10 = "table10";
+    private final String table20 = "table20";
+    private final PiTableId piTableId1 = PiTableId.of(table10);
+    private final PiTableId sameAsPiTableId1 = PiTableId.of(table10);
+    private final PiTableId piTableId2 = PiTableId.of(table20);
 
-    final String tableScope = "local";
-    final PiTableId piTableIdWithScope1 = PiTableId.of(tableScope, table10);
-    final PiTableId sameAsPiTableIdWithScope1 = PiTableId.of(tableScope, table10);
-    final PiTableId piTableIdWithScope2 = PiTableId.of(tableScope, table20);
     /**
      * Checks that the PiTableId class is immutable.
      */
@@ -58,37 +55,11 @@
     }
 
     /**
-     * Checks the operation of equals(), hashCode() and toString() methods.
-     */
-    @Test
-    public void testEqualsWithScope() {
-        new EqualsTester()
-                .addEqualityGroup(piTableIdWithScope1, sameAsPiTableIdWithScope1)
-                .addEqualityGroup(piTableIdWithScope2)
-                .testEquals();
-    }
-
-    /**
      * Checks the construction of a PiTableId object.
      */
     @Test
     public void testConstruction() {
-        final String name = "table1";
-        final PiTableId piTableId = PiTableId.of(name);
-        assertThat(piTableId, is(notNullValue()));
-        assertThat(piTableId.name(), is(name));
-    }
-
-    /**
-     * Checks the construction of a PiTableId object.
-     */
-    @Test
-    public void testConstructionWithScope() {
-        final String name = "table1";
-        final String scope = "local";
-        final PiTableId piTableId = PiTableId.of(scope, name);
-        assertThat(piTableId, is(notNullValue()));
-        assertThat(piTableId.name(), is(name));
-        assertThat(piTableId.scope().get(), is(scope));
+        assertThat(piTableId1, is(notNullValue()));
+        assertThat(piTableId1.id(), is(table10));
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatchTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatchTest.java
index 2078496..f36ba6b 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatchTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiTernaryFieldMatchTest.java
@@ -19,6 +19,7 @@
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -26,6 +27,7 @@
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.DST_ADDR;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.IPV4_HEADER_NAME;
 
@@ -33,15 +35,15 @@
  * Unit tests for PiTernaryFieldMatch class.
  */
 public class PiTernaryFieldMatchTest {
-    final ImmutableByteSequence value1 = copyFrom(0x0a010101);
-    final ImmutableByteSequence mask1 = copyFrom(0x00ffffff);
-    final ImmutableByteSequence value2 = copyFrom(0x0a010102);
-    final ImmutableByteSequence mask2 = copyFrom(0x0000ffff);
+    private final ImmutableByteSequence value1 = copyFrom(0x0a010101);
+    private final ImmutableByteSequence mask1 = copyFrom(0x00ffffff);
+    private final ImmutableByteSequence value2 = copyFrom(0x0a010102);
+    private final ImmutableByteSequence mask2 = copyFrom(0x0000ffff);
 
-    final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(IPV4_HEADER_NAME, DST_ADDR);
-    PiTernaryFieldMatch piTernaryFieldMatch1 = new PiTernaryFieldMatch(piHeaderField, value1, mask1);
-    PiTernaryFieldMatch sameAsPiTernaryFieldMatch1 = new PiTernaryFieldMatch(piHeaderField, value1, mask1);
-    PiTernaryFieldMatch piTernaryFieldMatch2 = new PiTernaryFieldMatch(piHeaderField, value2, mask2);
+    private final PiMatchFieldId piMatchField = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + DST_ADDR);
+    private PiTernaryFieldMatch piTernaryFieldMatch1 = new PiTernaryFieldMatch(piMatchField, value1, mask1);
+    private PiTernaryFieldMatch sameAsPiTernaryFieldMatch1 = new PiTernaryFieldMatch(piMatchField, value1, mask1);
+    private PiTernaryFieldMatch piTernaryFieldMatch2 = new PiTernaryFieldMatch(piMatchField, value2, mask2);
 
     /**
      * Checks that the PiTernaryFieldMatch class is immutable.
@@ -69,8 +71,8 @@
     public void testConstruction() {
         final ImmutableByteSequence value = copyFrom(0x0a01010a);
         final ImmutableByteSequence mask = copyFrom(0x00ffffff);
-        final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(IPV4_HEADER_NAME, DST_ADDR);
-        PiTernaryFieldMatch piTernaryFieldMatch = new PiTernaryFieldMatch(piHeaderField, value, mask);
+        final PiMatchFieldId piMatchField = PiMatchFieldId.of(IPV4_HEADER_NAME + DOT + DST_ADDR);
+        PiTernaryFieldMatch piTernaryFieldMatch = new PiTernaryFieldMatch(piMatchField, value, mask);
         assertThat(piTernaryFieldMatch, is(notNullValue()));
         assertThat(piTernaryFieldMatch.value(), is(value));
         assertThat(piTernaryFieldMatch.mask(), is(mask));
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiValidFieldMatchTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiValidFieldMatchTest.java
index 6a32ed1..a62588a 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiValidFieldMatchTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiValidFieldMatchTest.java
@@ -18,12 +18,14 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DOT;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.VID;
 import static org.onosproject.net.pi.runtime.PiConstantsTest.VLAN_HEADER_NAME;
 
@@ -31,13 +33,12 @@
  * Unit tests for PiValidFieldMatch class.
  */
 public class PiValidFieldMatchTest {
-
-    final boolean isValid1 = true;
-    final boolean isValid2 = false;
-    final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(VLAN_HEADER_NAME, VID);
-    PiValidFieldMatch piValidFieldMatch1 = new PiValidFieldMatch(piHeaderField, isValid1);
-    PiValidFieldMatch sameAsPiValidFieldMatch1 = new PiValidFieldMatch(piHeaderField, isValid1);
-    PiValidFieldMatch piValidFieldMatch2 = new PiValidFieldMatch(piHeaderField, isValid2);
+    private final boolean isValid1 = true;
+    private final boolean isValid2 = false;
+    private final PiMatchFieldId piMatchField = PiMatchFieldId.of(VLAN_HEADER_NAME + DOT + VID);
+    private PiValidFieldMatch piValidFieldMatch1 = new PiValidFieldMatch(piMatchField, isValid1);
+    private PiValidFieldMatch sameAsPiValidFieldMatch1 = new PiValidFieldMatch(piMatchField, isValid1);
+    private PiValidFieldMatch piValidFieldMatch2 = new PiValidFieldMatch(piMatchField, isValid2);
 
     /**
      * Checks that the PiValidFieldMatch class is immutable.
@@ -63,11 +64,8 @@
      */
     @Test
     public void testConstruction() {
-        final boolean isValid = true;
-        final PiHeaderFieldId piHeaderField = PiHeaderFieldId.of(VLAN_HEADER_NAME, VID);
-        PiValidFieldMatch piTernaryFieldMatch = new PiValidFieldMatch(piHeaderField, isValid);
-        assertThat(piTernaryFieldMatch, is(notNullValue()));
-        assertThat(piTernaryFieldMatch.isValid(), is(isValid));
-        assertThat(piTernaryFieldMatch.type(), is(PiMatchType.VALID));
+        assertThat(piValidFieldMatch1, is(notNullValue()));
+        assertThat(piValidFieldMatch1.isValid(), is(isValid1));
+        assertThat(piValidFieldMatch1.type(), is(PiMatchType.VALID));
     }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
index 7a88366..13782c7 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/CodecManager.java
@@ -75,10 +75,10 @@
 import org.onosproject.net.intent.Constraint;
 import org.onosproject.net.intent.HostToHostIntent;
 import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.intent.PointToPointIntent;
 import org.onosproject.net.intent.SinglePointToMultiPointIntent;
 import org.onosproject.net.intent.util.IntentMiniSummary;
-import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.key.DeviceKey;
 import org.onosproject.net.mcast.McastRoute;
 import org.onosproject.net.meter.Band;
@@ -87,12 +87,9 @@
 import org.onosproject.net.packet.PacketRequest;
 import org.onosproject.net.pi.model.PiActionModel;
 import org.onosproject.net.pi.model.PiActionParamModel;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineModel;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
 import org.onosproject.net.pi.model.PiTableModel;
 import org.onosproject.net.region.Region;
 import org.onosproject.net.statistic.Load;
@@ -186,13 +183,10 @@
         registerCodec(TransportEndpointDescription.class, new TransportEndpointDescriptionCodec());
         registerCodec(PacketRequest.class, new PacketRequestCodec());
         registerCodec(PiActionModel.class, new PiActionModelCodec());
-        registerCodec(PiHeaderModel.class, new PiHeaderModelCodec());
         registerCodec(PiPipelineModel.class, new PiPipelineModelCodec());
         registerCodec(PiPipeconf.class, new PiPipeconfCodec());
         registerCodec(PiTableModel.class, new PiTableModelCodec());
-        registerCodec(PiTableMatchFieldModel.class, new PiTableMatchFieldModelCodec());
-        registerCodec(PiHeaderFieldTypeModel.class, new PiHeaderFieldTypeModelCodec());
-        registerCodec(PiHeaderTypeModel.class, new PiHeaderTypeModelCodec());
+        registerCodec(PiMatchFieldModel.class, new PiMatchFieldModelCodec());
         registerCodec(PiActionParamModel.class, new PiActionParamModelCodec());
         log.info("Started");
     }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
index da34c5e..37a173d 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/FlowRuleCodec.java
@@ -27,7 +27,7 @@
 import org.onosproject.net.flow.IndexTableId;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiTableId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.util.Tools.nullIsIllegal;
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
index 10cef43..dd7b336 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiActionModelCodec.java
@@ -33,7 +33,7 @@
     @Override
     public ObjectNode encode(PiActionModel action, CodecContext context) {
         ObjectNode result = context.mapper().createObjectNode();
-        result.put(NAME, action.name());
+        result.put(NAME, action.id().toString());
         ArrayNode params = result.putArray(PARAMS);
         action.params().forEach(param -> {
             ObjectNode paramData = context.encode(param, PiActionParamModel.class);
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
index ac553fe..17f797a 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiActionParamModelCodec.java
@@ -32,7 +32,7 @@
     @Override
     public ObjectNode encode(PiActionParamModel param, CodecContext context) {
         ObjectNode result = context.mapper().createObjectNode();
-        result.put(NAME, param.name());
+        result.put(NAME, param.id().toString());
         result.put(BIT_WIDTH, param.bitWidth());
         return result;
     }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java
deleted file mode 100644
index 7bcd134..0000000
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderModelCodec.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.codec.impl;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.onosproject.codec.CodecContext;
-import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
-
-/**
- * Codec for PiHeaderModel.
- */
-public class PiHeaderModelCodec extends JsonCodec<PiHeaderModel> {
-    private static final String NAME = "name";
-    private static final String TYPE = "type";
-    private static final String IS_META = "isMetadata";
-    private static final String INDEX = "index";
-
-    @Override
-    public ObjectNode encode(PiHeaderModel header, CodecContext context) {
-        ObjectNode result = context.mapper().createObjectNode();
-        ObjectNode headerTypeData = context.encode(header.type(), PiHeaderTypeModel.class);
-        result.put(NAME, header.name());
-        result.set(TYPE, headerTypeData);
-        result.put(IS_META, header.isMetadata());
-        result.put(INDEX, header.index());
-        return result;
-    }
-}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java
deleted file mode 100644
index 1065a94..0000000
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderTypeModelCodec.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.codec.impl;
-
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.onosproject.codec.CodecContext;
-import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
-
-/**
- * Codec for PiHeaderTypeModel.
- */
-public class PiHeaderTypeModelCodec extends JsonCodec<PiHeaderTypeModel> {
-    private static final String NAME = "name";
-    private static final String FIELDS = "fields";
-
-    @Override
-    public ObjectNode encode(PiHeaderTypeModel headerType, CodecContext context) {
-        ObjectNode result = context.mapper().createObjectNode();
-        result.put(NAME, headerType.name());
-        ArrayNode fields = result.putArray(FIELDS);
-
-        headerType.fields().forEach(field -> {
-            fields.add(context.encode(field, PiHeaderFieldTypeModel.class));
-        });
-
-        return result;
-    }
-}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiMatchFieldModelCodec.java
similarity index 65%
rename from core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java
rename to core/common/src/main/java/org/onosproject/codec/impl/PiMatchFieldModelCodec.java
index 09a1566..6d6dad4 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiHeaderFieldTypeModelCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiMatchFieldModelCodec.java
@@ -19,20 +19,19 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
 
 /**
- * Codec for PiHeaderFieldTypeModel.
+ * Codec for PiMatchFieldModel.
  */
-public class PiHeaderFieldTypeModelCodec extends JsonCodec<PiHeaderFieldTypeModel> {
-    private static final String NAME = "name";
-    private static final String BIT_WIDTH = "bitWidth";
-
+public class PiMatchFieldModelCodec extends JsonCodec<PiMatchFieldModel> {
+    private static final String MATCH_TYPE = "matchType";
+    private static final String FIELD = "field";
     @Override
-    public ObjectNode encode(PiHeaderFieldTypeModel headerFieldType, CodecContext context) {
+    public ObjectNode encode(PiMatchFieldModel matchFieldModel, CodecContext context) {
         ObjectNode result = context.mapper().createObjectNode();
-        result.put(NAME, headerFieldType.name());
-        result.put(BIT_WIDTH, headerFieldType.bitWidth());
+        result.put(MATCH_TYPE, matchFieldModel.matchType().toString());
+        result.put(FIELD, matchFieldModel.id().toString());
         return result;
     }
 }
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
index 6b42c8d..28a7010 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiPipelineModelCodec.java
@@ -21,7 +21,6 @@
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
 import org.onosproject.net.pi.model.PiActionModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
 import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.model.PiTableModel;
 
@@ -29,19 +28,17 @@
  * Codec for PiPipelineModel.
  */
 public class PiPipelineModelCodec extends JsonCodec<PiPipelineModel> {
-    private static final String HEADERS = "headers";
     private static final String ACTIONS = "actions";
     private static final String TABLES = "tables";
 
     @Override
     public ObjectNode encode(PiPipelineModel pipeline, CodecContext context) {
         ObjectNode result = context.mapper().createObjectNode();
-        ArrayNode headers = result.putArray(HEADERS);
-        pipeline.headers().stream()
-                .map(header -> context.encode(header, PiHeaderModel.class))
-                .forEach(headers::add);
         ArrayNode actions = result.putArray(ACTIONS);
-        pipeline.actions().stream()
+        pipeline.tables()
+                .stream()
+                .flatMap(piTableModel -> piTableModel.actions().stream())
+                .distinct()
                 .map(action -> context.encode(action, PiActionModel.class))
                 .forEach(actions::add);
         ArrayNode tables = result.putArray(TABLES);
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java
deleted file mode 100644
index a94bcba..0000000
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiTableMatchFieldModelCodec.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.codec.impl;
-
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import org.onosproject.codec.CodecContext;
-import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
-
-/**
- * Codec for PiTableMatchFieldModel.
- */
-public class PiTableMatchFieldModelCodec extends JsonCodec<PiTableMatchFieldModel> {
-    private static final String MATCH_TYPE = "matchType";
-    private static final String HEADER = "header";
-    private static final String FIELD = "field";
-    @Override
-    public ObjectNode encode(PiTableMatchFieldModel tableMatchField, CodecContext context) {
-        ObjectNode result = context.mapper().createObjectNode();
-        result.put(MATCH_TYPE, tableMatchField.matchType().toString());
-        PiHeaderModel header = tableMatchField.field().header();
-        PiHeaderFieldTypeModel field = tableMatchField.field().type();
-        ObjectNode headerData = context.encode(header, PiHeaderModel.class);
-        ObjectNode headerFieldData = context.encode(field, PiHeaderFieldTypeModel.class);
-        result.set(HEADER, headerData);
-        result.set(FIELD, headerFieldData);
-        return result;
-    }
-}
diff --git a/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java b/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
index 052e083..adf8bd7 100644
--- a/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
+++ b/core/common/src/main/java/org/onosproject/codec/impl/PiTableModelCodec.java
@@ -20,7 +20,7 @@
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.codec.JsonCodec;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
 import org.onosproject.net.pi.model.PiTableModel;
 
 /**
@@ -40,21 +40,21 @@
 
         ObjectNode result = context.mapper().createObjectNode();
 
-        result.put(NAME, table.name());
+        result.put(NAME, table.id().toString());
         result.put(MAX_SIZE, table.maxSize());
-        result.put(HAS_COUNTERS, table.hasCounters());
+        result.put(HAS_COUNTERS, table.counters().size() > 0);
         result.put(SUPPORT_AGING, table.supportsAging());
 
         ArrayNode matchFields = result.putArray(MATCH_FIELDS);
         table.matchFields().forEach(matchField -> {
             ObjectNode matchFieldData =
-                    context.encode(matchField, PiTableMatchFieldModel.class);
+                    context.encode(matchField, PiMatchFieldModel.class);
             matchFields.add(matchFieldData);
         });
 
         ArrayNode actions = result.putArray(ACTIONS);
         table.actions().forEach(action -> {
-            actions.add(action.name());
+            actions.add(action.id().toString());
         });
 
         return result;
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java b/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java
index 4e0fd16..20a2d0f 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/CriterionTranslatorHelper.java
@@ -80,10 +80,10 @@
 import org.onosproject.net.pi.impl.CriterionTranslators.UdpPortCriterionTranslator;
 import org.onosproject.net.pi.impl.CriterionTranslators.VlanIdCriterionTranslator;
 import org.onosproject.net.pi.impl.CriterionTranslators.VlanPcpCriterionTranslator;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 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.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 
@@ -140,14 +140,14 @@
     /**
      * Translates a given criterion instance to a PiFieldMatch with the given id, match type, and bit-width.
      *
-     * @param fieldId   PI header field identifier
+     * @param fieldId   PI match field identifier
      * @param criterion criterion
      * @param matchType match type
      * @param bitWidth  size of the field match in bits
      * @return a PI field match
      * @throws PiTranslationException if the criterion cannot be translated (see exception message)
      */
-    static PiFieldMatch translateCriterion(Criterion criterion, PiHeaderFieldId fieldId, PiMatchType matchType,
+    static PiFieldMatch translateCriterion(Criterion criterion, PiMatchFieldId fieldId, PiMatchType matchType,
                                            int bitWidth)
             throws PiTranslationException {
 
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
index 0a4ffbb..6f61c3b 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiFlowRuleTranslator.java
@@ -29,22 +29,22 @@
 import org.onosproject.net.flow.instructions.PiInstruction;
 import org.onosproject.net.pi.model.PiActionModel;
 import org.onosproject.net.pi.model.PiActionParamModel;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.model.PiTableModel;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
 import org.onosproject.net.pi.runtime.PiTableAction;
 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 org.slf4j.LoggerFactory;
@@ -128,7 +128,7 @@
                 tableEntryBuilder.withTimeout((double) rule.timeout());
             } else {
                 log.warn("Flow rule is temporary, but table '{}' doesn't support " +
-                                 "aging, translating to permanent.", tableModel.name());
+                                 "aging, translating to permanent.", tableModel.id());
             }
 
         }
@@ -159,7 +159,7 @@
 
     private static PiTableModel getTableModel(PiTableId piTableId, PiPipelineModel pipelineModel)
             throws PiTranslationException {
-        return pipelineModel.table(piTableId.toString())
+        return pipelineModel.table(piTableId)
                 .orElseThrow(() -> new PiTranslationException(format(
                         "Not such a table in pipeline model: %s", piTableId)));
     }
@@ -221,24 +221,24 @@
     private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
             throws PiTranslationException {
         // Table supports this action?
-        PiActionModel actionModel = table.action(piAction.id().name()).orElseThrow(
+        PiActionModel actionModel = table.action(piAction.id()).orElseThrow(
                 () -> new PiTranslationException(format("Not such action '%s' for table '%s'",
-                                                        piAction.id(), table.name())));
+                                                        piAction.id(), table.id())));
 
         // Is the number of runtime parameters correct?
         if (actionModel.params().size() != piAction.parameters().size()) {
             throw new PiTranslationException(format(
                     "Wrong number of runtime parameters for action '%s', expected %d but found %d",
-                    actionModel.name(), actionModel.params().size(), piAction.parameters().size()));
+                    actionModel.id(), actionModel.params().size(), piAction.parameters().size()));
         }
 
         // Forge a new action instance with well-sized parameters.
         // The same comment as in typeCheckFieldMatch() about duplicating field match instances applies here.
         PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
         for (PiActionParam param : piAction.parameters()) {
-            PiActionParamModel paramModel = actionModel.param(param.id().name())
+            PiActionParamModel paramModel = actionModel.param(param.id())
                     .orElseThrow(() -> new PiTranslationException(format(
-                            "Not such parameter '%s' for action '%s'", param.id(), actionModel.name())));
+                            "Not such parameter '%s' for action '%s'", param.id(), actionModel)));
             try {
                 newActionBuilder.withParameter(new PiActionParam(param.id(),
                                                                  fit(param.value(), paramModel.bitWidth())));
@@ -260,16 +260,16 @@
                                                                   TrafficSelector selector, PiTableModel tableModel)
             throws PiTranslationException {
 
-        Map<PiHeaderFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
+        Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
 
         // If present, find a PiCriterion and get its field matches as a map. Otherwise, use an empty map.
-        Map<PiHeaderFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
+        Map<PiMatchFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
                 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
                 .map(c -> (PiCriterion) c)
                 .findFirst()
                 .map(PiCriterion::fieldMatches)
                 .map(c -> {
-                    Map<PiHeaderFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
+                    Map<PiMatchFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
                     c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
                     return fieldMap;
                 })
@@ -277,31 +277,20 @@
 
         Set<Criterion> translatedCriteria = Sets.newHashSet();
         Set<Criterion> ignoredCriteria = Sets.newHashSet();
-        Set<PiHeaderFieldId> usedPiCriterionFields = Sets.newHashSet();
-        Set<PiHeaderFieldId> ignoredPiCriterionFields = Sets.newHashSet();
+        Set<PiMatchFieldId> usedPiCriterionFields = Sets.newHashSet();
+        Set<PiMatchFieldId> ignoredPiCriterionFields = Sets.newHashSet();
 
-        for (PiTableMatchFieldModel fieldModel : tableModel.matchFields()) {
+        for (PiMatchFieldModel fieldModel : tableModel.matchFields()) {
 
-            PiHeaderFieldId fieldId = PiHeaderFieldId.of(fieldModel.field().header().name(),
-                                                         fieldModel.field().type().name(),
-                                                         fieldModel.field().header().index());
+            PiMatchFieldId fieldId = fieldModel.id();
 
-            // FIXME: workaround until ONOS-7066 is resolved
-            if (fieldId.id().startsWith("scalars")) {
-                String newFieldId = fieldId.id()
-                        .replace("scalars.", "")
-                        .replace("_t.", ".");
-                String[] piecies = newFieldId.split("\\.");
-                fieldId = PiHeaderFieldId.of(piecies[0], piecies[1]);
-            }
-
-            int bitWidth = fieldModel.field().type().bitWidth();
+            int bitWidth = fieldModel.bitWidth();
             int fieldByteWidth = (int) Math.ceil((double) bitWidth / 8);
 
             Optional<Criterion.Type> criterionType =
                     interpreter == null
                             ? Optional.empty()
-                            : interpreter.mapPiHeaderFieldId(fieldId);
+                            : interpreter.mapPiMatchFieldId(fieldId);
 
             Criterion criterion = criterionType.map(selector::getCriterion).orElse(null);
 
@@ -378,7 +367,7 @@
         if (skippedCriteriaJoiner.length() > 0) {
             throw new PiTranslationException(format(
                     "The following criteria cannot be translated for table '%s': %s",
-                    tableModel.name(), skippedCriteriaJoiner.toString()));
+                    tableModel.id(), skippedCriteriaJoiner.toString()));
         }
 
         // Check if all fields found in PiCriterion have been used.
@@ -389,13 +378,13 @@
         if (skippedPiFieldsJoiner.length() > 0) {
             throw new PiTranslationException(format(
                     "The following PiCriterion field matches are not supported in table '%s': %s",
-                    tableModel.name(), skippedPiFieldsJoiner.toString()));
+                    tableModel.id(), skippedPiFieldsJoiner.toString()));
         }
 
         return fieldMatches.values();
     }
 
-    private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiTableMatchFieldModel fieldModel)
+    private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiMatchFieldModel fieldModel)
             throws PiTranslationException {
 
         // Check parameter type and size
@@ -405,7 +394,7 @@
                     fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
         }
 
-        int modelBitWidth = fieldModel.field().type().bitWidth();
+        int modelBitWidth = fieldModel.bitWidth();
 
         /*
         Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java
index 7b798c0..a682409 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiGroupTranslator.java
@@ -19,6 +19,7 @@
 import org.onosproject.net.Device;
 import org.onosproject.net.group.Group;
 import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.pi.model.PiActionGroupType;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiAction;
@@ -64,7 +65,7 @@
 
         switch (group.type()) {
             case SELECT:
-                piActionGroupBuilder.withType(PiActionGroup.Type.SELECT);
+                piActionGroupBuilder.withType(PiActionGroupType.SELECT);
                 break;
             default:
                 throw new PiTranslationException(format("Group type %s not supported", group.type()));
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java
index aab8b40..f3270f4 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiUtils.java
@@ -21,7 +21,7 @@
 import org.onosproject.net.flow.TableId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiTranslationService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiCriterionTranslatorsTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiCriterionTranslatorsTest.java
index 156be3a..1938c05 100644
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/PiCriterionTranslatorsTest.java
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiCriterionTranslatorsTest.java
@@ -57,8 +57,8 @@
 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.PiMatchFieldId;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 
@@ -67,7 +67,9 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.onosproject.net.pi.impl.CriterionTranslatorHelper.translateCriterion;
-import static org.onosproject.net.pi.model.PiMatchType.*;
+import static org.onosproject.net.pi.model.PiMatchType.EXACT;
+import static org.onosproject.net.pi.model.PiMatchType.LPM;
+import static org.onosproject.net.pi.model.PiMatchType.TERNARY;
 
 /**
  * Tests for CriterionTranslators.
@@ -75,7 +77,7 @@
 public class PiCriterionTranslatorsTest {
 
     private Random random = new Random();
-    private final PiHeaderFieldId fieldId = PiHeaderFieldId.of("foo", "bar");
+    private final PiMatchFieldId fieldId = PiMatchFieldId.of("foo.bar");
 
     @Test
     public void testEthCriterion() throws Exception {
@@ -480,4 +482,4 @@
 
         assertThat(exactMatch.value().asReadOnlyBuffer().get(), is(criterion.ipEcn()));
     }
-}
\ No newline at end of file
+}
diff --git a/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java b/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java
index e7e3e23..d13def7 100644
--- a/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java
+++ b/core/net/src/test/java/org/onosproject/net/pi/impl/PiTranslatorServiceTest.java
@@ -43,6 +43,7 @@
 import org.onosproject.net.group.GroupBucket;
 import org.onosproject.net.group.GroupBuckets;
 import org.onosproject.net.group.GroupDescription;
+import org.onosproject.net.pi.model.PiActionGroupType;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionGroup;
@@ -169,13 +170,13 @@
                 .addEqualityGroup(entry1, entry2)
                 .testEquals();
 
-        int numMatchParams = pipeconf.pipelineModel().table(TBL_TABLE0_ID.id()).get().matchFields().size();
+        int numMatchParams = pipeconf.pipelineModel().table(TBL_TABLE0_ID).get().matchFields().size();
         // parse values stored in entry1
         PiTernaryFieldMatch inPortParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_IN_PORT_ID).get();
         PiTernaryFieldMatch ethDstParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_DST_ID).get();
         PiTernaryFieldMatch ethSrcParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_SRC_ID).get();
         PiTernaryFieldMatch ethTypeParam = (PiTernaryFieldMatch) entry1.matchKey().fieldMatch(HDR_ETH_TYPE_ID).get();
-        Optional<Double> expectedTimeout = pipeconf.pipelineModel().table(TBL_TABLE0_ID.id()).get().supportsAging()
+        Optional<Double> expectedTimeout = pipeconf.pipelineModel().table(TBL_TABLE0_ID).get().supportsAging()
                 ? Optional.of((double) rule1.timeout()) : Optional.empty();
 
         // check that the number of parameters in the entry is the same as the number of table keys
@@ -245,7 +246,7 @@
         assertThat("Group ID must be equal",
                    piGroup1.id().id(), is(equalTo(GROUP_ID.id())));
         assertThat("Group type must be SELECT",
-                   piGroup1.type(), is(equalTo(PiActionGroup.Type.SELECT)));
+                   piGroup1.type(), is(equalTo(PiActionGroupType.SELECT)));
         assertThat("Action profile ID must be equal",
                    piGroup1.actionProfileId(), is(equalTo(ACT_PRF_WCMP_SELECTOR_ID)));
 
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 6c4ad07..793b05b 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -202,31 +202,40 @@
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.DefaultPacketRequest;
 import org.onosproject.net.packet.PacketPriority;
+import org.onosproject.net.pi.model.PiActionGroupType;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterType;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiMatchType;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiPacketOperationType;
 import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.model.PiTableType;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiControlMetadata;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiGroupKey;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
-import org.onosproject.net.pi.runtime.PiPacketMetadata;
-import org.onosproject.net.pi.runtime.PiPacketMetadataId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiPipeconfConfig;
 import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
 import org.onosproject.net.pi.runtime.PiTableAction;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import org.onosproject.net.pi.runtime.PiValidFieldMatch;
 import org.onosproject.net.provider.ProviderId;
@@ -601,34 +610,43 @@
             .register(DomainConstraint.class)
             .register(
                     // PI model
+                    PiActionGroupType.class,
+                    PiActionId.class,
+                    PiActionParamId.class,
+                    PiActionProfileId.class,
+                    PiControlMetadataId.class,
+                    PiCounterId.class,
+                    PiCounterType.class,
+                    PiMatchFieldId.class,
                     PiMatchType.class,
+                    PiMeterId.class,
+                    PiMeterType.class,
+                    PiPacketOperationType.class,
                     PiPipeconfId.class,
+                    PiTableId.class,
+                    PiTableType.class,
                     // PI Runtime
                     PiAction.class,
                     PiActionGroup.class,
                     PiActionGroupId.class,
                     PiActionGroupMember.class,
                     PiActionGroupMemberId.class,
-                    PiActionId.class,
                     PiActionParam.class,
-                    PiActionParamId.class,
+                    PiControlMetadata.class,
+                    PiCounterCellData.class,
+                    PiCounterCellId.class,
                     PiExactFieldMatch.class,
                     PiFieldMatch.class,
                     PiGroupKey.class,
-                    PiHeaderFieldId.class,
                     PiLpmFieldMatch.class,
                     PiMatchKey.class,
-                    PiPacketMetadata.class,
-                    PiPacketMetadataId.class,
                     PiPacketOperation.class,
                     PiPipeconfConfig.class,
                     PiRangeFieldMatch.class,
                     PiTableAction.class,
                     PiTableEntry.class,
-                    PiTableId.class,
                     PiTernaryFieldMatch.class,
                     PiValidFieldMatch.class,
-                    PiActionProfileId.class,
                     // Other
                     PiCriterion.class,
                     PiInstruction.class
diff --git a/drivers/barefoot/BUCK b/drivers/barefoot/BUCK
index f38ad9f..6dfe7ba 100644
--- a/drivers/barefoot/BUCK
+++ b/drivers/barefoot/BUCK
@@ -3,7 +3,6 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
     '//lib:minimal-json',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
     '//drivers/p4runtime:onos-drivers-p4runtime',
     '//incubator/grpc-dependencies:grpc-core-repkg-' + GRPC_VER,
@@ -12,7 +11,6 @@
 
 BUNDLES = [
     ':onos-drivers-barefoot',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
 ]
 
 osgi_jar(
diff --git a/drivers/barefoot/src/main/resources/barefoot-drivers.xml b/drivers/barefoot/src/main/resources/barefoot-drivers.xml
index ee56455..4424e5b 100644
--- a/drivers/barefoot/src/main/resources/barefoot-drivers.xml
+++ b/drivers/barefoot/src/main/resources/barefoot-drivers.xml
@@ -16,7 +16,7 @@
   -->
 <drivers>
     <driver name="tofino" manufacturer="Barefoot Networks" hwVersion="1.0" swVersion="1.0" extends="p4runtime">
-        <behaviour api="org.onosproject.net.pi.model.PiPipelineProgrammable"
+        <behaviour api="org.onosproject.net.behaviour.PiPipelineProgrammable"
                    impl="org.onosproject.drivers.barefoot.TofinoPipelineProgrammable"/>
     </driver>
 </drivers>
diff --git a/drivers/bmv2/BUCK b/drivers/bmv2/BUCK
index ed32104..d79c85f 100644
--- a/drivers/bmv2/BUCK
+++ b/drivers/bmv2/BUCK
@@ -4,7 +4,7 @@
     '//lib:CORE_DEPS',
     '//lib:minimal-json',
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
+    '//protocols/p4runtime/model:onos-protocols-p4runtime-model',
     '//drivers/p4runtime:onos-drivers-p4runtime',
     '//incubator/grpc-dependencies:grpc-core-repkg-' + GRPC_VER,
     '//lib:grpc-netty-' + GRPC_VER,
@@ -13,7 +13,6 @@
 
 BUNDLES = [
     ':onos-drivers-bmv2',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
 ]
 
 osgi_jar(
@@ -31,4 +30,4 @@
         'org.onosproject.drivers.p4runtime',
         'org.onosproject.pipelines.basic',
     ],
-)
\ No newline at end of file
+)
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DriversLoader.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DriversLoader.java
index 8ee87d4..51a5432 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DriversLoader.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2DriversLoader.java
@@ -20,7 +20,7 @@
 import org.onosproject.net.driver.AbstractDriverLoader;
 
 /**
- * Loader for P4Runtime device drivers.
+ * Loader for BMv2 device drivers.
  */
 @Component(immediate = true)
 public class Bmv2DriversLoader extends AbstractDriverLoader {
diff --git a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java
index bf7061f..b8d2f27 100644
--- a/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java
+++ b/drivers/bmv2/src/main/java/org/onosproject/drivers/bmv2/Bmv2PipelineProgrammable.java
@@ -18,6 +18,7 @@
 
 import org.apache.commons.io.IOUtils;
 import org.onosproject.drivers.p4runtime.AbstractP4RuntimePipelineProgrammable;
+import org.onosproject.net.behaviour.PiPipelineProgrammable;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.pipelines.basic.PipeconfLoader;
 
@@ -30,7 +31,8 @@
 /**
  * Implementation of the PiPipelineProgrammable behavior for BMv2.
  */
-public class Bmv2PipelineProgrammable extends AbstractP4RuntimePipelineProgrammable {
+public class Bmv2PipelineProgrammable extends AbstractP4RuntimePipelineProgrammable
+        implements PiPipelineProgrammable {
 
     @Override
     public ByteBuffer createDeviceDataBuffer(PiPipeconf pipeconf) {
diff --git a/drivers/bmv2/src/main/resources/bmv2-drivers.xml b/drivers/bmv2/src/main/resources/bmv2-drivers.xml
index 97972b3..22bf067 100644
--- a/drivers/bmv2/src/main/resources/bmv2-drivers.xml
+++ b/drivers/bmv2/src/main/resources/bmv2-drivers.xml
@@ -16,7 +16,7 @@
   -->
 <drivers>
     <driver name="bmv2" manufacturer="p4.org" hwVersion="master" swVersion="master" extends="p4runtime">
-        <behaviour api="org.onosproject.net.pi.model.PiPipelineProgrammable"
+        <behaviour api="org.onosproject.net.behaviour.PiPipelineProgrammable"
                    impl="org.onosproject.drivers.bmv2.Bmv2PipelineProgrammable"/>
     </driver>
 </drivers>
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java
index 911c5ed..6a6c2e9 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/AbstractP4RuntimePipelineProgrammable.java
@@ -20,7 +20,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
 import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.model.PiPipelineProgrammable;
+import org.onosproject.net.behaviour.PiPipelineProgrammable;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.api.P4RuntimeController;
 import org.slf4j.Logger;
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index c9fe49d83..81626fe 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -24,16 +24,15 @@
 import org.onosproject.net.flow.FlowEntry;
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.model.PiTableModel;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiDirectCounterCellId;
-import org.onosproject.net.pi.runtime.PiTranslationService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.runtime.PiTranslationService;
 import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
 import org.onosproject.p4runtime.api.P4RuntimeFlowRuleWrapper;
 import org.onosproject.p4runtime.api.P4RuntimeTableEntryReference;
@@ -53,7 +52,9 @@
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
 import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
 import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
-import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.*;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
+import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
 
 /**
  * Implementation of the flow rule programmable behaviour for P4Runtime.
@@ -132,7 +133,7 @@
 
         for (PiTableModel tableModel : pipelineModel.tables()) {
 
-            PiTableId piTableId = PiTableId.of(tableModel.name());
+            PiTableId piTableId = tableModel.id();
 
             // Only dump tables that are exposed by the interpreter.
             // The reason is that some P4 targets (e.g. BMv2's simple_switch) use more table than those defined in the
@@ -162,12 +163,12 @@
                         cellDatas = client.readAllCounterCells(Collections.singleton(piCounterId), pipeconf).get();
                     } else {
                         Set<PiCounterCellId> cellIds = installedEntries.stream()
-                                .map(entry -> PiDirectCounterCellId.of(piCounterId, entry))
+                                .map(entry -> PiCounterCellId.ofDirect(piCounterId, entry))
                                 .collect(Collectors.toSet());
                         cellDatas = client.readCounterCells(cellIds, pipeconf).get();
                     }
                     counterCellMap = cellDatas.stream()
-                            .collect(Collectors.toMap(c -> ((PiDirectCounterCellId) c.cellId()).tableEntry(), c -> c));
+                            .collect(Collectors.toMap(c -> (c.cellId()).tableEntry(), c -> c));
                 } else {
                     counterCellMap = Collections.emptyMap();
                 }
@@ -339,4 +340,4 @@
     enum Operation {
         APPLY, REMOVE
     }
-}
\ No newline at end of file
+}
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
index b9f9766..1c4d5dc 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
@@ -27,9 +27,9 @@
 import org.onosproject.net.group.GroupOperations;
 import org.onosproject.net.group.GroupProgrammable;
 import org.onosproject.net.group.GroupStore;
+import org.onosproject.net.pi.model.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiTranslationService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.api.P4RuntimeGroupReference;
diff --git a/incubator/bmv2/model/BUCK b/incubator/bmv2/model/BUCK
deleted file mode 100644
index 53bfb14..0000000
--- a/incubator/bmv2/model/BUCK
+++ /dev/null
@@ -1,8 +0,0 @@
-COMPILE_DEPS = [
-  '//lib:CORE_DEPS',
-  '//lib:minimal-json',
-]
-
-osgi_jar_with_tests (
-  deps = COMPILE_DEPS,
-)
diff --git a/incubator/bmv2/model/pom.xml b/incubator/bmv2/model/pom.xml
deleted file mode 100644
index 7b08fbd..0000000
--- a/incubator/bmv2/model/pom.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Copyright 2016-present Open Networking Foundation
-  ~
-  ~ 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.
-  -->
-
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>onos-incubator-bmv2</artifactId>
-        <groupId>org.onosproject</groupId>
-        <version>1.12.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <packaging>bundle</packaging>
-    <artifactId>onos-incubator-bmv2-model</artifactId>
-</project>
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2ActionModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2ActionModel.java
deleted file mode 100644
index 3359236..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2ActionModel.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import org.onosproject.net.pi.model.PiActionModel;
-import org.onosproject.net.pi.model.PiActionParamModel;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 action model.
- */
-@Beta
-public final class Bmv2ActionModel implements PiActionModel {
-    private final String name;
-    private final int id;
-    private final ImmutableMap<String, PiActionParamModel> params;
-
-    /**
-     * Builds BMv2 action model with given information.
-     *
-     * @param name the model name
-     * @param id the model id
-     * @param params the action parameters
-     */
-    public Bmv2ActionModel(String name, int id, List<Bmv2ActionParamModel> params) {
-        checkNotNull(name, "Model name can't be null");
-        checkNotNull(params, "Action parameters can't be null");
-
-        this.name = name;
-        this.id = id;
-        ImmutableMap.Builder<String, PiActionParamModel> mapBuilder = ImmutableMap.builder();
-        params.forEach(param -> mapBuilder.put(param.name(), param));
-        this.params = mapBuilder.build();
-    }
-
-    /**
-     * Gets id from this action model.
-     *
-     * @return the model id
-     */
-    public int id() {
-        return id;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    @Override
-    public Optional<PiActionParamModel> param(String name) {
-        checkNotNull(name, "Parameter name can't be null");
-        return Optional.ofNullable(params.get(name));
-    }
-
-    @Override
-    public List<PiActionParamModel> params() {
-        return (ImmutableList<PiActionParamModel>) params.values();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name, params);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof Bmv2ActionModel)) {
-            return false;
-        }
-        Bmv2ActionModel that = (Bmv2ActionModel) obj;
-        return Objects.equals(this.name, that.name) &&
-                Objects.equals(this.params, that.params);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("name", name)
-                .add("params", params)
-                .toString();
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2ActionParamModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2ActionParamModel.java
deleted file mode 100644
index 69cf186..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2ActionParamModel.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
-import org.onosproject.net.pi.model.PiActionParamModel;
-
-import java.util.Objects;
-
-import static com.google.common.base.Preconditions.*;
-
-/**
- * BMv2 action parameter model.
- */
-@Beta
-public final class Bmv2ActionParamModel implements PiActionParamModel {
-    private final String name;
-    private final int bitWidth;
-
-    /**
-     * Builds a BMv2 action parameter model with given name and bit width.
-     *
-     * @param name the name
-     * @param bitWidth the bit width
-     */
-    public Bmv2ActionParamModel(String name, int bitWidth) {
-        checkNotNull(name, "Parameter name can't be null");
-        checkArgument(bitWidth > 0, "Bit width should be a non-zero positive integer");
-        this.name = name;
-        this.bitWidth = bitWidth;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    @Override
-    public int bitWidth() {
-        return bitWidth;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name, bitWidth);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof Bmv2ActionParamModel)) {
-            return false;
-        }
-        Bmv2ActionParamModel that = (Bmv2ActionParamModel) obj;
-        return Objects.equals(this.name, that.name) &&
-                this.bitWidth == that.bitWidth;
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("name", name)
-                .add("bitWidth", bitWidth)
-                .toString();
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderFieldModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderFieldModel.java
deleted file mode 100644
index 9f8010d..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderFieldModel.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Objects;
-import org.onosproject.net.pi.model.PiHeaderFieldModel;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 header field model.
- */
-@Beta
-public final class Bmv2HeaderFieldModel implements PiHeaderFieldModel {
-    private final Bmv2HeaderModel header;
-    private final Bmv2HeaderFieldTypeModel type;
-
-    /**
-     * Builds a BMv2 header field model with given BMv2 header model and header field type model.
-     *
-     * @param header the header model
-     * @param type the header field type model
-     */
-    public Bmv2HeaderFieldModel(Bmv2HeaderModel header, Bmv2HeaderFieldTypeModel type) {
-        checkNotNull(header, "Header can't be null");
-        checkNotNull(type, "Type can't be null");
-        this.header = header;
-        this.type = type;
-    }
-
-    @Override
-    public PiHeaderModel header() {
-        return header;
-    }
-
-    @Override
-    public PiHeaderFieldTypeModel type() {
-        return type;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(header, type);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof Bmv2HeaderFieldModel)) {
-            return false;
-        }
-        Bmv2HeaderFieldModel that = (Bmv2HeaderFieldModel) obj;
-        return Objects.equal(this.header, that.header) &&
-                Objects.equal(this.type, that.type);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("header", header)
-                .add("type", type)
-                .toString();
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderFieldTypeModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderFieldTypeModel.java
deleted file mode 100644
index a8a9cad..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderFieldTypeModel.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Objects;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 header field type model.
- */
-@Beta
-public final class Bmv2HeaderFieldTypeModel implements PiHeaderFieldTypeModel {
-    private final String name;
-    private final int bitWidth;
-    private final boolean signed;
-
-    /**
-     * Builds a BMv2 header field type model with given information.
-     *
-     * @param name the name of field type
-     * @param bitWidth the bit width of field type
-     * @param signed header type field is signed or not
-     */
-    public Bmv2HeaderFieldTypeModel(String name, int bitWidth, boolean signed) {
-        checkNotNull(name, "Header field type name can't be null");
-        checkArgument(bitWidth > 0, "Bit width should be non-zero positive integer");
-        this.name = name;
-        this.bitWidth = bitWidth;
-        this.signed = signed;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    @Override
-    public int bitWidth() {
-        return bitWidth;
-    }
-
-    /**
-     * Determine whether the header type field is signed or not.
-     *
-     * @return true if it is signed; otherwise false
-     */
-    public boolean signed() {
-        return signed;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(name, bitWidth);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        final Bmv2HeaderFieldTypeModel other = (Bmv2HeaderFieldTypeModel) obj;
-        return Objects.equal(this.name, other.name) &&
-                Objects.equal(this.bitWidth, other.bitWidth) &&
-                Objects.equal(this.signed, other.signed);
-    }
-
-    @Override
-    public String toString() {
-        return toStringHelper(this)
-                .add("name", name)
-                .add("bitWidth", bitWidth)
-                .toString();
-    }
-
-
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderModel.java
deleted file mode 100644
index 27732301..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderModel.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Objects;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-import static com.google.common.base.Preconditions.*;
-
-/**
- * BMv2 header model.
- */
-@Beta
-public final class Bmv2HeaderModel implements PiHeaderModel {
-    private final String name;
-    private final int id;
-    private final int index;
-    private final Bmv2HeaderTypeModel type;
-    private final boolean isMetadata;
-
-    /**
-     * Builds a new BMv2 header model with given information.
-     *
-     * @param name the name of this header mdoel
-     * @param id the id of this header model
-     * @param index the header index
-     * @param type the type of this header model
-     * @param metadata if the header is metadata
-     */
-    public Bmv2HeaderModel(String name, int id, int index, Bmv2HeaderTypeModel type, boolean metadata) {
-        checkNotNull(name, "Model name can't be null.");
-        checkArgument(index >= 0, "Index should be a positive integer");
-        checkNotNull(type, "Header type can't be null.");
-        this.name = name;
-        this.id = id;
-        this.index = index;
-        this.type = type;
-        this.isMetadata = metadata;
-    }
-
-    /**
-     * Gets the name of this header model.
-     *
-     * @return name of this model
-     */
-    @Override
-    public String name() {
-        return name;
-    }
-
-    /**
-     * Gets the id of this header model.
-     *
-     * @return if of this header model
-     */
-    public int id() {
-        return id;
-    }
-
-    @Override
-    public int index() {
-        return index;
-    }
-
-    @Override
-    public PiHeaderTypeModel type() {
-        return type;
-    }
-
-    @Override
-    public boolean isMetadata() {
-        return isMetadata;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(id, type, isMetadata);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        final Bmv2HeaderModel other = (Bmv2HeaderModel) obj;
-        return Objects.equal(this.id, other.id)
-                && Objects.equal(this.type, other.type)
-                && Objects.equal(this.isMetadata, other.isMetadata);
-    }
-
-    @Override
-    public String toString() {
-        return toStringHelper(this)
-                .add("index", id)
-                .add("type", type)
-                .add("isMetadata", isMetadata)
-                .toString();
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderTypeModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderTypeModel.java
deleted file mode 100644
index 4238c92..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2HeaderTypeModel.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import org.onosproject.net.pi.model.PiHeaderFieldTypeModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 header type model.
- */
-@Beta
-public final class Bmv2HeaderTypeModel implements PiHeaderTypeModel {
-    private final String name;
-    private final int id;
-    private final ImmutableMap<String, PiHeaderFieldTypeModel> fields;
-
-    /**
-     * Builds a BMv2 header type model by given information.
-     *
-     * @param name the name
-     * @param id the id
-     * @param fields the fields
-     */
-    public Bmv2HeaderTypeModel(String name,
-                               int id,
-                               List<Bmv2HeaderFieldTypeModel> fields) {
-        checkNotNull(name, "Type name can't be null");
-        checkNotNull(fields, "Fields can't be null");
-        this.name = name;
-        this.id = id;
-        ImmutableMap.Builder<String, PiHeaderFieldTypeModel> mapBuilder = ImmutableMap.builder();
-        fields.forEach(field -> mapBuilder.put(field.name(), field));
-        this.fields = mapBuilder.build();
-    }
-
-    /**
-     * Gets id of this header type model.
-     *
-     * @return the id
-     */
-    public int id() {
-        return id;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    @Override
-    public Optional<PiHeaderFieldTypeModel> field(String fieldName) {
-        return Optional.ofNullable(fields.get(fieldName));
-    }
-
-    @Override
-    public List<PiHeaderFieldTypeModel> fields() {
-        return (ImmutableList<PiHeaderFieldTypeModel>) fields.values();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name, fields);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        final Bmv2HeaderTypeModel other = (Bmv2HeaderTypeModel) obj;
-        return Objects.equals(this.name, other.name)
-                && Objects.equals(this.fields, other.fields);
-    }
-
-    @Override
-    public String toString() {
-        return toStringHelper(this)
-                .add("name", name)
-                .add("fields", fields)
-                .toString();
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2Pipeconf.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2Pipeconf.java
deleted file mode 100644
index 5e5f50d..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2Pipeconf.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import org.onosproject.net.driver.Behaviour;
-import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.model.PiPipeconfId;
-import org.onosproject.net.pi.model.PiPipelineModel;
-
-import java.io.InputStream;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 pipeline configuration (pipeconf).
- */
-public final class Bmv2Pipeconf implements PiPipeconf {
-    private final PiPipeconfId id;
-    private final Bmv2PipelineModel pipelineModel;
-    private final Set<Class<? extends Behaviour>> behaviours;
-    private final Map<ExtensionType, InputStream> extensions;
-
-    /**
-     * Builds a new BMv2 pipeline configuration (pipeconf) by given information.
-     *
-     * @param id the pipeconf id
-     * @param pipelineModel the pipeline model
-     * @param behaviours the behaviors of the pipeline
-     * @param extensions the extensions of the pipeline
-     */
-    public Bmv2Pipeconf(PiPipeconfId id,
-                        Bmv2PipelineModel pipelineModel,
-                        Set<Class<? extends Behaviour>> behaviours,
-                        Map<ExtensionType, InputStream> extensions) {
-        checkNotNull(id, "Pipeconf Id can't be null");
-        checkNotNull(pipelineModel, "Pipeline model can't be null");
-
-        this.id = id;
-        this.pipelineModel = pipelineModel;
-        this.behaviours = behaviours == null ? ImmutableSet.of() : behaviours;
-        this.extensions = extensions == null ? Maps.newHashMap() : extensions;
-    }
-
-    @Override
-    public PiPipeconfId id() {
-        return id;
-    }
-
-    @Override
-    public PiPipelineModel pipelineModel() {
-        return pipelineModel;
-    }
-
-    @Override
-    public Collection<Class<? extends Behaviour>> behaviours() {
-        return behaviours;
-    }
-
-    @Override
-    public Optional<Class<? extends Behaviour>> implementation(Class<? extends Behaviour> behaviour) {
-        return behaviours.stream()
-                .filter(behaviour::isAssignableFrom)
-                .findAny();
-    }
-
-    @Override
-    public boolean hasBehaviour(Class<? extends Behaviour> behaviourClass) {
-        return behaviours.stream()
-                .anyMatch(behaviourClass::isAssignableFrom);
-    }
-
-    @Override
-    public Optional<InputStream> extension(ExtensionType type) {
-        return Optional.ofNullable(extensions.get(type));
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
deleted file mode 100644
index b19ef61..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModel.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import org.onosproject.net.pi.model.PiActionModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
-import org.onosproject.net.pi.model.PiPipelineModel;
-import org.onosproject.net.pi.model.PiTableModel;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 pipeline model.
- */
-@Beta
-public class Bmv2PipelineModel implements PiPipelineModel {
-    private final Map<String, PiHeaderTypeModel> headerTypeModels;
-    private final Map<String, PiHeaderModel> headerModels;
-    private final Map<String, PiActionModel> actionModels;
-    private final Map<String, PiTableModel> tableModels;
-
-    /**
-     * Constructs a BMv2 pipeline model by given information.
-     *
-     * @param headerTypeModels the header type models for this pipeline model
-     * @param headerModels the header models for this pipeline model
-     * @param actionModels the action models for this pipeline model
-     * @param tableModels the table models for this pipeline model
-     */
-    Bmv2PipelineModel(List<Bmv2HeaderTypeModel> headerTypeModels,
-                      List<Bmv2HeaderModel> headerModels,
-                      List<Bmv2ActionModel> actionModels,
-                      List<Bmv2TableModel> tableModels) {
-        checkNotNull(headerTypeModels, "Header type models can't be null");
-        checkNotNull(headerModels, "Header models can't be null");
-        checkNotNull(actionModels, "Action models can't be null");
-        checkNotNull(tableModels, "Table models can't be null");
-
-        Map<String, PiHeaderTypeModel> headerTypeModelMap = Maps.newHashMap();
-        headerTypeModels.stream()
-                .filter(Objects::nonNull)
-                .forEach(htm -> headerTypeModelMap.put(htm.name(), htm));
-        this.headerTypeModels = ImmutableMap.copyOf(headerTypeModelMap);
-
-        Map<String, PiHeaderModel> headerModelMap = Maps.newHashMap();
-        headerModels.stream()
-                .filter(Objects::nonNull)
-                .forEach(hm -> headerModelMap.put(hm.name(), hm));
-        this.headerModels = ImmutableMap.copyOf(headerModelMap);
-
-        Map<String, PiActionModel> actionModelMap = Maps.newHashMap();
-        actionModels.stream()
-                .filter(Objects::nonNull)
-                .forEach(am -> actionModelMap.put(am.name(), am));
-        this.actionModels = ImmutableMap.copyOf(actionModelMap);
-
-        Map<String, PiTableModel> tableModelMap = Maps.newHashMap();
-        tableModels.stream()
-                .filter(Objects::nonNull)
-                .forEach(tm -> tableModelMap.put(tm.name(), tm));
-        this.tableModels = ImmutableMap.copyOf(tableModelMap);
-    }
-
-    @Override
-    public Optional<PiHeaderTypeModel> headerType(String name) {
-        return Optional.ofNullable(headerTypeModels.get(name));
-    }
-
-    @Override
-    public Collection<PiHeaderTypeModel> headerTypes() {
-        return headerTypeModels.values();
-    }
-
-    @Override
-    public Optional<PiHeaderModel> header(String name) {
-        return Optional.ofNullable(headerModels.get(name));
-    }
-
-    @Override
-    public Collection<PiHeaderModel> headers() {
-        return headerModels.values();
-    }
-
-    @Override
-    public Optional<PiActionModel> action(String name) {
-        return Optional.ofNullable(actionModels.get(name));
-    }
-
-    @Override
-    public Collection<PiActionModel> actions() {
-        return actionModels.values();
-    }
-
-    @Override
-    public Optional<PiTableModel> table(String name) {
-        return Optional.ofNullable(tableModels.get(name));
-    }
-
-    @Override
-    public Collection<PiTableModel> tables() {
-        return tableModels.values();
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(headerTypeModels, headerModels, actionModels, tableModels);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof Bmv2PipelineModel)) {
-            return false;
-        }
-        Bmv2PipelineModel that = (Bmv2PipelineModel) obj;
-        return Objects.equals(this.headerTypeModels, that.headerTypeModels) &&
-                Objects.equals(this.headerModels, that.headerModels) &&
-                Objects.equals(this.actionModels, that.actionModels) &&
-                Objects.equals(this.tableModels, that.tableModels);
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java
deleted file mode 100644
index 458fa9e..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2PipelineModelParser.java
+++ /dev/null
@@ -1,350 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.eclipsesource.json.Json;
-import com.eclipsesource.json.JsonArray;
-import com.eclipsesource.json.JsonObject;
-import com.eclipsesource.json.JsonValue;
-import com.google.common.annotations.Beta;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import org.onosproject.net.pi.model.PiMatchType;
-import org.slf4j.Logger;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static org.slf4j.LoggerFactory.getLogger;
-
-/**
- * BMv2 pipeline model parser.
- *
- * @see <a href="https://github.com/p4lang/behavioral-model/blob/master/docs/JSON_format.md"> BMv2 JSON
- * specification</a>
- */
-@Beta
-public final class Bmv2PipelineModelParser {
-    private static final Logger log = getLogger(Bmv2PipelineModelParser.class);
-
-    // General fields and values
-    private static final String NAME = "name";
-    private static final String ID = "id";
-    private static final int NO_ID = Integer.MIN_VALUE;
-
-    // Hide default parser
-    private Bmv2PipelineModelParser() {
-    }
-
-    /**
-     * Parse the given JSON object representing a BMv2 configuration, to a Bmv2PipelineModel object.
-     *
-     * @param jsonObject the BMv2 json config
-     * @return Bmv2PipelineModel BMv2 pipeline model object
-     */
-    public static Bmv2PipelineModel parse(JsonObject jsonObject) {
-        List<Bmv2HeaderTypeModel> headerTypeModels = HeaderTypesParser.parse(jsonObject);
-        Map<Integer, Integer> headerIdToIndex = HeaderStackParser.parse(jsonObject);
-        List<Bmv2HeaderModel> headerModels = HeadersParser.parse(jsonObject, headerTypeModels, headerIdToIndex);
-        List<Bmv2ActionModel> actionModels = ActionsParser.parse(jsonObject);
-        List<Bmv2TableModel> tableModels = TablesParser.parse(jsonObject, headerModels, actionModels);
-
-        return new Bmv2PipelineModel(headerTypeModels, headerModels,
-                                     actionModels, tableModels);
-    }
-
-    /**
-     * Parse the input stream pointing to a BMv2 JSON configuration, to a Bmv2PipelineModel object.
-     *
-     * @param url URL pointing to a BMv2 JSON configuration
-     * @return Bmv2PipelineModel BMv2 pipeline model object
-     */
-    public static Bmv2PipelineModel parse(URL url) {
-        checkNotNull(url, "Url cannot be null");
-        try {
-            InputStream inputStream = url.openStream();
-            checkArgument(inputStream.available() > 0, "Empty or non-existent JSON at " + url.toString());
-            return parse(Json.parse(new BufferedReader(
-                    new InputStreamReader(inputStream))).asObject());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Parser for BMv2 header types.
-     */
-    private static class HeaderTypesParser {
-        private static final String HEADER_TYPES = "header_types";
-        private static final String FIELDS = "fields";
-        private static final int FIELD_NAME_INDEX = 0;
-        private static final int FIELD_BIT_WIDTH_INDEX = 1;
-        private static final int FIELD_SIGNED_INDEX = 2;
-        private static final int SIZE_WITH_SIGNED_FLAG = 3;
-
-        private static List<Bmv2HeaderTypeModel> parse(JsonObject jsonObject) {
-            List<Bmv2HeaderTypeModel> headerTypeModels = Lists.newArrayList();
-            jsonObject.get(HEADER_TYPES).asArray().forEach(jsonValue -> {
-                JsonObject headerFieldType = jsonValue.asObject();
-                String name = headerFieldType.getString(NAME, null);
-                int id = headerFieldType.getInt(ID, NO_ID);
-                if (id == NO_ID) {
-                    log.warn("Can't get id from header type field {}", jsonValue);
-                    return;
-                }
-                if (name == null) {
-                    log.warn("Can't get name from header type field {}", jsonValue);
-                    return;
-                }
-                List<Bmv2HeaderFieldTypeModel> fields = Lists.newArrayList();
-                headerFieldType.get(FIELDS).asArray().forEach(fieldValue -> {
-                    JsonArray fieldInfo = fieldValue.asArray();
-                    boolean signed = false;
-                    if (fieldInfo.size() == SIZE_WITH_SIGNED_FLAG) {
-                        // 3-tuple value, third value is a boolean value
-                        // true if the field is signed; otherwise false
-                        signed = fieldInfo.get(FIELD_SIGNED_INDEX).asBoolean();
-                    }
-                    fields.add(new Bmv2HeaderFieldTypeModel(fieldInfo.get(FIELD_NAME_INDEX).asString(),
-                                                            fieldInfo.get(FIELD_BIT_WIDTH_INDEX).asInt(),
-                                                            signed));
-                });
-                headerTypeModels.add(new Bmv2HeaderTypeModel(name, id, fields));
-            });
-            return headerTypeModels;
-        }
-    }
-
-    /**
-     * Parser for BMv2 header stacks.
-     */
-    private static class HeaderStackParser {
-        private static final String HEADER_STACK = "header_stacks";
-        private static final String HEADER_IDS = "header_ids";
-
-        /**
-         * Parser header stacks, return header-id to stack index mapping.
-         *
-         * @param jsonObject BMv2 json config
-         * @return header-id to stack index mapping
-         */
-        private static Map<Integer, Integer> parse(JsonObject jsonObject) {
-            Map<Integer, Integer> headerIdToIndex = Maps.newHashMap();
-            jsonObject.get(HEADER_STACK).asArray().forEach(jsonValue -> {
-                JsonArray headerIds = jsonValue.asObject().get(HEADER_IDS).asArray();
-                int index = 0;
-                for (JsonValue id : headerIds.values()) {
-                    headerIdToIndex.put(id.asInt(), index);
-                    index++;
-                }
-            });
-            return headerIdToIndex;
-        }
-    }
-
-    /**
-     * Parser for BMv2 headers.
-     */
-    private static class HeadersParser {
-        private static final String HEADERS = "headers";
-        private static final String HEADER_TYPE = "header_type";
-        private static final String METADATA = "metadata";
-        private static final String DEFAULT_HEADER_TYPE = "";
-        private static final Integer DEFAULT_HEADER_INDEX = 0;
-
-        private static List<Bmv2HeaderModel> parse(JsonObject jsonObject,
-                                                   List<Bmv2HeaderTypeModel> headerTypeModels,
-                                                   Map<Integer, Integer> headerIdToIndex) {
-            List<Bmv2HeaderModel> headerModels = Lists.newArrayList();
-
-            jsonObject.get(HEADERS).asArray().forEach(jsonValue -> {
-                JsonObject header = jsonValue.asObject();
-                String name = header.getString(NAME, null);
-                int id = header.getInt(ID, NO_ID);
-                String headerTypeName = header.getString(HEADER_TYPE, DEFAULT_HEADER_TYPE);
-                boolean isMetadata = header.getBoolean(METADATA, false);
-
-                if (name == null || id == -1) {
-                    log.warn("Can't get name or id from header {}", header);
-                    return;
-                }
-                Bmv2HeaderTypeModel headerTypeModel = headerTypeModels.stream()
-                        .filter(model -> model.name().equals(headerTypeName))
-                        .findFirst()
-                        .orElse(null);
-
-                if (headerTypeModel == null) {
-                    log.warn("Can't get header type model {} from header {}", headerTypeName, header);
-                    return;
-                }
-
-                Integer index = headerIdToIndex.get(id);
-
-                // No index for this header, set to default
-                if (index == null) {
-                    index = DEFAULT_HEADER_INDEX;
-                }
-                headerModels.add(new Bmv2HeaderModel(name, id, index, headerTypeModel, isMetadata));
-            });
-
-            return headerModels;
-        }
-    }
-
-    /**
-     * Parser for BMv2 actions.
-     */
-    private static class ActionsParser {
-        private static final String ACTIONS = "actions";
-        private static final String RUNTIME_DATA = "runtime_data";
-        private static final String BITWIDTH = "bitwidth";
-
-        private static List<Bmv2ActionModel> parse(JsonObject jsonObject) {
-            List<Bmv2ActionModel> actionModels = Lists.newArrayList();
-
-            jsonObject.get(ACTIONS).asArray().forEach(jsonValue -> {
-                JsonObject action = jsonValue.asObject();
-                String name = action.getString(NAME, null);
-                int id = action.getInt(ID, NO_ID);
-                List<Bmv2ActionParamModel> paramModels = Lists.newArrayList();
-                action.get(RUNTIME_DATA).asArray().forEach(paramValue -> {
-                    JsonObject paramInfo = paramValue.asObject();
-                    String paramName = paramInfo.getString(NAME, null);
-                    int bitWidth = paramInfo.getInt(BITWIDTH, -1);
-
-                    if (paramName == null || bitWidth == -1) {
-                        log.warn("Can't get name or bit width from runtime data {}", paramInfo);
-                        return;
-                    }
-                    paramModels.add(new Bmv2ActionParamModel(paramName, bitWidth));
-                });
-
-                actionModels.add(new Bmv2ActionModel(name, id, paramModels));
-            });
-
-            return actionModels;
-        }
-    }
-
-    /**
-     * Parser for BMv2 tables.
-     */
-    private static class TablesParser {
-        private static final String PIPELINES = "pipelines";
-        private static final String TABLES = "tables";
-        private static final String KEY = "key";
-        private static final String MATCH_TYPE = "match_type";
-        private static final String TARGET = "target";
-        private static final int TARGET_HEADER_INDEX = 0;
-        private static final int TARGET_FIELD_INDEX = 1;
-        private static final String ACTIONS = "actions";
-        private static final String MAX_SIZE = "max_size";
-        private static final int DEFAULT_MAX_SIZE = 0;
-        private static final String WITH_COUNTERS = "with_counters";
-        private static final String SUPPORT_TIMEOUT = "support_timeout";
-
-        private static List<Bmv2TableModel> parse(JsonObject jsonObject,
-                                                  List<Bmv2HeaderModel> headerModels,
-                                                  List<Bmv2ActionModel> actionModels) {
-            List<Bmv2TableModel> tableModels = Lists.newArrayList();
-            jsonObject.get(PIPELINES).asArray().forEach(pipelineVal -> {
-                JsonObject pipeline = pipelineVal.asObject();
-                pipeline.get(TABLES).asArray().forEach(tableVal -> {
-                    JsonObject table = tableVal.asObject();
-                    String tableName = table.getString(NAME, null);
-                    int tableId = table.getInt(ID, NO_ID);
-                    int maxSize = table.getInt(MAX_SIZE, DEFAULT_MAX_SIZE);
-                    boolean hasCounters = table.getBoolean(WITH_COUNTERS, false);
-                    boolean suppportAging = table.getBoolean(SUPPORT_TIMEOUT, false);
-
-                    // Match field
-                    Set<Bmv2TableMatchFieldModel> matchFieldModels =
-                            Sets.newHashSet();
-                    table.get(KEY).asArray().forEach(keyVal -> {
-                        JsonObject key = keyVal.asObject();
-                        String matchTypeName = key.getString(MATCH_TYPE, null);
-
-                        if (matchTypeName == null) {
-                            log.warn("Can't find match type from key {}", key);
-                            return;
-                        }
-                        PiMatchType matchType = PiMatchType.valueOf(matchTypeName.toUpperCase());
-
-                        // convert target array to Bmv2HeaderFieldTypeModel
-                        // e.g. ["ethernet", "dst"]
-                        JsonArray targetArray = key.get(TARGET).asArray();
-                        Bmv2HeaderFieldModel matchField;
-
-                        String headerName = targetArray.get(TARGET_HEADER_INDEX).asString();
-                        String fieldName = targetArray.get(TARGET_FIELD_INDEX).asString();
-
-                        Bmv2HeaderModel headerModel = headerModels.stream()
-                                .filter(hm -> hm.name().equals(headerName))
-                                .findAny()
-                                .orElse(null);
-
-                        if (headerModel == null) {
-                            log.warn("Can't find header {} for table {}", headerName, tableName);
-                            return;
-                        }
-                        Bmv2HeaderFieldTypeModel fieldModel =
-                                (Bmv2HeaderFieldTypeModel) headerModel.type()
-                                        .field(fieldName)
-                                        .orElse(null);
-
-                        if (fieldModel == null) {
-                            log.warn("Can't find field {} from header {}", fieldName, headerName);
-                            return;
-                        }
-                        matchField = new Bmv2HeaderFieldModel(headerModel, fieldModel);
-                        matchFieldModels.add(new Bmv2TableMatchFieldModel(matchType, matchField));
-                    });
-
-                    // Actions
-                    Set<Bmv2ActionModel> actions = Sets.newHashSet();
-                    table.get(ACTIONS).asArray().forEach(actionVal -> {
-                        String actionName = actionVal.asString();
-                        Bmv2ActionModel action = actionModels.stream()
-                                .filter(am -> am.name().equals(actionName))
-                                .findAny()
-                                .orElse(null);
-                        if (action == null) {
-                            log.warn("Can't find action {}", actionName);
-                            return;
-                        }
-                        actions.add(action);
-                    });
-
-                    tableModels.add(new Bmv2TableModel(tableName, tableId,
-                                                       maxSize, hasCounters,
-                                                       suppportAging,
-                                                       matchFieldModels, actions));
-                });
-            });
-
-            return tableModels;
-        }
-    }
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2TableMatchFieldModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2TableMatchFieldModel.java
deleted file mode 100644
index 753dc85..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2TableMatchFieldModel.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Objects;
-import org.onosproject.net.pi.model.PiMatchType;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
-
-import static com.google.common.base.MoreObjects.toStringHelper;
-import static com.google.common.base.Preconditions.*;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * BMv2 table match field model.
- */
-@Beta
-public final class Bmv2TableMatchFieldModel implements PiTableMatchFieldModel {
-    private final PiMatchType matchType;
-    private final Bmv2HeaderFieldModel field;
-
-    /**
-     * Creates new BMv2 table match field model using the given type and header field.
-     *
-     * @param matchType the match type
-     * @param field the header field
-     */
-    public Bmv2TableMatchFieldModel(PiMatchType matchType, Bmv2HeaderFieldModel field) {
-        checkNotNull(matchType, "Match type can't be null");
-        checkNotNull(field, "Header field can't be null");
-
-        this.matchType = matchType;
-        this.field = field;
-    }
-
-    @Override
-    public PiMatchType matchType() {
-        return matchType;
-    }
-
-    @Override
-    public Bmv2HeaderFieldModel field() {
-        return field;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hashCode(matchType, field);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj == null || getClass() != obj.getClass()) {
-            return false;
-        }
-        final Bmv2TableMatchFieldModel other = (Bmv2TableMatchFieldModel) obj;
-        return Objects.equal(this.matchType, other.matchType)
-                && Objects.equal(this.field, other.field);
-    }
-
-    @Override
-    public String toString() {
-        return toStringHelper(this)
-                .add("matchType", matchType)
-                .add("field", field)
-                .toString();
-    }
-
-
-}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2TableModel.java b/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2TableModel.java
deleted file mode 100644
index 2ca4732..0000000
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/Bmv2TableModel.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2017-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import org.onosproject.net.pi.model.PiActionModel;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
-import org.onosproject.net.pi.model.PiTableModel;
-
-import java.util.Collection;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-
-import static com.google.common.base.Preconditions.*;
-
-/**
- * BMv2 table model.
- */
-@Beta
-public final class Bmv2TableModel implements PiTableModel {
-    private final String name;
-    private final int id;
-    private final int maxSize;
-    private final boolean hasCounters;
-    private final boolean supportAging;
-    private final Set<PiTableMatchFieldModel> matchFields;
-    private final Map<String, PiActionModel> actions;
-
-    /**
-     * Creates new BMv2 table model.
-     *
-     * @param name the name of table model
-     * @param id the table id
-     * @param maxSize the max size of table model
-     * @param hasCounters true if the table model has counter; null otherwise
-     * @param supportAging true if the table model supports aging; null otherwise
-     * @param matchFields the match fields of table model
-     * @param actions the actions of table model
-     */
-    public Bmv2TableModel(String name, int id,
-                          int maxSize, boolean hasCounters,
-                          boolean supportAging,
-                          Set<Bmv2TableMatchFieldModel> matchFields,
-                          Set<Bmv2ActionModel> actions) {
-        checkNotNull(name, "Model name can't be null");
-        checkArgument(maxSize >= 0, "Max size should more than 0");
-        checkNotNull(matchFields, "Match fields can't be null");
-        checkNotNull(actions, "Actions can't be null");
-        this.name = name;
-        this.id = id;
-        this.maxSize = maxSize;
-        this.hasCounters = hasCounters;
-        this.supportAging = supportAging;
-        this.matchFields = ImmutableSet.copyOf(matchFields);
-        ImmutableMap.Builder<String, PiActionModel> mapBuilder = ImmutableMap.builder();
-        actions.forEach(a -> mapBuilder.put(a.name(), a));
-        this.actions = mapBuilder.build();
-    }
-
-    /**
-     * Gets table model id.
-     *
-     * @return table model id
-     */
-    public int id() {
-        return id;
-    }
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    @Override
-    public int maxSize() {
-        return maxSize;
-    }
-
-    @Override
-    public boolean hasCounters() {
-        return hasCounters;
-    }
-
-    @Override
-    public boolean supportsAging() {
-        return supportAging;
-    }
-
-    @Override
-    public Collection<PiTableMatchFieldModel> matchFields() {
-        return matchFields;
-    }
-
-    @Override
-    public Collection<PiActionModel> actions() {
-        return actions.values();
-    }
-
-    @Override
-    public Optional<PiActionModel> action(String name) {
-        return Optional.ofNullable(actions.get(name));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name, id, maxSize, hasCounters, supportAging,
-                            matchFields, actions);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (!(obj instanceof Bmv2TableModel)) {
-            return false;
-        }
-        Bmv2TableModel that = (Bmv2TableModel) obj;
-        return Objects.equals(name, that.name) &&
-                Objects.equals(id, that.id) &&
-                Objects.equals(maxSize, that.maxSize) &&
-                Objects.equals(hasCounters, that.hasCounters) &&
-                Objects.equals(supportAging, that.supportAging) &&
-                Objects.equals(matchFields, that.matchFields) &&
-                Objects.equals(actions, that.actions);
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("name", name)
-                .add("id", id)
-                .add("maxSize", maxSize)
-                .add("hasCounters", hasCounters)
-                .add("supportAging", supportAging)
-                .add("matchFields", matchFields)
-                .add("actions", actions.values())
-                .toString();
-    }
-}
\ No newline at end of file
diff --git a/incubator/bmv2/model/src/test/java/org/onosproject/bmv2/model/Bmv2PipelineModelParserTest.java b/incubator/bmv2/model/src/test/java/org/onosproject/bmv2/model/Bmv2PipelineModelParserTest.java
deleted file mode 100644
index ab9e9fb..0000000
--- a/incubator/bmv2/model/src/test/java/org/onosproject/bmv2/model/Bmv2PipelineModelParserTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Foundation
- *
- * 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.bmv2.model;
-
-import com.eclipsesource.json.Json;
-import com.eclipsesource.json.JsonObject;
-import com.google.common.collect.Sets;
-import com.google.common.testing.EqualsTester;
-import org.hamcrest.collection.IsIterableContainingInOrder;
-import org.junit.Before;
-import org.junit.Test;
-import org.onosproject.net.pi.model.PiHeaderFieldModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
-import org.onosproject.net.pi.model.PiMatchType;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.*;
-import static org.hamcrest.core.IsEqual.equalTo;
-
-/**
- * BMv2 JSON configuration parser test.
- */
-public class Bmv2PipelineModelParserTest {
-
-    private JsonObject json;
-    private JsonObject json2;
-
-    @Before
-    public void setUp() throws Exception {
-        json = Json.parse(new BufferedReader(new InputStreamReader(
-                this.getClass().getResourceAsStream("/default.json")))).asObject();
-        json2 = Json.parse(new BufferedReader(new InputStreamReader(
-                this.getClass().getResourceAsStream("/default.json")))).asObject();
-    }
-
-    @Test
-    public void testParse() throws Exception {
-        Bmv2PipelineModel config = Bmv2PipelineModelParser.parse(json);
-        Bmv2PipelineModel config2 = Bmv2PipelineModelParser.parse(json2);
-
-        new EqualsTester()
-                .addEqualityGroup(config, config2)
-                .testEquals();
-
-        /* Check header types */
-        Bmv2HeaderTypeModel stdMetaT =
-                (Bmv2HeaderTypeModel) config.headerType("standard_metadata").orElse(null);
-        Bmv2HeaderTypeModel ethernetT =
-                (Bmv2HeaderTypeModel) config.headerType("ethernet_t").orElse(null);
-        Bmv2HeaderTypeModel intrinsicMetaT =
-                (Bmv2HeaderTypeModel) config.headerType("intrinsic_metadata_t").orElse(null);
-
-        Bmv2HeaderTypeModel stdMetaT2 =
-                (Bmv2HeaderTypeModel) config2.headerType("standard_metadata").orElse(null);
-        Bmv2HeaderTypeModel ethernetT2 =
-                (Bmv2HeaderTypeModel) config2.headerType("ethernet_t").orElse(null);
-        Bmv2HeaderTypeModel intrinsicMetaT2 =
-                (Bmv2HeaderTypeModel) config2.headerType("intrinsic_metadata_t").orElse(null);
-
-        new EqualsTester()
-                .addEqualityGroup(stdMetaT, stdMetaT2)
-                .addEqualityGroup(ethernetT, ethernetT2)
-                .addEqualityGroup(intrinsicMetaT, intrinsicMetaT2)
-                .testEquals();
-
-        // existence
-        assertThat("Json parsed value is null", stdMetaT, notNullValue());
-        assertThat("Json parsed value is null", ethernetT, notNullValue());
-        assertThat("Json parsed value is null", intrinsicMetaT, notNullValue());
-
-        // fields size
-        assertThat("Incorrect size for header type fields",
-                   stdMetaT.fields(), hasSize(18));
-        assertThat("Incorrect size for header type fields",
-                   ethernetT.fields(), hasSize(3));
-        assertThat("Incorrect size for header type fields",
-                   intrinsicMetaT.fields(), hasSize(4));
-
-        // check that fields are in order
-        assertThat("Incorrect order for header type fields",
-                   stdMetaT.fields(), IsIterableContainingInOrder.contains(
-                        stdMetaT.field("ingress_port").get(),
-                        stdMetaT.field("egress_spec").get(),
-                        stdMetaT.field("egress_port").get(),
-                        stdMetaT.field("clone_spec").get(),
-                        stdMetaT.field("instance_type").get(),
-                        stdMetaT.field("drop").get(),
-                        stdMetaT.field("recirculate_port").get(),
-                        stdMetaT.field("packet_length").get(),
-                        stdMetaT.field("enq_timestamp").get(),
-                        stdMetaT.field("enq_qdepth").get(),
-                        stdMetaT.field("deq_timedelta").get(),
-                        stdMetaT.field("deq_qdepth").get(),
-                        stdMetaT.field("ingress_global_timestamp").get(),
-                        stdMetaT.field("lf_field_list").get(),
-                        stdMetaT.field("mcast_grp").get(),
-                        stdMetaT.field("resubmit_flag").get(),
-                        stdMetaT.field("egress_rid").get(),
-                        stdMetaT.field("_padding").get()
-                ));
-
-        /* Check actions */
-        Bmv2ActionModel noAction =
-                (Bmv2ActionModel) config.action("NoAction").orElse(null);
-        Bmv2ActionModel setEgressPortAction =
-                (Bmv2ActionModel) config.action("set_egress_port_0").orElse(null);
-        Bmv2ActionModel sendToCpuAction =
-                (Bmv2ActionModel) config.action("send_to_cpu_0").orElse(null);
-        Bmv2ActionModel dropAction =
-                (Bmv2ActionModel) config.action("_drop_0").orElse(null);
-        Bmv2ActionModel processPortCountersCountPacketAction =
-                (Bmv2ActionModel) config.action("process_port_counters_0.count_packet").orElse(null);
-
-
-        Bmv2ActionModel noAction2 =
-                (Bmv2ActionModel) config.action("NoAction").orElse(null);
-        Bmv2ActionModel setEgressPortAction2 =
-                (Bmv2ActionModel) config.action("set_egress_port_0").orElse(null);
-        Bmv2ActionModel sendToCpuAction2 =
-                (Bmv2ActionModel) config.action("send_to_cpu_0").orElse(null);
-        Bmv2ActionModel dropAction2 =
-                (Bmv2ActionModel) config.action("_drop_0").orElse(null);
-        Bmv2ActionModel processPortCountersCountPacketAction2 =
-                (Bmv2ActionModel) config.action("process_port_counters_0.count_packet").orElse(null);
-
-        new EqualsTester()
-                .addEqualityGroup(noAction, noAction2)
-                .addEqualityGroup(setEgressPortAction, setEgressPortAction2)
-                .addEqualityGroup(sendToCpuAction, sendToCpuAction2)
-                .addEqualityGroup(dropAction, dropAction2)
-                .addEqualityGroup(processPortCountersCountPacketAction, processPortCountersCountPacketAction2)
-                .testEquals();
-
-        // existence
-        assertThat("Json parsed value is null", noAction, notNullValue());
-        assertThat("Json parsed value is null", setEgressPortAction, notNullValue());
-        assertThat("Json parsed value is null", sendToCpuAction, notNullValue());
-        assertThat("Json parsed value is null", dropAction, notNullValue());
-        assertThat("Json parsed value is null", processPortCountersCountPacketAction, notNullValue());
-
-        // runtime data size
-        assertThat("Incorrect size for action runtime data",
-                   noAction.params().size(), is(equalTo(0)));
-        assertThat("Incorrect size for action runtime data",
-                   setEgressPortAction.params().size(), is(equalTo(1)));
-        assertThat("Incorrect size for action runtime data",
-                   sendToCpuAction.params().size(), is(equalTo(0)));
-        assertThat("Incorrect size for action runtime data",
-                   dropAction.params().size(), is(equalTo(0)));
-        assertThat("Incorrect size for action runtime data",
-                   processPortCountersCountPacketAction.params().size(), is(equalTo(0)));
-
-        // runtime data existence and parsing
-        assertThat("Parsed Json value is null",
-                   setEgressPortAction.param("port").orElse(null), notNullValue());
-        assertThat("Incorrect value for action runtime data bitwidth",
-                   setEgressPortAction.param("port").get().bitWidth(), is(equalTo(9)));
-
-
-        /* Check tables */
-        Bmv2TableModel table0 =
-                (Bmv2TableModel) config.table("table0").orElse(null);
-        Bmv2TableModel table02 =
-                (Bmv2TableModel) config2.table("table0").orElse(null);
-
-        new EqualsTester()
-                .addEqualityGroup(table0, table02)
-                .testEquals();
-
-        // existence
-        assertThat("Parsed Json value is null", table0, notNullValue());
-
-        // id and name correspondence
-        assertThat("Incorrect value for table name",
-                   table0.name(), is(equalTo("table0")));
-
-        Set<PiTableMatchFieldModel> matchFields = Sets.newHashSet(table0.matchFields());
-
-        // keys size
-        assertThat("Incorrect size for table keys",
-                   matchFields.size(), is(equalTo(4)));
-
-        Set<PiMatchType> matchTypes = matchFields.stream()
-                .map(PiTableMatchFieldModel::matchType)
-                .collect(Collectors.toSet());
-
-        // key match type
-        assertThat("Incorrect value for table key match type",
-                   matchTypes, containsInAnyOrder(PiMatchType.TERNARY));
-
-        Set<PiHeaderTypeModel> headerTypeModels = matchFields.stream()
-                .map(PiTableMatchFieldModel::field)
-                .map(PiHeaderFieldModel::header)
-                .map(PiHeaderModel::type)
-                .collect(Collectors.toSet());
-
-        // header type
-        assertThat("Incorrect value for table key header type",
-                   headerTypeModels, containsInAnyOrder(ethernetT, stdMetaT));
-
-    }
-}
\ No newline at end of file
diff --git a/incubator/bmv2/model/src/test/resources/default.json b/incubator/bmv2/model/src/test/resources/default.json
deleted file mode 100644
index f0bcc71..0000000
--- a/incubator/bmv2/model/src/test/resources/default.json
+++ /dev/null
@@ -1,777 +0,0 @@
-{
-  "program" : "default.p4",
-  "__meta__" : null,
-  "header_types" : [
-    {
-      "name" : "scalars",
-      "id" : 0,
-      "fields" : [
-        ["tmp", 32, false],
-        ["tmp_0", 32, false]
-      ]
-    },
-    {
-      "name" : "ethernet_t",
-      "id" : 1,
-      "fields" : [
-        ["dstAddr", 48, false],
-        ["srcAddr", 48, false],
-        ["etherType", 16, false]
-      ]
-    },
-    {
-      "name" : "ipv4_t",
-      "id" : 2,
-      "fields" : [
-        ["version", 4, false],
-        ["ihl", 4, false],
-        ["diffserv", 8, false],
-        ["totalLen", 16, false],
-        ["identification", 16, false],
-        ["flags", 3, false],
-        ["fragOffset", 13, false],
-        ["ttl", 8, false],
-        ["protocol", 8, false],
-        ["hdrChecksum", 16, false],
-        ["srcAddr", 32, false],
-        ["dstAddr", 32, false]
-      ]
-    },
-    {
-      "name" : "tcp_t",
-      "id" : 3,
-      "fields" : [
-        ["srcPort", 16, false],
-        ["dstPort", 16, false],
-        ["seqNo", 32, false],
-        ["ackNo", 32, false],
-        ["dataOffset", 4, false],
-        ["res", 3, false],
-        ["ecn", 3, false],
-        ["ctrl", 6, false],
-        ["window", 16, false],
-        ["checksum", 16, false],
-        ["urgentPtr", 16, false]
-      ]
-    },
-    {
-      "name" : "udp_t",
-      "id" : 4,
-      "fields" : [
-        ["srcPort", 16, false],
-        ["dstPort", 16, false],
-        ["length_", 16, false],
-        ["checksum", 16, false]
-      ]
-    },
-    {
-      "name" : "ecmp_metadata_t",
-      "id" : 5,
-      "fields" : [
-        ["groupId", 16, false],
-        ["selector", 16, false]
-      ]
-    },
-    {
-      "name" : "wcmp_meta_t",
-      "id" : 6,
-      "fields" : [
-        ["groupId", 16, false],
-        ["numBits", 8, false],
-        ["selector", 64, false]
-      ]
-    },
-    {
-      "name" : "intrinsic_metadata_t",
-      "id" : 7,
-      "fields" : [
-        ["ingress_global_timestamp", 32, false],
-        ["lf_field_list", 32, false],
-        ["mcast_grp", 16, false],
-        ["egress_rid", 16, false]
-      ]
-    },
-    {
-      "name" : "standard_metadata",
-      "id" : 8,
-      "fields" : [
-        ["ingress_port", 9, false],
-        ["egress_spec", 9, false],
-        ["egress_port", 9, false],
-        ["clone_spec", 32, false],
-        ["instance_type", 32, false],
-        ["drop", 1, false],
-        ["recirculate_port", 16, false],
-        ["packet_length", 32, false],
-        ["enq_timestamp", 32, false],
-        ["enq_qdepth", 19, false],
-        ["deq_timedelta", 32, false],
-        ["deq_qdepth", 19, false],
-        ["ingress_global_timestamp", 48, false],
-        ["lf_field_list", 32, false],
-        ["mcast_grp", 16, false],
-        ["resubmit_flag", 1, false],
-        ["egress_rid", 16, false],
-        ["_padding", 5, false]
-      ]
-    }
-  ],
-  "headers" : [
-    {
-      "name" : "scalars",
-      "id" : 0,
-      "header_type" : "scalars",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "standard_metadata",
-      "id" : 1,
-      "header_type" : "standard_metadata",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ethernet",
-      "id" : 2,
-      "header_type" : "ethernet_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ipv4",
-      "id" : 3,
-      "header_type" : "ipv4_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "tcp",
-      "id" : 4,
-      "header_type" : "tcp_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "udp",
-      "id" : 5,
-      "header_type" : "udp_t",
-      "metadata" : false,
-      "pi_omit" : true
-    },
-    {
-      "name" : "ecmp_metadata",
-      "id" : 6,
-      "header_type" : "ecmp_metadata_t",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "wcmp_meta",
-      "id" : 7,
-      "header_type" : "wcmp_meta_t",
-      "metadata" : true,
-      "pi_omit" : true
-    },
-    {
-      "name" : "intrinsic_metadata",
-      "id" : 8,
-      "header_type" : "intrinsic_metadata_t",
-      "metadata" : true,
-      "pi_omit" : true
-    }
-  ],
-  "header_stacks" : [],
-  "field_lists" : [],
-  "errors" : [
-    ["NoError", 0],
-    ["PacketTooShort", 1],
-    ["NoMatch", 2],
-    ["StackOutOfBounds", 3],
-    ["HeaderTooShort", 4],
-    ["ParserTimeout", 5]
-  ],
-  "enums" : [],
-  "parsers" : [
-    {
-      "name" : "parser",
-      "id" : 0,
-      "init_state" : "start",
-      "parse_states" : [
-        {
-          "name" : "parse_ethernet",
-          "id" : 0,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "ethernet"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "0x0800",
-              "mask" : null,
-              "next_state" : "parse_ipv4"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["ethernet", "etherType"]
-            }
-          ]
-        },
-        {
-          "name" : "parse_ipv4",
-          "id" : 1,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "ipv4"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "0x000006",
-              "mask" : null,
-              "next_state" : "parse_tcp"
-            },
-            {
-              "value" : "0x000011",
-              "mask" : null,
-              "next_state" : "parse_udp"
-            },
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : [
-            {
-              "type" : "field",
-              "value" : ["ipv4", "fragOffset"]
-            },
-            {
-              "type" : "field",
-              "value" : ["ipv4", "protocol"]
-            }
-          ]
-        },
-        {
-          "name" : "parse_tcp",
-          "id" : 2,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "tcp"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : []
-        },
-        {
-          "name" : "parse_udp",
-          "id" : 3,
-          "parser_ops" : [
-            {
-              "parameters" : [
-                {
-                  "type" : "regular",
-                  "value" : "udp"
-                }
-              ],
-              "op" : "extract"
-            }
-          ],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : null
-            }
-          ],
-          "transition_key" : []
-        },
-        {
-          "name" : "start",
-          "id" : 4,
-          "parser_ops" : [],
-          "transitions" : [
-            {
-              "value" : "default",
-              "mask" : null,
-              "next_state" : "parse_ethernet"
-            }
-          ],
-          "transition_key" : []
-        }
-      ]
-    }
-  ],
-  "deparsers" : [
-    {
-      "name" : "deparser",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "./include/parsers.p4",
-        "line" : 34,
-        "column" : 8,
-        "source_fragment" : "DeparserImpl"
-      },
-      "order" : ["ethernet", "ipv4", "udp", "tcp"]
-    }
-  ],
-  "meter_arrays" : [],
-  "counter_arrays" : [
-    {
-      "name" : "process_port_counters_0.egress_port_counter",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "./include/port_counters.p4",
-        "line" : 5,
-        "column" : 41,
-        "source_fragment" : "egress_port_counter"
-      },
-      "size" : 254,
-      "is_direct" : false
-    },
-    {
-      "name" : "process_port_counters_0.ingress_port_counter",
-      "id" : 1,
-      "source_info" : {
-        "filename" : "./include/port_counters.p4",
-        "line" : 6,
-        "column" : 41,
-        "source_fragment" : "ingress_port_counter"
-      },
-      "size" : 254,
-      "is_direct" : false
-    }
-  ],
-  "register_arrays" : [],
-  "calculations" : [],
-  "learn_lists" : [],
-  "actions" : [
-    {
-      "name" : "NoAction",
-      "id" : 0,
-      "runtime_data" : [],
-      "primitives" : []
-    },
-    {
-      "name" : "NoAction",
-      "id" : 1,
-      "runtime_data" : [],
-      "primitives" : []
-    },
-    {
-      "name" : "set_egress_port_0",
-      "id" : 2,
-      "runtime_data" : [
-        {
-          "name" : "port",
-          "bitwidth" : 9
-        }
-      ],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "runtime_data",
-              "value" : 0
-            }
-          ],
-          "source_info" : {
-            "filename" : "default.p4",
-            "line" : 22,
-            "column" : 8,
-            "source_fragment" : "standard_metadata.egress_spec = port"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "send_to_cpu_0",
-      "id" : 3,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x00ff"
-            }
-          ],
-          "source_info" : {
-            "filename" : "default.p4",
-            "line" : 26,
-            "column" : 8,
-            "source_fragment" : "standard_metadata.egress_spec = 9w255"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "_drop_0",
-      "id" : 4,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["standard_metadata", "egress_spec"]
-            },
-            {
-              "type" : "hexstr",
-              "value" : "0x01ff"
-            }
-          ],
-          "source_info" : {
-            "filename" : "default.p4",
-            "line" : 30,
-            "column" : 8,
-            "source_fragment" : "standard_metadata.egress_spec = 9w511"
-          }
-        }
-      ]
-    },
-    {
-      "name" : "process_port_counters_0.count_packet",
-      "id" : 5,
-      "runtime_data" : [],
-      "primitives" : [
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "expression",
-                    "value" : {
-                      "op" : "&",
-                      "left" : {
-                        "type" : "field",
-                        "value" : ["standard_metadata", "ingress_port"]
-                      },
-                      "right" : {
-                        "type" : "hexstr",
-                        "value" : "0xff"
-                      }
-                    }
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "process_port_counters_0.ingress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "./include/port_counters.p4",
-            "line" : 8,
-            "column" : 8,
-            "source_fragment" : "ingress_port_counter.count((bit<32>)(bit<8>)standard_metadata.ingress_port)"
-          }
-        },
-        {
-          "op" : "assign",
-          "parameters" : [
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            },
-            {
-              "type" : "expression",
-              "value" : {
-                "type" : "expression",
-                "value" : {
-                  "op" : "&",
-                  "left" : {
-                    "type" : "expression",
-                    "value" : {
-                      "op" : "&",
-                      "left" : {
-                        "type" : "field",
-                        "value" : ["standard_metadata", "egress_spec"]
-                      },
-                      "right" : {
-                        "type" : "hexstr",
-                        "value" : "0xff"
-                      }
-                    }
-                  },
-                  "right" : {
-                    "type" : "hexstr",
-                    "value" : "0xffffffff"
-                  }
-                }
-              }
-            }
-          ]
-        },
-        {
-          "op" : "count",
-          "parameters" : [
-            {
-              "type" : "counter_array",
-              "value" : "process_port_counters_0.egress_port_counter"
-            },
-            {
-              "type" : "field",
-              "value" : ["scalars", "tmp_0"]
-            }
-          ],
-          "source_info" : {
-            "filename" : "./include/port_counters.p4",
-            "line" : 9,
-            "column" : 8,
-            "source_fragment" : "egress_port_counter.count((bit<32>)(bit<8>)standard_metadata.egress_spec)"
-          }
-        }
-      ]
-    }
-  ],
-  "pipelines" : [
-    {
-      "name" : "ingress",
-      "id" : 0,
-      "source_info" : {
-        "filename" : "default.p4",
-        "line" : 9,
-        "column" : 8,
-        "source_fragment" : "ingress"
-      },
-      "init_table" : "table0",
-      "tables" : [
-        {
-          "name" : "table0",
-          "id" : 0,
-          "source_info" : {
-            "filename" : "default.p4",
-            "line" : 32,
-            "column" : 10,
-            "source_fragment" : "table0"
-          },
-          "key" : [
-            {
-              "match_type" : "ternary",
-              "target" : ["standard_metadata", "ingress_port"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "dstAddr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "srcAddr"],
-              "mask" : null
-            },
-            {
-              "match_type" : "ternary",
-              "target" : ["ethernet", "etherType"],
-              "mask" : null
-            }
-          ],
-          "match_type" : "ternary",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [2, 3, 4, 0],
-          "actions" : ["set_egress_port_0", "send_to_cpu_0", "_drop_0", "NoAction"],
-          "base_default_next" : "node_3",
-          "next_tables" : {
-            "set_egress_port_0" : "node_3",
-            "send_to_cpu_0" : "node_3",
-            "_drop_0" : "node_3",
-            "NoAction" : "node_3"
-          },
-          "default_entry" : {
-            "action_id" : 0,
-            "action_const" : false,
-            "action_data" : [],
-            "action_entry_const" : false
-          }
-        },
-        {
-          "name" : "process_port_counters_0.port_count_table",
-          "id" : 1,
-          "source_info" : {
-            "filename" : "./include/port_counters.p4",
-            "line" : 11,
-            "column" : 10,
-            "source_fragment" : "port_count_table"
-          },
-          "key" : [],
-          "match_type" : "exact",
-          "type" : "simple",
-          "max_size" : 1024,
-          "with_counters" : false,
-          "support_timeout" : false,
-          "direct_meters" : null,
-          "action_ids" : [5, 1],
-          "actions" : ["process_port_counters_0.count_packet", "NoAction"],
-          "base_default_next" : null,
-          "next_tables" : {
-            "process_port_counters_0.count_packet" : null,
-            "NoAction" : null
-          },
-          "default_entry" : {
-            "action_id" : 1,
-            "action_const" : false,
-            "action_data" : [],
-            "action_entry_const" : false
-          }
-        }
-      ],
-      "action_profiles" : [],
-      "conditionals" : [
-        {
-          "name" : "node_3",
-          "id" : 0,
-          "source_info" : {
-            "filename" : "./include/port_counters.p4",
-            "line" : 17,
-            "column" : 12,
-            "source_fragment" : "standard_metadata.egress_spec < 9w254"
-          },
-          "expression" : {
-            "type" : "expression",
-            "value" : {
-              "op" : "<",
-              "left" : {
-                "type" : "field",
-                "value" : ["standard_metadata", "egress_spec"]
-              },
-              "right" : {
-                "type" : "hexstr",
-                "value" : "0x00fe"
-              }
-            }
-          },
-          "false_next" : null,
-          "true_next" : "process_port_counters_0.port_count_table"
-        }
-      ]
-    },
-    {
-      "name" : "egress",
-      "id" : 1,
-      "source_info" : {
-        "filename" : "default.p4",
-        "line" : 54,
-        "column" : 8,
-        "source_fragment" : "egress"
-      },
-      "init_table" : null,
-      "tables" : [],
-      "action_profiles" : [],
-      "conditionals" : []
-    }
-  ],
-  "checksums" : [],
-  "force_arith" : [],
-  "extern_instances" : [],
-  "field_aliases" : [
-    [
-      "queueing_metadata.enq_timestamp",
-      ["standard_metadata", "enq_timestamp"]
-    ],
-    [
-      "queueing_metadata.enq_qdepth",
-      ["standard_metadata", "enq_qdepth"]
-    ],
-    [
-      "queueing_metadata.deq_timedelta",
-      ["standard_metadata", "deq_timedelta"]
-    ],
-    [
-      "queueing_metadata.deq_qdepth",
-      ["standard_metadata", "deq_qdepth"]
-    ],
-    [
-      "intrinsic_metadata.ingress_global_timestamp",
-      ["standard_metadata", "ingress_global_timestamp"]
-    ],
-    [
-      "intrinsic_metadata.lf_field_list",
-      ["standard_metadata", "lf_field_list"]
-    ],
-    [
-      "intrinsic_metadata.mcast_grp",
-      ["standard_metadata", "mcast_grp"]
-    ],
-    [
-      "intrinsic_metadata.resubmit_flag",
-      ["standard_metadata", "resubmit_flag"]
-    ],
-    [
-      "intrinsic_metadata.egress_rid",
-      ["standard_metadata", "egress_rid"]
-    ]
-  ]
-}
\ No newline at end of file
diff --git a/incubator/bmv2/pom.xml b/incubator/bmv2/pom.xml
deleted file mode 100644
index ed74e6a..0000000
--- a/incubator/bmv2/pom.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  ~ Copyright 2017-present Open Networking Foundation
-  ~
-  ~ 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.
-  -->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-
-    <parent>
-        <groupId>org.onosproject</groupId>
-        <artifactId>onos-incubator</artifactId>
-        <version>1.12.0-SNAPSHOT</version>
-    </parent>
-
-    <artifactId>onos-incubator-bmv2</artifactId>
-    <packaging>pom</packaging>
-
-
-    <modules>
-        <module>model</module>
-    </modules>
-
-</project>
diff --git a/pipelines/basic/BUCK b/pipelines/basic/BUCK
index 2f27732..1b6cc47 100644
--- a/pipelines/basic/BUCK
+++ b/pipelines/basic/BUCK
@@ -1,15 +1,13 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
     '//lib:minimal-json',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
     '//drivers/default:onos-drivers-default',
+    '//protocols/p4runtime/model:onos-protocols-p4runtime-model',
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
 ]
 
 BUNDLES = [
     '//pipelines/basic:onos-pipelines-basic',
-    '//drivers/default:onos-drivers-default',
-    '//incubator/bmv2/model:onos-incubator-bmv2-model',
 ]
 
 osgi_jar(
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java
index df0923a..b71acbf 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicConstants.java
@@ -16,14 +16,13 @@
 
 package org.onosproject.pipelines.basic;
 
-import org.onosproject.net.pi.runtime.PiActionId;
-import org.onosproject.net.pi.runtime.PiActionParamId;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiCounterType;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
-import org.onosproject.net.pi.runtime.PiPacketMetadataId;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiTableId;
 
 /**
  * Constants for the basic.p4 program.
@@ -32,29 +31,36 @@
 
     // TODO: constants could be auto-generated starting from the P4info.
 
+    private static final String TABLE0_CONTROL = "table0_control";
+    private static final String WCMP_CONTROL = "wcmp_control";
+
     // Header field IDs
+    public static final String DOT =  ".";
+    public static final String HDR = "hdr";
     public static final String ETHERNET = "ethernet";
     public static final String LOCAL_METADATA = "local_metadata";
     public static final String STANDARD_METADATA = "standard_metadata";
-    public static final PiHeaderFieldId HDR_IN_PORT_ID = PiHeaderFieldId.of(STANDARD_METADATA, "ingress_port");
-    public static final PiHeaderFieldId HDR_ETH_DST_ID = PiHeaderFieldId.of(ETHERNET, "dst_addr");
-    public static final PiHeaderFieldId HDR_ETH_SRC_ID = PiHeaderFieldId.of(ETHERNET, "src_addr");
-    public static final PiHeaderFieldId HDR_ETH_TYPE_ID = PiHeaderFieldId.of(ETHERNET, "ether_type");
-    public static final PiHeaderFieldId HDR_NEXT_HOP_ID = PiHeaderFieldId.of(LOCAL_METADATA, "next_hop_id");
-    public static final PiHeaderFieldId HDR_SELECTOR_ID = PiHeaderFieldId.of(LOCAL_METADATA, "selector");
+    public static final PiMatchFieldId HDR_IN_PORT_ID = PiMatchFieldId.of(STANDARD_METADATA + DOT + "ingress_port");
+    public static final PiMatchFieldId HDR_ETH_DST_ID = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "dst_addr");
+    public static final PiMatchFieldId HDR_ETH_SRC_ID = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "src_addr");
+    public static final PiMatchFieldId HDR_ETH_TYPE_ID = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "ether_type");
+    public static final PiMatchFieldId HDR_NEXT_HOP_ID = PiMatchFieldId.of(LOCAL_METADATA + DOT + "next_hop_id");
+    public static final PiMatchFieldId HDR_SELECTOR_ID = PiMatchFieldId.of(LOCAL_METADATA + DOT + "selector");
     // Table IDs
-    public static final PiTableId TBL_TABLE0_ID = PiTableId.of("table0_control.table0");
-    public static final PiTableId TBL_WCMP_TABLE_ID = PiTableId.of("wcmp_control.wcmp_table");
+    public static final PiTableId TBL_TABLE0_ID = PiTableId.of(TABLE0_CONTROL + DOT  + "table0");
+    public static final PiTableId TBL_WCMP_TABLE_ID = PiTableId.of(WCMP_CONTROL + DOT  + "wcmp_table");
     // Counter IDs
-    public static final PiCounterId CNT_TABLE0_ID = PiCounterId.of("table0_control.table0_counter",
-                                                                   PiCounterType.DIRECT);
-    public static final PiCounterId CNT_WCMP_TABLE_ID = PiCounterId.of("wcmp_control.wcmp_table_counter",
-                                                                       PiCounterType.DIRECT);
+    public static final PiCounterId CNT_EGRESS_PORT_COUNTER_ID =
+            PiCounterId.of("port_counters_egress.egress_port_counter");
+    public static final PiCounterId CNT_INGRESS_PORT_COUNTER_ID =
+            PiCounterId.of("port_counters_ingress.ingress_port_counter");
+    public static final PiCounterId CNT_TABLE0_ID = PiCounterId.of(TABLE0_CONTROL + DOT  + "table0_counter");
+    public static final PiCounterId CNT_WCMP_TABLE_ID = PiCounterId.of(WCMP_CONTROL + DOT  + "wcmp_table_counter");
     // Action IDs
     public static final PiActionId ACT_NOACTION_ID = PiActionId.of("NoAction");
     public static final PiActionId ACT_DROP_ID = PiActionId.of("_drop");
     public static final PiActionId ACT_SET_EGRESS_PORT_ID = PiActionId.of("set_egress_port");
-    public static final PiActionId ACT_SET_NEXT_HOP_ID = PiActionId.of("table0_control.set_next_hop_id");
+    public static final PiActionId ACT_SET_NEXT_HOP_ID = PiActionId.of(TABLE0_CONTROL + DOT  + "set_next_hop_id");
     public static final PiActionId ACT_SEND_TO_CPU_ID = PiActionId.of("send_to_cpu");
     // Action Param IDs
     public static final PiActionParamId ACT_PRM_PORT_ID = PiActionParamId.of("port");
@@ -62,8 +68,8 @@
     // Action Profile IDs
     public static final PiActionProfileId ACT_PRF_WCMP_SELECTOR_ID = PiActionProfileId.of("wcmp_selector");
     // Packet Metadata IDs
-    public static final PiPacketMetadataId PKT_META_EGRESS_PORT_ID = PiPacketMetadataId.of("egress_port");
-    public static final PiPacketMetadataId PKT_META_INGRESS_PORT_ID = PiPacketMetadataId.of("ingress_port");
+    public static final PiControlMetadataId PKT_META_EGRESS_PORT_ID = PiControlMetadataId.of("egress_port");
+    public static final PiControlMetadataId PKT_META_INGRESS_PORT_ID = PiControlMetadataId.of("ingress_port");
     // Bitwidths
     public static final int PORT_BITWIDTH = 9;
 
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java
index f9ff527..cc0ef46 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/BasicInterpreterImpl.java
@@ -22,7 +22,6 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
@@ -33,14 +32,14 @@
 import org.onosproject.net.packet.DefaultInboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
-import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
-import org.onosproject.net.pi.runtime.PiTableId;
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
@@ -55,7 +54,7 @@
 import static org.onosproject.net.PortNumber.FLOOD;
 import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
 import static org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
-import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
 import static org.onosproject.pipelines.basic.BasicConstants.ACT_DROP_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.ACT_NOACTION_ID;
 import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRM_PORT_ID;
@@ -88,8 +87,8 @@
                     .put(TBL_TABLE0_ID, CNT_TABLE0_ID)
                     .put(TBL_WCMP_TABLE_ID, CNT_WCMP_TABLE_ID)
                     .build();
-    private static final ImmutableBiMap<Criterion.Type, PiHeaderFieldId> CRITERION_MAP =
-            new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+    private static final ImmutableBiMap<Criterion.Type, PiMatchFieldId> CRITERION_MAP =
+            new ImmutableBiMap.Builder<Criterion.Type, PiMatchFieldId>()
                     .put(Criterion.Type.IN_PORT, HDR_IN_PORT_ID)
                     .put(Criterion.Type.ETH_DST, HDR_ETH_DST_ID)
                     .put(Criterion.Type.ETH_SRC, HDR_ETH_SRC_ID)
@@ -183,7 +182,7 @@
     }
 
     @Override
-    public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
+    public InboundPacket mapInboundPacket(PiPacketOperation packetIn)
             throws PiInterpreterException {
         // Assuming that the packet is ethernet, which is fine since basic.p4
         // can deparse only ethernet packets.
@@ -196,36 +195,37 @@
         }
 
         // Returns the ingress port packet metadata.
-        Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas()
+        Optional<PiControlMetadata> packetMetadata = packetIn.metadatas()
                 .stream().filter(m -> m.id().equals(PKT_META_INGRESS_PORT_ID))
                 .findFirst();
 
         if (packetMetadata.isPresent()) {
             ImmutableByteSequence portByteSequence = packetMetadata.get().value();
             short s = portByteSequence.asReadOnlyBuffer().getShort();
-            ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+            ConnectPoint receivedFrom = new ConnectPoint(packetIn.deviceId(), PortNumber.portNumber(s));
             ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
             return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
         } else {
             throw new PiInterpreterException(format(
                     "Missing metadata '%s' in packet-in received from '%s': %s",
-                    PKT_META_INGRESS_PORT_ID, deviceId, packetIn));
+                    PKT_META_INGRESS_PORT_ID, packetIn.deviceId(), packetIn));
         }
     }
 
     private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber)
             throws PiInterpreterException {
-        PiPacketMetadata metadata = createPacketMetadata(portNumber);
+        PiControlMetadata metadata = createPacketMetadata(portNumber);
         return PiPacketOperation.builder()
+                .forDevice(this.data().deviceId())
                 .withType(PACKET_OUT)
                 .withData(copyFrom(data))
                 .withMetadatas(ImmutableList.of(metadata))
                 .build();
     }
 
-    private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+    private PiControlMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
         try {
-            return PiPacketMetadata.builder()
+            return PiControlMetadata.builder()
                     .withId(PKT_META_EGRESS_PORT_ID)
                     .withValue(fit(copyFrom(portNumber), PORT_BITWIDTH))
                     .build();
@@ -236,12 +236,12 @@
     }
 
     @Override
-    public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
+    public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
         return Optional.ofNullable(CRITERION_MAP.get(type));
     }
 
     @Override
-    public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
+    public Optional<Criterion.Type> mapPiMatchFieldId(PiMatchFieldId headerFieldId) {
         return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
     }
 
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpConstants.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpConstants.java
index 284d5cd..1671d0a 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpConstants.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpConstants.java
@@ -16,7 +16,7 @@
 
 package org.onosproject.pipelines.basic;
 
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiTableId;
 
 /**
  * Constants for the ecmp.p4 program.
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpInterpreterImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpInterpreterImpl.java
index 2596ee9..d103cdb 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpInterpreterImpl.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/EcmpInterpreterImpl.java
@@ -17,7 +17,7 @@
 package org.onosproject.pipelines.basic;
 
 import com.google.common.collect.ImmutableBiMap;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiTableId;
 
 import java.util.Optional;
 
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java
index bcfb7d0..9e298d3 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PipeconfLoader.java
@@ -22,7 +22,6 @@
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
 import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
 import org.onosproject.net.behaviour.Pipeliner;
 import org.onosproject.net.device.PortStatisticsDiscovery;
@@ -32,6 +31,8 @@
 import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.p4runtime.model.P4InfoParser;
+import org.onosproject.p4runtime.model.P4InfoParserException;
 
 import java.net.URL;
 import java.util.Collection;
@@ -75,10 +76,10 @@
     private static PiPipeconf buildBasicPipeconf() {
         final URL jsonUrl = PipeconfLoader.class.getResource(BASIC_JSON_PATH);
         final URL p4InfoUrl = PipeconfLoader.class.getResource(BASIC_P4INFO);
-        final PiPipelineModel model = Bmv2PipelineModelParser.parse(jsonUrl);
+
         return DefaultPiPipeconf.builder()
                 .withId(BASIC_PIPECONF_ID)
-                .withPipelineModel(model)
+                .withPipelineModel(parseP4Info(p4InfoUrl))
                 .addBehaviour(PiPipelineInterpreter.class, BasicInterpreterImpl.class)
                 .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
                 .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
@@ -92,10 +93,10 @@
     private static PiPipeconf buildEcmpPipeconf() {
         final URL jsonUrl = PipeconfLoader.class.getResource(ECMP_JSON_PATH);
         final URL p4InfoUrl = PipeconfLoader.class.getResource(ECMP_P4INFO);
-        final PiPipelineModel model = Bmv2PipelineModelParser.parse(jsonUrl);
+
         return DefaultPiPipeconf.builder()
                 .withId(ECMP_PIPECONF_ID)
-                .withPipelineModel(model)
+                .withPipelineModel(parseP4Info(p4InfoUrl))
                 .addBehaviour(PiPipelineInterpreter.class, EcmpInterpreterImpl.class)
                 .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
                 .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
@@ -103,4 +104,12 @@
                 .addExtension(BMV2_JSON, jsonUrl)
                 .build();
     }
+
+    private static PiPipelineModel parseP4Info(URL p4InfoUrl) {
+        try {
+            return P4InfoParser.parse(p4InfoUrl);
+        } catch (P4InfoParserException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java
index 425f8fe..bfcdd86 100644
--- a/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java
+++ b/pipelines/basic/src/main/java/org/onosproject/pipelines/basic/PortStatisticsDiscoveryImpl.java
@@ -24,11 +24,10 @@
 import org.onosproject.net.device.PortStatistics;
 import org.onosproject.net.device.PortStatisticsDiscovery;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.api.P4RuntimeController;
@@ -42,7 +41,9 @@
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
-import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
+import static org.onosproject.net.pi.model.PiCounterType.INDIRECT;
+import static org.onosproject.pipelines.basic.BasicConstants.CNT_EGRESS_PORT_COUNTER_ID;
+import static org.onosproject.pipelines.basic.BasicConstants.CNT_INGRESS_PORT_COUNTER_ID;
 
 /**
  * Implementation of the PortStatisticsBehaviour for basic.p4.
@@ -51,19 +52,13 @@
 
     protected final Logger log = LoggerFactory.getLogger(getClass());
 
-    private static final String SCOPE = "port_counters_control";
-    private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("port_counters_ingress",
-                                                                         "ingress_port_counter", INDIRECT);
-    private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("port_counters_egress",
-                                                                        "egress_port_counter", INDIRECT);
-
     /**
      * Returns the ID of the ingress port counter.
      *
      * @return counter ID
      */
     public PiCounterId ingressCounterId() {
-        return INGRESS_COUNTER_ID;
+        return CNT_INGRESS_PORT_COUNTER_ID;
     }
 
     /**
@@ -72,7 +67,7 @@
      * @return counter ID
      */
     public PiCounterId egressCounterId() {
-        return EGRESS_COUNTER_ID;
+        return CNT_EGRESS_PORT_COUNTER_ID;
     }
 
     @Override
@@ -106,8 +101,8 @@
         Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
         portStatBuilders.keySet().forEach(p -> {
             // Counter cell/index = port number.
-            counterCellIds.add(PiIndirectCounterCellId.of(ingressCounterId(), p));
-            counterCellIds.add(PiIndirectCounterCellId.of(egressCounterId(), p));
+            counterCellIds.add(PiCounterCellId.ofIndirect(ingressCounterId(), p));
+            counterCellIds.add(PiCounterCellId.ofIndirect(egressCounterId(), p));
         });
 
         Collection<PiCounterCellData> counterEntryResponse;
@@ -120,11 +115,11 @@
         }
 
         counterEntryResponse.forEach(counterData -> {
-            if (counterData.cellId().type() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
+            if (counterData.cellId().counterType() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterData.cellId().counterType());
                 return;
             }
-            PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
+            PiCounterCellId indCellId = counterData.cellId();
             if (!portStatBuilders.containsKey(indCellId.index())) {
                 log.warn("Unrecognized counter index {}, skipping", counterData);
                 return;
diff --git a/protocols/p4runtime/BUCK b/protocols/p4runtime/BUCK
index bf89ed7..9668a16 100644
--- a/protocols/p4runtime/BUCK
+++ b/protocols/p4runtime/BUCK
@@ -2,6 +2,7 @@
     '//protocols/p4runtime/proto:onos-protocols-p4runtime-proto',
     '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
     '//protocols/p4runtime/ctl:onos-protocols-p4runtime-ctl',
+    '//protocols/p4runtime/model:onos-protocols-p4runtime-model',
 ]
 
 onos_app(
@@ -14,4 +15,4 @@
     required_apps = [
         'org.onosproject.protocols.grpc'
     ],
-)
\ No newline at end of file
+)
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
index 5072c65..7ecbd80 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeClient.java
@@ -19,13 +19,13 @@
 import com.google.common.annotations.Beta;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiActionGroup;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.net.pi.model.PiTableId;
 
 import java.nio.ByteBuffer;
 import java.util.Collection;
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java
index 543ae4d..55c9e61 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeGroupReference.java
@@ -20,7 +20,7 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 
 /**
diff --git a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeTableEntryReference.java b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeTableEntryReference.java
index 5a0e9ca..d345f15 100644
--- a/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeTableEntryReference.java
+++ b/protocols/p4runtime/api/src/main/java/org/onosproject/p4runtime/api/P4RuntimeTableEntryReference.java
@@ -19,8 +19,8 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Objects;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiMatchKey;
-import org.onosproject.net.pi.runtime.PiTableId;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
index eb43f4c..c2a291e 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/ActionProfileGroupEncoder.java
@@ -17,10 +17,11 @@
 package org.onosproject.p4runtime.ctl;
 
 import com.google.common.collect.Maps;
+import org.onosproject.net.pi.model.PiActionGroupType;
+import org.onosproject.net.pi.model.PiActionProfileId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
 import p4.P4RuntimeOuterClass.ActionProfileGroup;
 import p4.P4RuntimeOuterClass.ActionProfileGroup.Member;
 import p4.P4RuntimeOuterClass.ActionProfileMember;
@@ -118,7 +119,7 @@
             case UNSPECIFIED:
                 // FIXME: PI returns unspecified for select groups. Remove this case when PI bug will be fixed.
             case SELECT:
-                piActionGroupBuilder.withType(PiActionGroup.Type.SELECT);
+                piActionGroupBuilder.withType(PiActionGroupType.SELECT);
                 break;
             default:
                 throw new EncodeException(format("Action profile type %s is not supported",
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
index c22d612..df4e72f 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/CounterEntryCodec.java
@@ -16,12 +16,11 @@
 
 package org.onosproject.p4runtime.ctl;
 
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterType;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiDirectCounterCellId;
-import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.slf4j.Logger;
 import p4.P4RuntimeOuterClass.CounterData;
@@ -120,27 +119,25 @@
         int counterId;
         Entity entity;
         // Encode PI cell ID into entity message and add to read request.
-        switch (cellId.type()) {
+        switch (cellId.counterType()) {
             case INDIRECT:
                 counterId = browser.counters().getByName(cellId.counterId().id()).getPreamble().getId();
-                PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) cellId;
                 entity = Entity.newBuilder().setCounterEntry(CounterEntry.newBuilder()
                                                                      .setCounterId(counterId)
-                                                                     .setIndex(indCellId.index())
+                                                                     .setIndex(cellId.index())
                                                                      .build())
                         .build();
                 break;
             case DIRECT:
                 counterId = browser.directCounters().getByName(cellId.counterId().id()).getPreamble().getId();
-                PiDirectCounterCellId dirCellId = (PiDirectCounterCellId) cellId;
                 DirectCounterEntry.Builder entryBuilder = DirectCounterEntry.newBuilder().setCounterId(counterId);
-                if (!dirCellId.tableEntry().equals(PiTableEntry.EMTPY)) {
-                    entryBuilder.setTableEntry(TableEntryEncoder.encode(dirCellId.tableEntry(), pipeconf));
+                if (!cellId.tableEntry().equals(PiTableEntry.EMTPY)) {
+                    entryBuilder.setTableEntry(TableEntryEncoder.encode(cellId.tableEntry(), pipeconf));
                 }
                 entity = Entity.newBuilder().setDirectCounterEntry(entryBuilder.build()).build();
                 break;
             default:
-                throw new EncodeException(format("Unrecognized PI counter cell ID type '%s'", cellId.type()));
+                throw new EncodeException(format("Unrecognized PI counter cell ID type '%s'", cellId.counterType()));
         }
         counterIdMap.put(counterId, cellId.counterId());
 
@@ -169,17 +166,23 @@
 
         PiCounterId piCounterId = counterIdMap.get(counterId);
 
+        if (!pipeconf.pipelineModel().counter(piCounterId).isPresent()) {
+            throw new EncodeException(format(
+                    "Unable to find counter '%s' in pipeline model", counterId));
+        }
+        PiCounterType piCounterType = pipeconf.pipelineModel().counter(piCounterId).get().counterType();
+
         // Compute PI cell ID.
         PiCounterCellId piCellId;
 
-        switch (piCounterId.type()) {
+        switch (piCounterType) {
             case INDIRECT:
                 if (entity.getEntityCase() != COUNTER_ENTRY) {
                     throw new EncodeException(format(
                             "Counter ID '%s' is indirect, but processed entity is %s",
                             piCounterId, entity.getEntityCase()));
                 }
-                piCellId = PiIndirectCounterCellId.of(piCounterId,
+                piCellId = PiCounterCellId.ofIndirect(piCounterId,
                                                       entity.getCounterEntry().getIndex());
                 break;
             case DIRECT:
@@ -190,10 +193,10 @@
                 }
                 PiTableEntry piTableEntry = TableEntryEncoder.decode(entity.getDirectCounterEntry().getTableEntry(),
                                                                      pipeconf);
-                piCellId = PiDirectCounterCellId.of(piCounterId, piTableEntry);
+                piCellId = PiCounterCellId.ofDirect(piCounterId, piTableEntry);
                 break;
             default:
-                throw new EncodeException(format("Unrecognized PI counter ID type '%s'", piCounterId.type()));
+                throw new EncodeException(format("Unrecognized PI counter ID type '%s'", piCounterType));
         }
 
         return new PiCounterCellData(piCellId, counterData.getPacketCount(), counterData.getByteCount());
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 48b1476..055b0c3 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
@@ -74,14 +74,7 @@
                     String tableName = entity.getPreamble().getName();
                     EntityBrowser<MatchField> matchFieldBrowser = new EntityBrowser<>(format(
                             "match field for table '%s'", tableName));
-                    entity.getMatchFieldsList().forEach(m -> {
-                        // FIXME: nasty hack needed to provide compatibility with BMv2-based pipeline models.
-                        // Indeed in the BMv2 JSON header fields have format like "ethernet.srd_addr", while in P4Info
-                        // the same will be "hdr.ethernet.srd_addr".
-                        // To be removed when ONOS-7066 will be implemented.
-                        String simpleName = extractMatchFieldSimpleName(m.getName());
-                        matchFieldBrowser.add(simpleName, null, m.getId(), m);
-                    });
+                    entity.getMatchFieldsList().forEach(m -> matchFieldBrowser.add(m.getName(), null, m.getId(), m));
                     matchFields.put(tableId, matchFieldBrowser);
                 });
 
@@ -125,19 +118,6 @@
                 });
     }
 
-    static String extractMatchFieldSimpleName(String name) {
-        // Removes the leading "hdr." or other scope identifier.
-        // E.g.: "hdr.ethernet.etherType" becomes "ethernet.etherType"
-        String[] pieces = name.split("\\.");
-        if (pieces.length == 3) {
-            return pieces[1] + "." + pieces[2];
-        } else if (pieces.length == 2) {
-            return name;
-        } else {
-            throw new UnsupportedOperationException("Invalid match field name: " + name);
-        }
-    }
-
     /**
      * Returns a browser for tables.
      *
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 738417e..28bcb6a 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
@@ -32,19 +32,18 @@
 import org.onlab.util.Tools;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.MastershipRole;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterType;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
-import org.onosproject.net.pi.runtime.PiCounterId;
-import org.onosproject.net.pi.runtime.PiDirectCounterCellId;
-import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
 import org.onosproject.p4runtime.api.P4RuntimeEvent;
 import org.slf4j.Logger;
@@ -107,7 +106,6 @@
             WriteOperationType.MODIFY, Update.Type.MODIFY,
             WriteOperationType.DELETE, Update.Type.DELETE
     );
-    private static final String ARBITRATION_RESULT_MASTER = "Is master";
 
     private final Logger log = getLogger(getClass());
 
@@ -225,15 +223,20 @@
         Set<PiCounterCellId> cellIds = Sets.newHashSet();
 
         for (PiCounterId counterId : counterIds) {
-            switch (counterId.type()) {
+            if (!pipeconf.pipelineModel().counter(counterId).isPresent()) {
+                log.warn("Unable to find counter '{}' in pipeline model",  counterId);
+                continue;
+            }
+            PiCounterType counterType = pipeconf.pipelineModel().counter(counterId).get().counterType();
+            switch (counterType) {
                 case INDIRECT:
-                    cellIds.add(PiIndirectCounterCellId.of(counterId, 0));
+                    cellIds.add(PiCounterCellId.ofIndirect(counterId, 0));
                     break;
                 case DIRECT:
-                    cellIds.add(PiDirectCounterCellId.of(counterId, PiTableEntry.EMTPY));
+                    cellIds.add(PiCounterCellId.ofDirect(counterId, PiTableEntry.EMTPY));
                     break;
                 default:
-                    log.warn("Unrecognized PI counter ID '{}'", counterId.type());
+                    log.warn("Unrecognized PI counter type '{}'", counterType);
             }
         }
 
@@ -460,7 +463,7 @@
             return;
         }
         // Decode packet message and post event.
-        PiPacketOperation packetOperation = PacketIOCodec.decodePacketIn(packetInMsg, pipeconf);
+        PiPacketOperation packetOperation = PacketIOCodec.decodePacketIn(packetInMsg, pipeconf, deviceId);
         DefaultPacketIn packetInEventSubject = new DefaultPacketIn(deviceId, packetOperation);
         P4RuntimeEvent event = new P4RuntimeEvent(P4RuntimeEvent.Type.PACKET_IN, packetInEventSubject);
         log.debug("Received packet in: {}", event);
@@ -802,4 +805,4 @@
             // FIXME: same concern as before.
         }
     }
-}
\ No newline at end of file
+}
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
index 03cdb1e..485689f 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/PacketIOCodec.java
@@ -18,9 +18,11 @@
 
 import com.google.protobuf.ByteString;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiPacketOperationType;
 import org.onosproject.net.pi.model.PiPipeconf;
-import org.onosproject.net.pi.runtime.PiPacketMetadata;
-import org.onosproject.net.pi.runtime.PiPacketMetadataId;
+import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiPacketOperation;
 import org.slf4j.Logger;
 import p4.config.P4InfoOuterClass;
@@ -33,7 +35,9 @@
 import static org.onlab.util.ImmutableByteSequence.copyFrom;
 import static org.onosproject.p4runtime.ctl.P4InfoBrowser.NotFoundException;
 import static org.slf4j.LoggerFactory.getLogger;
-import static p4.P4RuntimeOuterClass.*;
+import static p4.P4RuntimeOuterClass.PacketIn;
+import static p4.P4RuntimeOuterClass.PacketMetadata;
+import static p4.P4RuntimeOuterClass.PacketOut;
 
 /**
  * Encoder of packet metadata, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
@@ -53,10 +57,9 @@
     }
 
     /**
-     * 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.
+     * 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.
      *
@@ -72,15 +75,15 @@
         P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
 
         //Get the packet out controller packet metadata
-        P4InfoOuterClass.ControllerPacketMetadata controllerPacketMetadata =
+        P4InfoOuterClass.ControllerPacketMetadata controllerControlMetadata =
                 browser.controllerPacketMetadatas().getByName(PACKET_OUT);
         PacketOut.Builder packetOutBuilder = PacketOut.newBuilder();
 
         //outer controller packet metadata id
-        int controllerPacketMetadataId = controllerPacketMetadata.getPreamble().getId();
+        int controllerControlMetadataId = controllerControlMetadata.getPreamble().getId();
 
         //Add all its metadata to the packet out
-        packetOutBuilder.addAllMetadata(encodePacketMetadata(packet, browser, controllerPacketMetadataId));
+        packetOutBuilder.addAllMetadata(encodeControlMetadata(packet, browser, controllerControlMetadataId));
 
         //Set the packet out payload
         packetOutBuilder.setPayload(ByteString.copyFrom(packet.data().asReadOnlyBuffer()));
@@ -88,13 +91,13 @@
 
     }
 
-    private static List<PacketMetadata> encodePacketMetadata(PiPacketOperation packet,
-                                                             P4InfoBrowser browser, int controllerPacketMetadataId) {
+    private static List<PacketMetadata> encodeControlMetadata(PiPacketOperation packet,
+                                                              P4InfoBrowser browser, int controllerControlMetadataId) {
         return packet.metadatas().stream().map(metadata -> {
             try {
                 //get each metadata id
-                int metadataId = browser.packetMetadatas(controllerPacketMetadataId)
-                        .getByName(metadata.id().name()).getId();
+                int metadataId = browser.packetMetadatas(controllerControlMetadataId)
+                        .getByName(metadata.id().toString()).getId();
 
                 //Add the metadata id and it's data the packet out
                 return PacketMetadata.newBuilder()
@@ -102,35 +105,35 @@
                         .setValue(ByteString.copyFrom(metadata.value().asReadOnlyBuffer()))
                         .build();
             } catch (NotFoundException e) {
-                log.error("Cant find metadata with name {} in p4Info file.", metadata.id().name());
+                log.error("Cant find metadata with name {} in p4Info file.", metadata.id());
                 return null;
             }
         }).filter(Objects::nonNull).collect(Collectors.toList());
     }
 
     /**
-     * Returns a PiPacketOperation, decoded from the given P4Runtime PacketIn protobuf message
-     * for the given pipeconf. If a PI packet metadata inside the protobuf message cannot be decoded,
-     * it is skipped, hence the returned PiPacketOperation collection of metadatas might have different
-     * size than the input one.
+     * Returns a PiPacketOperation, decoded from the given P4Runtime PacketIn protobuf message for the given pipeconf
+     * and device ID. If a PI packet metadata inside the protobuf message cannot be decoded, it is skipped, hence the
+     * returned PiPacketOperation collection of metadatas might have different size than the input one.
      * <p>
      * Please check the log for an explanation of any error that might have occurred.
      *
-     * @param packetIn the P4Runtime PAcketIn message
+     * @param packetIn the P4Runtime PacketIn message
      * @param pipeconf the pipeconf for the program on the switch
+     * @param deviceId the deviceId that originated the PacketIn message
      * @return a PiPacketOperation
      */
-    static PiPacketOperation decodePacketIn(PacketIn packetIn, PiPipeconf pipeconf) {
+    static PiPacketOperation decodePacketIn(PacketIn packetIn, PiPipeconf pipeconf, DeviceId deviceId) {
 
         //Get the P4browser
         P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
 
-        List<PiPacketMetadata> packetMetadatas;
+        List<PiControlMetadata> packetMetadatas;
         try {
-            int controllerPacketMetadataId = browser.controllerPacketMetadatas().getByName(PACKET_IN)
-                                                .getPreamble().getId();
-            packetMetadatas = decodePacketMetadataIn(packetIn.getMetadataList(), browser,
-                                                                            controllerPacketMetadataId);
+            int controllerControlMetadataId = browser.controllerPacketMetadatas().getByName(PACKET_IN)
+                    .getPreamble().getId();
+            packetMetadatas = decodeControlMetadataIn(packetIn.getMetadataList(), browser,
+                                                      controllerControlMetadataId);
         } catch (NotFoundException e) {
             log.error("Unable to decode packet metadatas: {}", e.getMessage());
             packetMetadatas = Collections.emptyList();
@@ -141,25 +144,27 @@
 
         //Build the PiPacketOperation with all the metadatas.
         return PiPacketOperation.builder()
-                .withType(PiPacketOperation.Type.PACKET_IN)
+                .forDevice(deviceId)
+                .withType(PiPacketOperationType.PACKET_IN)
                 .withMetadatas(packetMetadatas)
                 .withData(data)
                 .build();
     }
 
-    private static List<PiPacketMetadata> decodePacketMetadataIn(List<PacketMetadata> packetMetadatas,
-                                                               P4InfoBrowser browser, int controllerPacketMetadataId) {
+    private static List<PiControlMetadata> decodeControlMetadataIn(List<PacketMetadata> packetMetadatas,
+                                                                   P4InfoBrowser browser,
+                                                                   int controllerControlMetadataId) {
         return packetMetadatas.stream().map(packetMetadata -> {
             try {
 
                 int packetMetadataId = packetMetadata.getMetadataId();
-                String packetMetadataName = browser.packetMetadatas(controllerPacketMetadataId)
+                String packetMetadataName = browser.packetMetadatas(controllerControlMetadataId)
                         .getById(packetMetadataId).getName();
 
-                PiPacketMetadataId metadataId = PiPacketMetadataId.of(packetMetadataName);
+                PiControlMetadataId metadataId = PiControlMetadataId.of(packetMetadataName);
 
                 //Build each metadata.
-                return PiPacketMetadata.builder()
+                return PiControlMetadata.builder()
                         .withId(metadataId)
                         .withValue(ImmutableByteSequence.copyFrom(packetMetadata.getValue().asReadOnlyByteBuffer()))
                         .build();
diff --git a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
index 4c192da..a6318de 100644
--- a/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
+++ b/protocols/p4runtime/ctl/src/main/java/org/onosproject/p4runtime/ctl/TableEntryEncoder.java
@@ -20,22 +20,22 @@
 import com.google.common.collect.Lists;
 import com.google.protobuf.ByteString;
 import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiTableId;
 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.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
 import org.onosproject.net.pi.runtime.PiTableAction;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import org.onosproject.net.pi.runtime.PiValidFieldMatch;
 import org.slf4j.Logger;
@@ -381,19 +381,7 @@
 
         int tableId = tableInfo.getPreamble().getId();
         String fieldMatchName = browser.matchFields(tableId).getById(fieldMatchMsg.getFieldId()).getName();
-
-        // FIXME: nasty hack needed to provide compatibility with BMv2-based pipeline models.
-        // Indeed in the BMv2 JSON header fields have format like "ethernet.srd_addr", while in P4Info
-        // the same will be "hdr.ethernet.srd_addr".
-        // To be removed when ONOS-7066 will be implemented.
-        fieldMatchName = P4InfoBrowser.extractMatchFieldSimpleName(fieldMatchName);
-
-        // FIXME: Add support for decoding of stacked header names.
-        String[] pieces = fieldMatchName.split("\\.");
-        if (pieces.length != 2) {
-            throw new EncodeException(format("unrecognized field match name '%s'", fieldMatchName));
-        }
-        PiHeaderFieldId headerFieldId = PiHeaderFieldId.of(pieces[0], pieces[1]);
+        PiMatchFieldId headerFieldId = PiMatchFieldId.of(fieldMatchName);
 
         FieldMatch.FieldMatchTypeCase typeCase = fieldMatchMsg.getFieldMatchTypeCase();
 
@@ -469,13 +457,13 @@
     static Action encodePiAction(PiAction piAction, P4InfoBrowser browser)
             throws P4InfoBrowser.NotFoundException, EncodeException {
 
-        int actionId = browser.actions().getByName(piAction.id().name()).getPreamble().getId();
+        int actionId = browser.actions().getByName(piAction.id().toString()).getPreamble().getId();
 
         Action.Builder actionMsgBuilder =
                 Action.newBuilder().setActionId(actionId);
 
         for (PiActionParam p : piAction.parameters()) {
-            P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().name());
+            P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().toString());
             ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer());
             assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()),
                        paramValue, paramInfo.getBitwidth());
diff --git a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
index debf6a6..afeb438 100644
--- a/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
+++ b/protocols/p4runtime/ctl/src/test/java/org/onosproject/p4runtime/ctl/P4RuntimeGroupTest.java
@@ -33,6 +33,9 @@
 import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionProfileId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineModel;
@@ -41,10 +44,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
-import org.onosproject.net.pi.runtime.PiActionProfileId;
 import p4.P4RuntimeOuterClass.ActionProfileGroup;
 import p4.P4RuntimeOuterClass.ActionProfileMember;
 import p4.P4RuntimeOuterClass.Entity;
@@ -64,8 +64,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.onosproject.net.pi.model.PiActionGroupType.SELECT;
 import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
-import static org.onosproject.net.pi.runtime.PiActionGroup.Type.SELECT;
 import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
 import static p4.P4RuntimeOuterClass.Action;
 import static p4.P4RuntimeOuterClass.ReadResponse;
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 20d6705..b805aa9 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
@@ -22,17 +22,17 @@
 import org.junit.Test;
 import org.onlab.util.ImmutableByteSequence;
 import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiMatchFieldId;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableId;
 import org.onosproject.net.pi.runtime.PiAction;
-import org.onosproject.net.pi.runtime.PiActionId;
 import org.onosproject.net.pi.runtime.PiActionParam;
-import org.onosproject.net.pi.runtime.PiActionParamId;
-import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiTableEntry;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import p4.P4RuntimeOuterClass.Action;
 import p4.P4RuntimeOuterClass.TableEntry;
@@ -55,9 +55,11 @@
  * Test for P4 runtime table entry encoder.
  */
 public class TableEntryEncoderTest {
+    private static final String DOT = ".";
     private static final String TABLE_0 = "table0";
     private static final String SET_EGRESS_PORT = "set_egress_port";
     private static final String PORT = "port";
+    private static final String HDR = "hdr";
     private static final String ETHERNET = "ethernet";
     private static final String DST_ADDR = "dstAddr";
     private static final String SRC_ADDR = "srcAddr";
@@ -77,10 +79,10 @@
     private final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(defaultPipeconf);
     private final ImmutableByteSequence ethAddr = fit(copyFrom(rand.nextInt()), 48);
     private final ImmutableByteSequence portValue = copyFrom((short) rand.nextInt());
-    private final PiHeaderFieldId ethDstAddrFieldId = PiHeaderFieldId.of(ETHERNET, DST_ADDR);
-    private final PiHeaderFieldId ethSrcAddrFieldId = PiHeaderFieldId.of(ETHERNET, SRC_ADDR);
-    private final PiHeaderFieldId inPortFieldId = PiHeaderFieldId.of(STANDARD_METADATA, INGRESS_PORT);
-    private final PiHeaderFieldId ethTypeFieldId = PiHeaderFieldId.of(ETHERNET, ETHER_TYPE);
+    private final PiMatchFieldId ethDstAddrFieldId = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + DST_ADDR);
+    private final PiMatchFieldId ethSrcAddrFieldId = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + SRC_ADDR);
+    private final PiMatchFieldId inPortFieldId = PiMatchFieldId.of(STANDARD_METADATA + DOT + INGRESS_PORT);
+    private final PiMatchFieldId ethTypeFieldId = PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + ETHER_TYPE);
     private final PiActionParamId portParamId = PiActionParamId.of(PORT);
     private final PiActionId outActionId = PiActionId.of(SET_EGRESS_PORT);
     private final PiTableId tableId = PiTableId.of(TABLE_0);
@@ -117,7 +119,7 @@
         int tableId = browser.tables().getByName(TABLE_0).getPreamble().getId();
         int actionId = browser.actions().getByName(SET_EGRESS_PORT).getPreamble().getId();
 
-        assertThat(browser.matchFields(tableId).hasName(STANDARD_METADATA + "." + INGRESS_PORT), is(true));
+        assertThat(browser.matchFields(tableId).hasName(STANDARD_METADATA + DOT + INGRESS_PORT), is(true));
         assertThat(browser.actionParams(actionId).hasName(PORT), is(true));
 
         // TODO: improve, assert browsing other entities (counters, meters, etc.)
@@ -152,12 +154,12 @@
         Action actionMsg = tableEntryMsg.getAction().getAction();
 
         // Action ID.
-        int p4InfoActionId = browser.actions().getByName(outActionId.name()).getPreamble().getId();
+        int p4InfoActionId = browser.actions().getByName(outActionId.toString()).getPreamble().getId();
         int encodedActionId = actionMsg.getActionId();
         assertThat(encodedActionId, is(p4InfoActionId));
 
         // Action param ID.
-        int p4InfoActionParamId = browser.actionParams(p4InfoActionId).getByName(portParamId.name()).getId();
+        int p4InfoActionParamId = browser.actionParams(p4InfoActionId).getByName(portParamId.toString()).getId();
         int encodedActionParamId = actionMsg.getParams(0).getParamId();
         assertThat(encodedActionParamId, is(p4InfoActionParamId));
 
diff --git a/protocols/p4runtime/model/BUCK b/protocols/p4runtime/model/BUCK
new file mode 100644
index 0000000..68442dc
--- /dev/null
+++ b/protocols/p4runtime/model/BUCK
@@ -0,0 +1,11 @@
+PROTOBUF_VER = '3.0.2'
+
+COMPILE_DEPS = [
+    '//lib:CORE_DEPS',
+    '//protocols/p4runtime/proto:onos-protocols-p4runtime-proto',
+    '//lib:protobuf-java-' + PROTOBUF_VER,
+]
+
+osgi_jar(
+    deps = COMPILE_DEPS,
+)
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionModel.java
new file mode 100644
index 0000000..dec4d1c
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionModel.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import com.google.common.collect.ImmutableMap;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionParamModel;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Implementation of PiActionModel for P4Runtime.
+ */
+final class P4ActionModel implements PiActionModel {
+
+    private final PiActionId id;
+    private final ImmutableMap<PiActionParamId, PiActionParamModel> params;
+
+    P4ActionModel(PiActionId id,
+                  ImmutableMap<PiActionParamId, PiActionParamModel> params) {
+        this.id = id;
+        this.params = params;
+    }
+
+    @Override
+    public PiActionId id() {
+        return id;
+    }
+
+    @Override
+    public Optional<PiActionParamModel> param(PiActionParamId paramId) {
+        return Optional.ofNullable(params.get(paramId));
+    }
+
+    @Override
+    public Collection<PiActionParamModel> params() {
+        return params.values();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, params);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4ActionModel other = (P4ActionModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.params, other.params);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionParamModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionParamModel.java
new file mode 100644
index 0000000..1c1f0a5
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionParamModel.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionParamModel;
+
+import java.util.Objects;
+
+/**
+ * Implementation of PiActionParamModel for P4Runtime.
+ */
+final class P4ActionParamModel implements PiActionParamModel {
+
+    private final PiActionParamId id;
+    private final int bitWidth;
+
+    P4ActionParamModel(PiActionParamId id, int bitWidth) {
+        this.id = id;
+        this.bitWidth = bitWidth;
+    }
+
+    @Override
+    public PiActionParamId id() {
+        return id;
+    }
+
+    @Override
+    public int bitWidth() {
+        return bitWidth;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, bitWidth);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4ActionParamModel other = (P4ActionParamModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.bitWidth, other.bitWidth);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionProfileModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionProfileModel.java
new file mode 100644
index 0000000..5417d44
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ActionProfileModel.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileModel;
+import org.onosproject.net.pi.model.PiTableId;
+
+import java.util.Collection;
+import java.util.Objects;
+
+/**
+ * Implementation of PiActionProfileModel for P4Runtime.
+ */
+final class P4ActionProfileModel implements PiActionProfileModel {
+
+    private final PiActionProfileId id;
+    private final ImmutableSet<PiTableId> tables;
+    private final boolean hasSelector;
+    private final long maxSize;
+
+    P4ActionProfileModel(PiActionProfileId id,
+                         ImmutableSet<PiTableId> tables, boolean hasSelector, long maxSize) {
+        this.id = id;
+        this.tables = tables;
+        this.hasSelector = hasSelector;
+        this.maxSize = maxSize;
+    }
+
+    @Override
+    public PiActionProfileId id() {
+        return id;
+    }
+
+    @Override
+    public Collection<PiTableId> tables() {
+        return tables;
+    }
+
+    @Override
+    public boolean hasSelector() {
+        return hasSelector;
+    }
+
+    @Override
+    public long maxSize() {
+        return maxSize;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, tables, hasSelector, maxSize);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4ActionProfileModel other = (P4ActionProfileModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.tables, other.tables)
+                && Objects.equals(this.hasSelector, other.hasSelector)
+                && Objects.equals(this.maxSize, other.maxSize);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java
new file mode 100644
index 0000000..45e7edc
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4ControlMetadataModel.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiControlMetadataModel;
+
+import java.util.Objects;
+
+/**
+ * Implementation of PiControlMetadataModel for P4Runtime.
+ */
+final class P4ControlMetadataModel implements PiControlMetadataModel {
+
+    private final PiControlMetadataId id;
+    private final int bitWidth;
+
+    P4ControlMetadataModel(PiControlMetadataId id, int bitWidth) {
+        this.id = id;
+        this.bitWidth = bitWidth;
+    }
+
+    @Override
+    public PiControlMetadataId id() {
+        return id;
+    }
+
+    @Override
+    public int bitWidth() {
+        return bitWidth;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, bitWidth);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4ControlMetadataModel other = (P4ControlMetadataModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.bitWidth, other.bitWidth);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4CounterModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4CounterModel.java
new file mode 100644
index 0000000..de6e755
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4CounterModel.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterModel;
+import org.onosproject.net.pi.model.PiCounterType;
+import org.onosproject.net.pi.model.PiTableId;
+
+import java.util.Objects;
+
+/**
+ * Implementation of PiCounterModel for P4Runtime.
+ */
+final class P4CounterModel implements PiCounterModel {
+
+    private final PiCounterId id;
+    private final PiCounterType counterType;
+    private final Unit unit;
+    private final PiTableId table;
+    private final long size;
+
+    P4CounterModel(PiCounterId id, PiCounterType counterType,
+                   Unit unit, PiTableId table, long size) {
+        this.id = id;
+        this.counterType = counterType;
+        this.unit = unit;
+        this.table = table;
+        this.size = size;
+    }
+
+    @Override
+    public PiCounterId id() {
+        return id;
+    }
+
+    @Override
+    public PiCounterType counterType() {
+        return counterType;
+    }
+
+    @Override
+    public Unit unit() {
+        return unit;
+    }
+
+    @Override
+    public PiTableId table() {
+        return table;
+    }
+
+    @Override
+    public long size() {
+        return size;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, counterType, unit, table, size);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4CounterModel other = (P4CounterModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.counterType, other.counterType)
+                && Objects.equals(this.unit, other.unit)
+                && Objects.equals(this.table, other.table)
+                && Objects.equals(this.size, other.size);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
new file mode 100644
index 0000000..dd365f6
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParser.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.TextFormat;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionParamId;
+import org.onosproject.net.pi.model.PiActionParamModel;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileModel;
+import org.onosproject.net.pi.model.PiControlMetadataId;
+import org.onosproject.net.pi.model.PiControlMetadataModel;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterModel;
+import org.onosproject.net.pi.model.PiCounterType;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
+import org.onosproject.net.pi.model.PiMatchType;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterModel;
+import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiPacketOperationModel;
+import org.onosproject.net.pi.model.PiPacketOperationType;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.model.PiTableType;
+import p4.config.P4InfoOuterClass.Action;
+import p4.config.P4InfoOuterClass.ActionProfile;
+import p4.config.P4InfoOuterClass.ActionRef;
+import p4.config.P4InfoOuterClass.ControllerPacketMetadata;
+import p4.config.P4InfoOuterClass.Counter;
+import p4.config.P4InfoOuterClass.CounterSpec;
+import p4.config.P4InfoOuterClass.DirectCounter;
+import p4.config.P4InfoOuterClass.DirectMeter;
+import p4.config.P4InfoOuterClass.MatchField;
+import p4.config.P4InfoOuterClass.Meter;
+import p4.config.P4InfoOuterClass.MeterSpec;
+import p4.config.P4InfoOuterClass.P4Info;
+import p4.config.P4InfoOuterClass.Table;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static java.lang.String.format;
+
+/**
+ * Parser of P4Info to PI pipeline model instances.
+ */
+public final class P4InfoParser {
+
+    private static final String PACKET_IN = "packet_in";
+    private static final String PACKET_OUT = "packet_out";
+
+    private static final Map<CounterSpec.Unit, PiCounterModel.Unit> COUNTER_UNIT_MAP =
+            new ImmutableMap.Builder<CounterSpec.Unit, PiCounterModel.Unit>()
+                    .put(CounterSpec.Unit.BYTES, PiCounterModel.Unit.BYTES)
+                    .put(CounterSpec.Unit.PACKETS, PiCounterModel.Unit.PACKETS)
+                    .put(CounterSpec.Unit.BOTH, PiCounterModel.Unit.PACKETS_AND_BYTES)
+                    // Don't map UNSPECIFIED as we don't support it at the moment.
+                    .build();
+
+    private static final Map<MeterSpec.Unit, PiMeterModel.Unit> METER_UNIT_MAP =
+            new ImmutableMap.Builder<MeterSpec.Unit, PiMeterModel.Unit>()
+                    .put(MeterSpec.Unit.BYTES, PiMeterModel.Unit.BYTES)
+                    .put(MeterSpec.Unit.PACKETS, PiMeterModel.Unit.PACKETS)
+                    // Don't map UNSPECIFIED as we don't support it at the moment.
+                    .build();
+
+    private static final Map<String, PiPacketOperationType> PACKET_OPERATION_TYPE_MAP =
+            new ImmutableMap.Builder<String, PiPacketOperationType>()
+                    .put(PACKET_IN, PiPacketOperationType.PACKET_IN)
+                    .put(PACKET_OUT, PiPacketOperationType.PACKET_OUT)
+                    .build();
+
+    private static final Map<MatchField.MatchType, PiMatchType> MATCH_TYPE_MAP =
+            new ImmutableMap.Builder<MatchField.MatchType, PiMatchType>()
+                    .put(MatchField.MatchType.VALID, PiMatchType.VALID)
+                    .put(MatchField.MatchType.EXACT, PiMatchType.EXACT)
+                    .put(MatchField.MatchType.LPM, PiMatchType.LPM)
+                    .put(MatchField.MatchType.TERNARY, PiMatchType.TERNARY)
+                    .put(MatchField.MatchType.RANGE, PiMatchType.RANGE)
+                    // Don't map UNSPECIFIED as we don't support it at the moment.
+                    .build();
+    public static final int NO_SIZE = -1;
+
+    private P4InfoParser() {
+        // Utility class, hides constructor.
+    }
+
+    /**
+     * Parse the given URL pointing to a P4Info file (in text format) to a PI pipeline model.
+     *
+     * @param p4InfoUrl URL to P4Info in text form
+     * @return PI pipeline model
+     * @throws P4InfoParserException if the P4Info file cannot be parsed (see message)
+     */
+    public static PiPipelineModel parse(URL p4InfoUrl) throws P4InfoParserException {
+
+        final P4Info p4info;
+        try {
+            p4info = getP4InfoMessage(p4InfoUrl);
+        } catch (IOException e) {
+            throw new P4InfoParserException("Unable to parse protobuf " + p4InfoUrl.toString(), e);
+        }
+
+        // Start by parsing and mapping instances to to their integer P4Info IDs.
+        // Convenient to build the table model at the end.
+
+        // Counters.
+        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
+        counterMap.putAll(parseCounters(p4info));
+        counterMap.putAll(parseDirectCounters(p4info));
+
+        // Meters.
+        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
+        meterMap.putAll(parseMeters(p4info));
+        meterMap.putAll(parseDirectMeters(p4info));
+
+        // Action profiles.
+        final Map<Integer, PiActionProfileModel> actProfileMap = parseActionProfiles(p4info);
+
+        // Actions.
+        final Map<Integer, PiActionModel> actionMap = parseActions(p4info);
+
+        // Controller packet metadatas.
+        final Map<PiPacketOperationType, PiPacketOperationModel> pktOpMap = parseCtrlPktMetadatas(p4info);
+
+        // Finally, parse tables.
+        final ImmutableMap.Builder<PiTableId, PiTableModel> tableImmMapBuilder =
+                ImmutableMap.builder();
+        for (Table tableMsg : p4info.getTablesList()) {
+            final PiTableId tableId = PiTableId.of(tableMsg.getPreamble().getName());
+            // Parse match fields.
+            final ImmutableMap.Builder<PiMatchFieldId, PiMatchFieldModel> tableFieldMapBuilder =
+                    ImmutableMap.builder();
+            for (MatchField fieldMsg : tableMsg.getMatchFieldsList()) {
+                final PiMatchFieldId fieldId = PiMatchFieldId.of(fieldMsg.getName());
+                tableFieldMapBuilder.put(
+                        fieldId,
+                        new P4MatchFieldModel(fieldId,
+                                              fieldMsg.getBitwidth(),
+                                              mapMatchFieldType(fieldMsg.getMatchType())));
+
+            }
+            // Retrieve action models by inter IDs.
+            final ImmutableMap.Builder<PiActionId, PiActionModel> tableActionMapBuilder =
+                    ImmutableMap.builder();
+            tableMsg.getActionRefsList().stream()
+                    .map(ActionRef::getId)
+                    .map(actionMap::get)
+                    .forEach(actionModel -> tableActionMapBuilder.put(actionModel.id(), actionModel));
+            // Retrieve direct meters by integer IDs.
+            final ImmutableMap.Builder<PiMeterId, PiMeterModel> tableMeterMapBuilder =
+                    ImmutableMap.builder();
+            tableMsg.getDirectResourceIdsList()
+                    .stream()
+                    .map(meterMap::get)
+                    // Direct resource ID might be that of a counter.
+                    // Filter out missed mapping.
+                    .filter(Objects::nonNull)
+                    .forEach(meterModel -> tableMeterMapBuilder.put(meterModel.id(), meterModel));
+            // Retrieve direct counters by integer IDs.
+            final ImmutableMap.Builder<PiCounterId, PiCounterModel> tableCounterMapBuilder =
+                    ImmutableMap.builder();
+            tableMsg.getDirectResourceIdsList()
+                    .stream()
+                    .map(counterMap::get)
+                    // As before, resource ID might be that of a meter.
+                    // Filter out missed mapping.
+                    .filter(Objects::nonNull)
+                    .forEach(counterModel -> tableCounterMapBuilder.put(counterModel.id(), counterModel));
+            tableImmMapBuilder.put(
+                    tableId,
+                    new P4TableModel(
+                            PiTableId.of(tableMsg.getPreamble().getName()),
+                            tableMsg.getImplementationId() == 0 ? PiTableType.DIRECT : PiTableType.INDIRECT,
+                            actProfileMap.get(tableMsg.getImplementationId()),
+                            tableMsg.getSize(),
+                            tableCounterMapBuilder.build(),
+                            tableMeterMapBuilder.build(),
+                            tableMsg.getWithEntryTimeout(),
+                            tableFieldMapBuilder.build(),
+                            tableActionMapBuilder.build(),
+                            actionMap.get(tableMsg.getConstDefaultActionId()),
+                            tableMsg.getConstDefaultActionHasMutableParams()));
+
+        }
+
+        // Get a map with proper PI IDs for some of those maps we created at the beginning.
+        ImmutableMap<PiCounterId, PiCounterModel> counterImmMap = ImmutableMap.copyOf(
+                counterMap.values().stream()
+                        .collect(Collectors.toMap(PiCounterModel::id, c -> c)));
+        ImmutableMap<PiMeterId, PiMeterModel> meterImmMap = ImmutableMap.copyOf(
+                meterMap.values().stream()
+                        .collect(Collectors.toMap(PiMeterModel::id, m -> m)));
+        ImmutableMap<PiActionProfileId, PiActionProfileModel> actProfileImmMap = ImmutableMap.copyOf(
+                actProfileMap.values().stream()
+                        .collect(Collectors.toMap(PiActionProfileModel::id, a -> a)));
+
+        return new P4PipelineModel(
+                tableImmMapBuilder.build(),
+                counterImmMap,
+                meterImmMap,
+                actProfileImmMap,
+                ImmutableMap.copyOf(pktOpMap));
+    }
+
+
+    private static Map<Integer, PiCounterModel> parseCounters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
+        for (Counter counterMsg : p4info.getCountersList()) {
+            counterMap.put(
+                    counterMsg.getPreamble().getId(),
+                    new P4CounterModel(
+                            PiCounterId.of(counterMsg.getPreamble().getName()),
+                            PiCounterType.INDIRECT,
+                            mapCounterSpecUnit(counterMsg.getSpec()),
+                            null,
+                            counterMsg.getSize()));
+        }
+        return counterMap;
+    }
+
+    private static Map<Integer, PiCounterModel> parseDirectCounters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
+        for (DirectCounter dirCounterMsg : p4info.getDirectCountersList()) {
+            counterMap.put(
+                    dirCounterMsg.getPreamble().getId(),
+                    new P4CounterModel(
+                            PiCounterId.of(dirCounterMsg.getPreamble().getName()),
+                            PiCounterType.DIRECT,
+                            mapCounterSpecUnit(dirCounterMsg.getSpec()),
+                            PiTableId.of(getTableName(dirCounterMsg.getDirectTableId(), p4info)),
+                            NO_SIZE));
+        }
+        return counterMap;
+    }
+
+    private static Map<Integer, PiMeterModel> parseMeters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
+        for (Meter meterMsg : p4info.getMetersList()) {
+            meterMap.put(
+                    meterMsg.getPreamble().getId(),
+                    new P4MeterModel(
+                            PiMeterId.of(meterMsg.getPreamble().getName()),
+                            PiMeterType.INDIRECT,
+                            mapMeterSpecUnit(meterMsg.getSpec()),
+                            null,
+                            meterMsg.getSize()));
+        }
+        return meterMap;
+    }
+
+    private static Map<Integer, PiMeterModel> parseDirectMeters(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
+        for (DirectMeter dirMeterMsg : p4info.getDirectMetersList()) {
+            meterMap.put(
+                    dirMeterMsg.getPreamble().getId(),
+                    new P4MeterModel(
+                            PiMeterId.of(dirMeterMsg.getPreamble().getName()),
+                            PiMeterType.DIRECT,
+                            mapMeterSpecUnit(dirMeterMsg.getSpec()),
+                            PiTableId.of(getTableName(dirMeterMsg.getDirectTableId(), p4info)),
+                            NO_SIZE));
+        }
+        return meterMap;
+    }
+
+    private static Map<Integer, PiActionProfileModel> parseActionProfiles(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<Integer, PiActionProfileModel> actProfileMap = Maps.newHashMap();
+        for (ActionProfile actProfileMsg : p4info.getActionProfilesList()) {
+            final ImmutableSet.Builder<PiTableId> tableIdSetBuilder = ImmutableSet.builder();
+            for (int tableId : actProfileMsg.getTableIdsList()) {
+                tableIdSetBuilder.add(PiTableId.of(getTableName(tableId, p4info)));
+            }
+            actProfileMap.put(
+                    actProfileMsg.getPreamble().getId(),
+                    new P4ActionProfileModel(
+                            PiActionProfileId.of(actProfileMsg.getPreamble().getName()),
+                            tableIdSetBuilder.build(),
+                            actProfileMsg.getWithSelector(),
+                            actProfileMsg.getSize()));
+        }
+        return actProfileMap;
+    }
+
+    private static Map<Integer, PiActionModel> parseActions(P4Info p4info) {
+        final Map<Integer, PiActionModel> actionMap = Maps.newHashMap();
+        for (Action actionMsg : p4info.getActionsList()) {
+            final ImmutableMap.Builder<PiActionParamId, PiActionParamModel> paramMapBuilder =
+                    ImmutableMap.builder();
+            actionMsg.getParamsList().forEach(paramMsg -> {
+                final PiActionParamId paramId = PiActionParamId.of(paramMsg.getName());
+                paramMapBuilder.put(paramId,
+                                    new P4ActionParamModel(PiActionParamId.of(paramMsg.getName()),
+                                                           paramMsg.getBitwidth()));
+            });
+            actionMap.put(
+                    actionMsg.getPreamble().getId(),
+                    new P4ActionModel(
+                            PiActionId.of(actionMsg.getPreamble().getName()),
+                            paramMapBuilder.build()));
+
+        }
+        return actionMap;
+    }
+
+    private static Map<PiPacketOperationType, PiPacketOperationModel> parseCtrlPktMetadatas(P4Info p4info)
+            throws P4InfoParserException {
+        final Map<PiPacketOperationType, PiPacketOperationModel> packetOpMap = Maps.newHashMap();
+        for (ControllerPacketMetadata ctrlPktMetaMsg : p4info.getControllerPacketMetadataList()) {
+            final ImmutableList.Builder<PiControlMetadataModel> metadataListBuilder =
+                    ImmutableList.builder();
+            ctrlPktMetaMsg.getMetadataList().forEach(metadataMsg -> metadataListBuilder.add(
+                    new P4ControlMetadataModel(PiControlMetadataId.of(metadataMsg.getName()),
+                                               metadataMsg.getBitwidth())));
+            packetOpMap.put(
+                    mapPacketOpType(ctrlPktMetaMsg.getPreamble().getName()),
+                    new P4PacketOperationModel(mapPacketOpType(ctrlPktMetaMsg.getPreamble().getName()),
+                                               metadataListBuilder.build()));
+
+        }
+        return packetOpMap;
+    }
+
+    private static P4Info getP4InfoMessage(URL p4InfoUrl) throws IOException {
+        InputStream p4InfoStream = p4InfoUrl.openStream();
+        P4Info.Builder p4InfoBuilder = P4Info.newBuilder();
+        TextFormat.getParser().merge(new InputStreamReader(p4InfoStream),
+                                     ExtensionRegistry.getEmptyRegistry(),
+                                     p4InfoBuilder);
+        return p4InfoBuilder.build();
+    }
+
+    private static String getTableName(int id, P4Info p4info)
+            throws P4InfoParserException {
+        return p4info.getTablesList().stream()
+                .filter(t -> t.getPreamble().getId() == id)
+                .findFirst()
+                .orElseThrow(() -> new P4InfoParserException(format(
+                        "Not such table with ID %d", id)))
+                .getPreamble()
+                .getName();
+    }
+
+    private static PiCounterModel.Unit mapCounterSpecUnit(CounterSpec spec)
+            throws P4InfoParserException {
+        if (!COUNTER_UNIT_MAP.containsKey(spec.getUnit())) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized counter unit '%s'", spec.getUnit()));
+        }
+        return COUNTER_UNIT_MAP.get(spec.getUnit());
+    }
+
+    private static PiMeterModel.Unit mapMeterSpecUnit(MeterSpec spec)
+            throws P4InfoParserException {
+        if (!METER_UNIT_MAP.containsKey(spec.getUnit())) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized meter unit '%s'", spec.getUnit()));
+        }
+        return METER_UNIT_MAP.get(spec.getUnit());
+    }
+
+    private static PiPacketOperationType mapPacketOpType(String name)
+            throws P4InfoParserException {
+        if (!PACKET_OPERATION_TYPE_MAP.containsKey(name)) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized controller packet metadata name '%s'", name));
+        }
+        return PACKET_OPERATION_TYPE_MAP.get(name);
+    }
+
+    private static PiMatchType mapMatchFieldType(MatchField.MatchType type)
+            throws P4InfoParserException {
+        if (!MATCH_TYPE_MAP.containsKey(type)) {
+            throw new P4InfoParserException(format(
+                    "Unrecognized match field type '%s'", type));
+        }
+        return MATCH_TYPE_MAP.get(type);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParserException.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParserException.java
new file mode 100644
index 0000000..f01f999
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4InfoParserException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+/**
+ * Signals an error occurred while parsing a P4Info object.
+ */
+public final class P4InfoParserException extends Exception {
+
+    /**
+     * Creates a new exception for the given message.
+     *
+     * @param message explanation
+     */
+    P4InfoParserException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates a new exception for the given message and cause.
+     *
+     * @param message message
+     * @param cause cause
+     */
+    P4InfoParserException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4MatchFieldModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4MatchFieldModel.java
new file mode 100644
index 0000000..f3b5ddc
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4MatchFieldModel.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
+import org.onosproject.net.pi.model.PiMatchType;
+
+import java.util.Objects;
+
+/**
+ * Implementation of PiMatchFieldModel for P4Runtime.
+ */
+final class P4MatchFieldModel implements PiMatchFieldModel {
+
+    private final PiMatchFieldId id;
+    private final int bitWidth;
+    private final PiMatchType matchType;
+
+    P4MatchFieldModel(PiMatchFieldId id, int bitWidth, PiMatchType matchType) {
+        this.id = id;
+        this.bitWidth = bitWidth;
+        this.matchType = matchType;
+    }
+
+    @Override
+    public PiMatchFieldId id() {
+        return id;
+    }
+
+    @Override
+    public int bitWidth() {
+        return bitWidth;
+    }
+
+    @Override
+    public PiMatchType matchType() {
+        return matchType;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, bitWidth, matchType);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4MatchFieldModel other = (P4MatchFieldModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.bitWidth, other.bitWidth)
+                && Objects.equals(this.matchType, other.matchType);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4MeterModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4MeterModel.java
new file mode 100644
index 0000000..93827cc
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4MeterModel.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterModel;
+import org.onosproject.net.pi.model.PiMeterType;
+import org.onosproject.net.pi.model.PiTableId;
+
+import java.util.Objects;
+
+/**
+ * Implementation of PiMeterModel for P4Runtime.
+ */
+final class P4MeterModel implements PiMeterModel {
+
+    private final PiMeterId id;
+    private final PiMeterType meterType;
+    private final Unit unit;
+    private final PiTableId table;
+    private final long size;
+
+    P4MeterModel(PiMeterId id, PiMeterType meterType, Unit unit, PiTableId table, long size) {
+        this.id = id;
+        this.meterType = meterType;
+        this.unit = unit;
+        this.table = table;
+        this.size = size;
+    }
+
+    @Override
+    public PiMeterId id() {
+        return id;
+    }
+
+    @Override
+    public PiMeterType meterType() {
+        return meterType;
+    }
+
+    @Override
+    public Unit unit() {
+        return unit;
+    }
+
+    @Override
+    public PiTableId table() {
+        return table;
+    }
+
+    @Override
+    public long size() {
+        return size;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, meterType, unit, table, size);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4MeterModel other = (P4MeterModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.meterType, other.meterType)
+                && Objects.equals(this.unit, other.unit)
+                && Objects.equals(this.table, other.table)
+                && Objects.equals(this.size, other.size);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java
new file mode 100644
index 0000000..84e2fe4
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PacketOperationModel.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import com.google.common.collect.ImmutableList;
+import org.onosproject.net.pi.model.PiControlMetadataModel;
+import org.onosproject.net.pi.model.PiPacketOperationModel;
+import org.onosproject.net.pi.model.PiPacketOperationType;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Implementation of PiPacketOperationModel for P4Runtime.
+ */
+final class P4PacketOperationModel implements PiPacketOperationModel {
+
+    private final PiPacketOperationType type;
+    private final ImmutableList<PiControlMetadataModel> metadatas;
+
+    P4PacketOperationModel(PiPacketOperationType type,
+                                  ImmutableList<PiControlMetadataModel> metadatas) {
+        this.type = type;
+        this.metadatas = metadatas;
+    }
+
+    @Override
+    public PiPacketOperationType type() {
+        return type;
+    }
+
+    @Override
+    public List<PiControlMetadataModel> metadatas() {
+        return metadatas;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, metadatas);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4PacketOperationModel other = (P4PacketOperationModel) obj;
+        return Objects.equals(this.type, other.type)
+                && Objects.equals(this.metadatas, other.metadatas);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PipelineModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PipelineModel.java
new file mode 100644
index 0000000..622cc23
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4PipelineModel.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import com.google.common.collect.ImmutableMap;
+import org.onosproject.net.pi.model.PiActionProfileId;
+import org.onosproject.net.pi.model.PiActionProfileModel;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterModel;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterModel;
+import org.onosproject.net.pi.model.PiPacketOperationModel;
+import org.onosproject.net.pi.model.PiPacketOperationType;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.model.PiTableModel;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Implementation of PiPipelineModel for P4Runtime.
+ */
+final class P4PipelineModel implements PiPipelineModel {
+
+    private final ImmutableMap<PiTableId, PiTableModel> tables;
+    private final ImmutableMap<PiCounterId, PiCounterModel> counters;
+    private final ImmutableMap<PiMeterId, PiMeterModel> meters;
+    private final ImmutableMap<PiActionProfileId, PiActionProfileModel> actionProfiles;
+    private final ImmutableMap<PiPacketOperationType, PiPacketOperationModel> packetOperations;
+
+    P4PipelineModel(
+            ImmutableMap<PiTableId, PiTableModel> tables,
+            ImmutableMap<PiCounterId, PiCounterModel> counters,
+            ImmutableMap<PiMeterId, PiMeterModel> meters,
+            ImmutableMap<PiActionProfileId, PiActionProfileModel> actionProfiles,
+            ImmutableMap<PiPacketOperationType, PiPacketOperationModel> packetOperations) {
+        this.tables = tables;
+        this.counters = counters;
+        this.meters = meters;
+        this.actionProfiles = actionProfiles;
+        this.packetOperations = packetOperations;
+    }
+
+    @Override
+    public Optional<PiTableModel> table(PiTableId tableId) {
+        return Optional.ofNullable(tables.get(tableId));
+    }
+
+    @Override
+    public Collection<PiTableModel> tables() {
+        return tables.values();
+    }
+
+    @Override
+    public Optional<PiCounterModel> counter(PiCounterId counterId) {
+        return Optional.ofNullable(counters.get(counterId));
+    }
+
+    @Override
+    public Collection<PiCounterModel> counters() {
+        return counters.values();
+    }
+
+    @Override
+    public Optional<PiMeterModel> meter(PiMeterId meterId) {
+        return Optional.ofNullable(meters.get(meterId));
+    }
+
+    @Override
+    public Collection<PiMeterModel> meters() {
+        return meters.values();
+    }
+
+    @Override
+    public Optional<PiActionProfileModel> actionProfiles(PiActionProfileId actionProfileId) {
+        return Optional.ofNullable(actionProfiles.get(actionProfileId));
+    }
+
+    @Override
+    public Collection<PiActionProfileModel> actionProfiles() {
+        return actionProfiles.values();
+    }
+
+    @Override
+    public Optional<PiPacketOperationModel> packetOperationModel(PiPacketOperationType type) {
+        return Optional.ofNullable(packetOperations.get(type));
+
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(tables, counters, meters, actionProfiles, packetOperations);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4PipelineModel other = (P4PipelineModel) obj;
+        return Objects.equals(this.tables, other.tables)
+                && Objects.equals(this.counters, other.counters)
+                && Objects.equals(this.meters, other.meters)
+                && Objects.equals(this.actionProfiles, other.actionProfiles)
+                && Objects.equals(this.packetOperations, other.packetOperations);
+    }
+}
diff --git a/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
new file mode 100644
index 0000000..89c4bed
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/P4TableModel.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.model;
+
+import com.google.common.collect.ImmutableMap;
+import org.onosproject.net.pi.model.PiActionId;
+import org.onosproject.net.pi.model.PiActionModel;
+import org.onosproject.net.pi.model.PiActionProfileModel;
+import org.onosproject.net.pi.model.PiCounterId;
+import org.onosproject.net.pi.model.PiCounterModel;
+import org.onosproject.net.pi.model.PiMatchFieldId;
+import org.onosproject.net.pi.model.PiMatchFieldModel;
+import org.onosproject.net.pi.model.PiMeterId;
+import org.onosproject.net.pi.model.PiMeterModel;
+import org.onosproject.net.pi.model.PiTableId;
+import org.onosproject.net.pi.model.PiTableModel;
+import org.onosproject.net.pi.model.PiTableType;
+
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Implementation of PiTableModel for P4Runtime.
+ */
+final class P4TableModel implements PiTableModel {
+
+    private final PiTableId id;
+    private final PiTableType tableType;
+    private final PiActionProfileModel actionProfile;
+    private final long maxSize;
+    private final ImmutableMap<PiCounterId, PiCounterModel> counters;
+    private final ImmutableMap<PiMeterId, PiMeterModel> meters;
+    private final boolean supportAging;
+    private final ImmutableMap<PiMatchFieldId, PiMatchFieldModel> matchFields;
+    private final ImmutableMap<PiActionId, PiActionModel> actions;
+    private final PiActionModel defaultAction;
+    private final boolean hasDefaultMutableParams;
+
+    P4TableModel(PiTableId id, PiTableType tableType,
+                        PiActionProfileModel actionProfile, long maxSize,
+                        ImmutableMap<PiCounterId, PiCounterModel> counters,
+                        ImmutableMap<PiMeterId, PiMeterModel> meters, boolean supportAging,
+                        ImmutableMap<PiMatchFieldId, PiMatchFieldModel> matchFields,
+                        ImmutableMap<PiActionId, PiActionModel> actions,
+                        PiActionModel defaultAction, boolean hasDefaultMutableParams) {
+        this.id = id;
+        this.tableType = tableType;
+        this.actionProfile = actionProfile;
+        this.maxSize = maxSize;
+        this.counters = counters;
+        this.meters = meters;
+        this.supportAging = supportAging;
+        this.matchFields = matchFields;
+        this.actions = actions;
+        this.defaultAction = defaultAction;
+        this.hasDefaultMutableParams = hasDefaultMutableParams;
+    }
+
+    @Override
+    public PiTableId id() {
+        return id;
+    }
+
+    @Override
+    public PiTableType tableType() {
+        return tableType;
+    }
+
+    @Override
+    public PiActionProfileModel actionProfile() {
+        return actionProfile;
+    }
+
+    @Override
+    public long maxSize() {
+        return maxSize;
+    }
+
+    @Override
+    public Collection<PiCounterModel> counters() {
+        return counters.values();
+    }
+
+    @Override
+    public Collection<PiMeterModel> meters() {
+        return meters.values();
+    }
+
+    @Override
+    public boolean supportsAging() {
+        return supportAging;
+    }
+
+    @Override
+    public Collection<PiMatchFieldModel> matchFields() {
+        return matchFields.values();
+    }
+
+    @Override
+    public Collection<PiActionModel> actions() {
+        return actions.values();
+    }
+
+    @Override
+    public Optional<PiActionModel> defaultAction() {
+        return Optional.ofNullable(defaultAction);
+    }
+
+    @Override
+    public boolean hasDefaultMutableParams() {
+        return hasDefaultMutableParams;
+    }
+
+    @Override
+    public Optional<PiActionModel> action(PiActionId actionId) {
+        return Optional.ofNullable(actions.get(actionId));
+    }
+
+    @Override
+    public Optional<PiMatchFieldModel> matchField(PiMatchFieldId matchFieldId) {
+        return Optional.ofNullable(matchFields.get(matchFieldId));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, tableType, actionProfile, maxSize, counters,
+                            meters, supportAging, matchFields, actions,
+                            defaultAction, hasDefaultMutableParams);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        final P4TableModel other = (P4TableModel) obj;
+        return Objects.equals(this.id, other.id)
+                && Objects.equals(this.tableType, other.tableType)
+                && Objects.equals(this.actionProfile, other.actionProfile)
+                && Objects.equals(this.maxSize, other.maxSize)
+                && Objects.equals(this.counters, other.counters)
+                && Objects.equals(this.meters, other.meters)
+                && Objects.equals(this.supportAging, other.supportAging)
+                && Objects.equals(this.matchFields, other.matchFields)
+                && Objects.equals(this.actions, other.actions)
+                && Objects.equals(this.defaultAction, other.defaultAction)
+                && Objects.equals(this.hasDefaultMutableParams, other.hasDefaultMutableParams);
+    }
+}
diff --git a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/package-info.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/package-info.java
similarity index 84%
rename from incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/package-info.java
rename to protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/package-info.java
index 3dac4a4..f1e8a69 100644
--- a/incubator/bmv2/model/src/main/java/org/onosproject/bmv2/model/package-info.java
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/package-info.java
@@ -15,6 +15,6 @@
  */
 
 /**
- * BMv2 target models.
+ * Pipeline model class implementation for P4Runtime and P4Info parser.
  */
-package org.onosproject.bmv2.model;
\ No newline at end of file
+package org.onosproject.p4runtime.model;
diff --git a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
index 75d0967..47ba6c4 100644
--- a/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
+++ b/providers/general/device/src/main/java/org/onosproject/provider/general/device/impl/GeneralDeviceProvider.java
@@ -38,6 +38,7 @@
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.behaviour.PiPipelineProgrammable;
 import org.onosproject.net.behaviour.PortAdmin;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
@@ -66,7 +67,6 @@
 import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
-import org.onosproject.net.pi.model.PiPipelineProgrammable;
 import org.onosproject.net.pi.runtime.PiPipeconfConfig;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.provider.AbstractProvider;
diff --git a/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java b/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java
index 19a42b3..fb4a97a 100644
--- a/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java
+++ b/providers/p4runtime/packet/src/main/java/org/onosproject/provider/p4runtime/packet/impl/P4RuntimePacketProvider.java
@@ -170,7 +170,7 @@
             PiPacketOperation operation = eventSubject.packetOperation();
             InboundPacket inPkt;
             try {
-                inPkt = device.as(PiPipelineInterpreter.class).mapInboundPacket(eventSubject.deviceId(), operation);
+                inPkt = device.as(PiPipelineInterpreter.class).mapInboundPacket(operation);
             } catch (PiPipelineInterpreter.PiInterpreterException e) {
                 log.warn("Unable to interpret inbound packet from {}: {}", deviceId, e.getMessage());
                 return;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
index 9ecbeca..e0ff366 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/PipeconfViewMessageHandler.java
@@ -18,23 +18,14 @@
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import org.onosproject.codec.CodecContext;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.pi.model.PiActionModel;
-import org.onosproject.net.pi.model.PiHeaderFieldModel;
-import org.onosproject.net.pi.model.PiHeaderModel;
-import org.onosproject.net.pi.model.PiHeaderTypeModel;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
-import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.model.PiPipelineModel;
-import org.onosproject.net.pi.model.PiTableMatchFieldModel;
-import org.onosproject.net.pi.model.PiTableModel;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
-import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiMessageHandler;
 import org.slf4j.Logger;
@@ -42,7 +33,6 @@
 
 import java.util.Collection;
 import java.util.Optional;
-import java.util.Set;
 
 public class PipeconfViewMessageHandler extends UiMessageHandler {
     private static final Logger log =
@@ -61,7 +51,7 @@
 
     private class PipeconfRequestHandler extends RequestHandler {
 
-        public PipeconfRequestHandler() {
+        PipeconfRequestHandler() {
             super(PIPECONF_REQUEST);
         }
 
@@ -103,9 +93,7 @@
                 sendMessage(NO_PIPECONF_RESP, null);
                 return;
             }
-            PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
-            PiPipelineModel pipelineModel =
-                    filteredOutAdditionalData(pipeconf.get().pipelineModel(), interpreter);
+            PiPipelineModel pipelineModel = pipeconf.get().pipelineModel();
 
             ObjectNode pipelineModelData =
                     codecContext.encode(pipelineModel, PiPipelineModel.class);
@@ -114,111 +102,4 @@
             sendMessage(PIPECONF_RESP, responseData);
         }
     }
-
-    private PiPipelineModel filteredOutAdditionalData(PiPipelineModel piPipelineModel,
-                                                      PiPipelineInterpreter interpreter) {
-        if (interpreter == null) {
-            // Do nothing if there is no interpreter
-            return piPipelineModel;
-        }
-        // filter out actions, headers and tables if not exists in interpreter
-        Set<PiHeaderTypeModel> newHeaderTypesModels = Sets.newHashSet();
-        Set<PiHeaderModel> newHeaderModels = Sets.newHashSet();
-        Set<PiActionModel> newActionModels = Sets.newHashSet();
-        Set<PiTableModel> newTableModels = Sets.newHashSet();
-
-        piPipelineModel.tables().forEach(table -> {
-            String tableName = table.name();
-            PiTableId tableId = PiTableId.of(tableName);
-
-            if (interpreter.mapPiTableId(tableId).isPresent()) {
-                newTableModels.add(table);
-
-                newActionModels.addAll(table.actions());
-                table.matchFields().stream()
-                        .map(PiTableMatchFieldModel::field)
-                        .map(PiHeaderFieldModel::header)
-                        .forEach(header -> {
-                            newHeaderModels.add(header);
-                            newHeaderTypesModels.add(header.type());
-                        });
-
-            }
-        });
-
-        return new FilteredPipelineModel(newHeaderTypesModels,
-                                         newHeaderModels,
-                                         newActionModels,
-                                         newTableModels);
-    }
-
-    /**
-     * Pipeline model for UI message.
-     * FIXME: Is it necessary to create this class?
-     */
-    private class FilteredPipelineModel implements PiPipelineModel {
-
-        private Set<PiHeaderTypeModel> headerTypesModels;
-        private Set<PiHeaderModel> headerModels;
-        private Set<PiActionModel> actionModels;
-        private Set<PiTableModel> tableModels;
-
-        public FilteredPipelineModel(Set<PiHeaderTypeModel> headerTypesModels,
-                                     Set<PiHeaderModel> headerModels,
-                                     Set<PiActionModel> actionModels,
-                                     Set<PiTableModel> tableModels) {
-            this.headerTypesModels = headerTypesModels;
-            this.headerModels = headerModels;
-            this.actionModels = actionModels;
-            this.tableModels = tableModels;
-        }
-
-        @Override
-        public Optional<PiHeaderTypeModel> headerType(String name) {
-            return headerTypesModels.stream()
-                    .filter(headerType -> headerType.name().equals(name))
-                    .findFirst();
-        }
-
-        @Override
-        public Collection<PiHeaderTypeModel> headerTypes() {
-            return headerTypesModels;
-        }
-
-        @Override
-        public Optional<PiHeaderModel> header(String name) {
-            return headerModels.stream()
-                    .filter(headerModel -> headerModel.name().equals(name))
-                    .findFirst();
-        }
-
-        @Override
-        public Collection<PiHeaderModel> headers() {
-            return headerModels;
-        }
-
-        @Override
-        public Optional<PiActionModel> action(String name) {
-            return actionModels.stream()
-                    .filter(actionModel -> actionModel.name().equals(name))
-                    .findFirst();
-        }
-
-        @Override
-        public Collection<PiActionModel> actions() {
-            return actionModels;
-        }
-
-        @Override
-        public Optional<PiTableModel> table(String name) {
-            return tableModels.stream()
-                    .filter(tableModel -> tableModel.name().equals(name))
-                    .findFirst();
-        }
-
-        @Override
-        public Collection<PiTableModel> tables() {
-            return tableModels;
-        }
-    }
 }