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/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/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/package-info.java b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/package-info.java
new file mode 100644
index 0000000..f1e8a69
--- /dev/null
+++ b/protocols/p4runtime/model/src/main/java/org/onosproject/p4runtime/model/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Pipeline model class implementation for P4Runtime and P4Info parser.
+ */
+package org.onosproject.p4runtime.model;