[ONOS-7596] Support reading table entries with counter data in P4Runtime

Change-Id: I85bacb1697a6c881dd69ba74a2162c73ec0b8aee
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 2afbce0..8043ae7 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
@@ -26,7 +26,7 @@
 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.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.service.PiPipeconfService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
@@ -95,7 +95,7 @@
         });
 
         // Query the device.
-        Collection<PiCounterCellData> counterEntryResponse;
+        Collection<PiCounterCell> counterEntryResponse;
         try {
             counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
         } catch (InterruptedException | ExecutionException e) {
@@ -105,24 +105,24 @@
         }
 
         // Process response.
-        counterEntryResponse.forEach(counterData -> {
-            if (counterData.cellId().counterType() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterData.cellId().counterType());
+        counterEntryResponse.forEach(counterCell -> {
+            if (counterCell.cellId().counterType() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
                 return;
             }
-            if (!portStatBuilders.containsKey(counterData.cellId().index())) {
-                log.warn("Unrecognized counter index {}, skipping", counterData);
+            if (!portStatBuilders.containsKey(counterCell.cellId().index())) {
+                log.warn("Unrecognized counter index {}, skipping", counterCell);
                 return;
             }
-            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterData.cellId().index());
-            if (counterData.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
-                statsBuilder.setPacketsReceived(counterData.packets());
-                statsBuilder.setBytesReceived(counterData.bytes());
-            } else if (counterData.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
-                statsBuilder.setPacketsSent(counterData.packets());
-                statsBuilder.setBytesSent(counterData.bytes());
+            DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(counterCell.cellId().index());
+            if (counterCell.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsReceived(counterCell.data().packets());
+                statsBuilder.setBytesReceived(counterCell.data().bytes());
+            } else if (counterCell.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
+                statsBuilder.setPacketsSent(counterCell.data().packets());
+                statsBuilder.setBytesSent(counterCell.data().bytes());
             } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterData);
+                log.warn("Unrecognized counter ID {}, skipping", counterCell);
             }
         });
 
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java
new file mode 100644
index 0000000..9508cbc
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCell.java
@@ -0,0 +1,98 @@
+/*
+ * 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.MoreObjects;
+import com.google.common.base.Objects;
+
+/**
+ * Counter cell of a protocol-independent pipeline.
+ */
+@Beta
+public final class PiCounterCell {
+
+    private final PiCounterCellId cellId;
+    private final PiCounterCellData counterData;
+
+    /**
+     * Creates a new counter cell for the given cell identifier and counter cell data.
+     *
+     * @param cellId  counter cell identifier
+     * @param piCounterCellData  counter cell data
+     */
+    public PiCounterCell(PiCounterCellId cellId, PiCounterCellData piCounterCellData) {
+        this.cellId = cellId;
+        this.counterData = piCounterCellData;
+    }
+
+    /**
+     * Creates a new counter cell for the given cell identifier, number of packets and bytes.
+     *
+     * @param cellId  counter cell identifier
+     * @param packets  number of packets
+     * @param bytes  number of bytes
+     */
+    public PiCounterCell(PiCounterCellId cellId, long packets, long bytes) {
+        this.cellId = cellId;
+        this.counterData = new PiCounterCellData(packets, bytes);
+    }
+
+    /**
+     * Returns the cell identifier.
+     *
+     * @return cell identifier
+     */
+    public PiCounterCellId cellId() {
+        return cellId;
+    }
+
+    /**
+     * Returns the data contained by this cell.
+     *
+     * @return counter cell data
+     */
+    public PiCounterCellData data() {
+        return counterData;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof PiCounterCell)) {
+            return false;
+        }
+        PiCounterCell that = (PiCounterCell) o;
+        return Objects.equal(cellId, that.cellId) &&
+                Objects.equal(counterData, that.counterData);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(cellId, counterData);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("cellId", cellId)
+                .add("counterData", counterData)
+                .toString();
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
index f0aada5..b640970 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiCounterCellData.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Foundation
+ * Copyright 2018-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.
@@ -23,37 +23,26 @@
 /**
  * Data of a counter cell of a protocol-independent pipeline.
  */
+
 @Beta
 public final class PiCounterCellData {
 
-    private final PiCounterCellId cellId;
     private final long packets;
     private final long bytes;
 
     /**
-     * Creates a new counter cell data for the given cell identifier, number of packets and bytes.
+     * Creates a new counter cell data for the given number of packets and bytes.
      *
-     * @param cellId  counter cell identifier
-     * @param packets number of packets
-     * @param bytes   number of bytes
+     * @param packets  number of packets
+     * @param bytes  number of bytes
      */
-    public PiCounterCellData(PiCounterCellId cellId, long packets, long bytes) {
-        this.cellId = cellId;
+    public PiCounterCellData(long packets, long bytes) {
         this.packets = packets;
         this.bytes = bytes;
     }
 
     /**
-     * Returns the cell identifier.
-     *
-     * @return cell identifier
-     */
-    public PiCounterCellId cellId() {
-        return cellId;
-    }
-
-    /**
-     * Returns the packet count value contained by this cell.
+     * Returns the packet count value contained by this counter data.
      *
      * @return number of packets
      */
@@ -62,7 +51,7 @@
     }
 
     /**
-     * Returns the byte count value contained by this cell.
+     * Returns the byte count value contained by this counter data.
      *
      * @return number of bytes
      */
@@ -80,19 +69,17 @@
         }
         PiCounterCellData that = (PiCounterCellData) o;
         return packets == that.packets &&
-                bytes == that.bytes &&
-                Objects.equal(cellId, that.cellId);
+                bytes == that.bytes;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hashCode(cellId, packets, bytes);
+        return Objects.hashCode(packets, bytes);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-                .add("cellId", cellId)
                 .add("packets", packets)
                 .add("bytes", bytes)
                 .toString();
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 f8a1460..ac73bbe 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
@@ -42,10 +42,11 @@
     private final long cookie;
     private final int priority;
     private final double timeout;
+    private final PiCounterCellData counterData;
 
     private PiTableEntry(PiTableId tableId, PiMatchKey matchKey,
                          PiTableAction tableAction, boolean isDefaultAction,
-                         long cookie, int priority, double timeout) {
+                         long cookie, int priority, double timeout, PiCounterCellData data) {
         this.tableId = tableId;
         this.matchKey = matchKey;
         this.tableAction = tableAction;
@@ -53,6 +54,7 @@
         this.cookie = cookie;
         this.priority = priority;
         this.timeout = timeout;
+        this.counterData = data;
     }
 
     /**
@@ -125,6 +127,18 @@
         return timeout == NO_TIMEOUT ? Optional.empty() : Optional.of(timeout);
     }
 
+    /**
+     * Returns the data of the counter cell associated with this table entry.
+     * This method is meaningful only if the table entry was read from the
+     * infrastructure device and the table has direct counters, otherwise
+     * returns null.
+     *
+     * @return counter cell data
+     */
+    public PiCounterCellData counter() {
+        return counterData;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -197,6 +211,7 @@
         private long cookie = 0;
         private int priority = NO_PRIORITY;
         private double timeout = NO_TIMEOUT;
+        private PiCounterCellData counterData;
 
         private Builder() {
             // Hides constructor.
@@ -272,6 +287,17 @@
         }
 
         /**
+         * Sets the counter cell data of this table entry.
+         *
+         * @param data counter cell data
+         * @return this
+         */
+        public Builder withCounterCellData(PiCounterCellData data) {
+            this.counterData = checkNotNull(data, "Counter cell data cannot be null");
+            return this;
+        }
+
+        /**
          * Builds the table entry.
          *
          * @return a new table entry
@@ -281,7 +307,7 @@
             checkNotNull(matchKey);
             final boolean isDefaultAction = matchKey.equals(PiMatchKey.EMPTY);
             return new PiTableEntry(tableId, matchKey, tableAction,
-                                    isDefaultAction, cookie, priority, timeout);
+                                    isDefaultAction, cookie, priority, timeout, counterData);
         }
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java
index 6676470..bfb8782 100644
--- a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellDataTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-present Open Networking Foundation
+ * Copyright 2018-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.
@@ -18,48 +18,25 @@
 
 import com.google.common.testing.EqualsTester;
 import org.junit.Test;
-import org.onosproject.net.pi.model.PiActionId;
-import org.onosproject.net.pi.model.PiTableId;
 
 import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
-import static org.onosproject.net.pi.runtime.PiConstantsTest.DROP;
 
 /**
- * Unit tests for PiCounterCellData class.
+ * Unit tests for PiCounterData class.
  */
 public class PiCounterCellDataTest {
 
-    private static final PiTableEntry PI_TABLE_ENTRY_1 = PiTableEntry.builder()
-            .forTable(PiTableId.of("T10"))
-            .withCookie(0xac)
-            .withPriority(10)
-            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
-            .withTimeout(100)
-            .build();
-    private static final PiTableEntry PI_TABLE_ENTRY_2 = PiTableEntry.builder()
-            .forTable(PiTableId.of("T20"))
-            .withCookie(0xac)
-            .withPriority(10)
-            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
-            .withTimeout(1000)
-            .build();
-
-    private static final PiCounterCellId PI_COUNTER_CELL_ID_1 =
-            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_1);
-    private static final PiCounterCellId PI_COUNTER_CELL_ID_2 =
-            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_2);
-
     private static final long PACKETS_1 = 10;
     private static final long PACKETS_2 = 20;
     private static final long BYTES_1 = 100;
     private static final long BYTES_2 = 200;
 
-    private static final PiCounterCellData PI_COUNTER_CELL_DATA_1 =
-            new PiCounterCellData(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
-    private static final PiCounterCellData SAME_AS_PI_COUNTER_CELL_DATA_1 =
-            new PiCounterCellData(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
-    private static final PiCounterCellData PI_COUNTER_CELL_DATA_2 =
-            new PiCounterCellData(PI_COUNTER_CELL_ID_2, PACKETS_2, BYTES_2);
+    private static final PiCounterCellData PI_COUNTER_DATA_1 =
+            new PiCounterCellData(PACKETS_1, BYTES_1);
+    private static final PiCounterCellData SAME_AS_PI_COUNTER_DATA_1 =
+            new PiCounterCellData(PACKETS_1, BYTES_1);
+    private static final PiCounterCellData PI_COUNTER_DATA_2 =
+            new PiCounterCellData(PACKETS_2, BYTES_2);
 
     /**
      * Checks that the PiCounterCellData class is immutable.
@@ -75,8 +52,8 @@
     @Test
     public void testEquals() {
         new EqualsTester()
-                .addEqualityGroup(PI_COUNTER_CELL_DATA_1, SAME_AS_PI_COUNTER_CELL_DATA_1)
-                .addEqualityGroup(PI_COUNTER_CELL_DATA_2)
+                .addEqualityGroup(PI_COUNTER_DATA_1, SAME_AS_PI_COUNTER_DATA_1)
+                .addEqualityGroup(PI_COUNTER_DATA_2)
                 .testEquals();
     }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellTest.java b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellTest.java
new file mode 100644
index 0000000..ab65efe
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/net/pi/runtime/PiCounterCellTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.PiActionId;
+import org.onosproject.net.pi.model.PiTableId;
+
+import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable;
+import static org.onosproject.net.pi.runtime.PiConstantsTest.DROP;
+
+/**
+ * Unit tests for PiCounterCell class.
+ */
+public class PiCounterCellTest {
+
+    private static final PiTableEntry PI_TABLE_ENTRY_1 = PiTableEntry.builder()
+            .forTable(PiTableId.of("T10"))
+            .withCookie(0xac)
+            .withPriority(10)
+            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
+            .withTimeout(100)
+            .build();
+    private static final PiTableEntry PI_TABLE_ENTRY_2 = PiTableEntry.builder()
+            .forTable(PiTableId.of("T20"))
+            .withCookie(0xac)
+            .withPriority(10)
+            .withAction(PiAction.builder().withId(PiActionId.of(DROP)).build())
+            .withTimeout(1000)
+            .build();
+
+    private static final PiCounterCellId PI_COUNTER_CELL_ID_1 =
+            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_1);
+    private static final PiCounterCellId PI_COUNTER_CELL_ID_2 =
+            PiCounterCellId.ofDirect(PI_TABLE_ENTRY_2);
+
+    private static final long PACKETS_1 = 10;
+    private static final long PACKETS_2 = 20;
+    private static final long BYTES_1 = 100;
+    private static final long BYTES_2 = 200;
+
+    private static final PiCounterCell PI_COUNTER_CELL_1 =
+            new PiCounterCell(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
+    private static final PiCounterCell SAME_AS_PI_COUNTER_CELL_1 =
+            new PiCounterCell(PI_COUNTER_CELL_ID_1, PACKETS_1, BYTES_1);
+    private static final PiCounterCell PI_COUNTER_CELL_2 =
+            new PiCounterCell(PI_COUNTER_CELL_ID_2, PACKETS_2, BYTES_2);
+
+    /**
+     * Checks that the PiCounterCell class is immutable.
+     */
+    @Test
+    public void testImmutability() {
+        assertThatClassIsImmutable(PiCounterCell.class);
+    }
+
+    /**
+     * Checks the operation of equals(), hashCode() and toString() methods.
+     */
+    @Test
+    public void testEquals() {
+        new EqualsTester()
+                .addEqualityGroup(PI_COUNTER_CELL_1, SAME_AS_PI_COUNTER_CELL_1)
+                .addEqualityGroup(PI_COUNTER_CELL_2)
+                .testEquals();
+    }
+}
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 0e684fe..0fd964e 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
@@ -231,6 +231,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
 import org.onosproject.net.pi.runtime.PiActionParam;
 import org.onosproject.net.pi.runtime.PiControlMetadata;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiEntity;
@@ -692,6 +693,7 @@
                     PiActionGroupMemberId.class,
                     PiActionParam.class,
                     PiControlMetadata.class,
+                    PiCounterCell.class,
                     PiCounterCellData.class,
                     PiCounterCellId.class,
                     PiEntity.class,
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 d0acdee..5a233ef 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
@@ -31,6 +31,7 @@
 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.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiTableEntry;
@@ -89,11 +90,11 @@
     private static final String SUPPORT_TABLE_COUNTERS = "supportTableCounters";
     private static final boolean DEFAULT_SUPPORT_TABLE_COUNTERS = true;
 
-    // If true, we read all direct counters of a table with one request.
-    // Otherwise, we send as many requests as the number of table entries.
-    private static final String READ_ALL_DIRECT_COUNTERS = "tableReadAllDirectCounters";
-    // FIXME: set to true as soon as the feature is implemented in P4Runtime.
-    private static final boolean DEFAULT_READ_ALL_DIRECT_COUNTERS = false;
+    // If true, assumes that the device returns table entry message populated
+    // with direct counter values. If false, we issue a second P4Runtime request
+    // to read the direct counter values.
+    private static final String READ_COUNTERS_WITH_TABLE_ENTRIES = "tableReadCountersWithTableEntries";
+    private static final boolean DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES = true;
 
     // For default entries, P4Runtime mandates that only MODIFY messages are
     // allowed. If true, treats default entries as normal table entries,
@@ -153,7 +154,6 @@
 
         // Synchronize mirror with the device state.
         syncMirror(deviceEntries);
-        // TODO: ONOS-7596 read counters with table entries
         final Map<PiTableEntry, PiCounterCellData> counterCellMap =
                 readEntryCounters(deviceEntries);
         // Forge flow entries with counter values.
@@ -461,25 +461,22 @@
             return Collections.emptyMap();
         }
 
-        Collection<PiCounterCellData> cellDatas;
-
-        if (driverBoolProperty(READ_ALL_DIRECT_COUNTERS,
-                               DEFAULT_READ_ALL_DIRECT_COUNTERS)) {
-            // FIXME: read counters when dumping table entries ONOS-7596
-            cellDatas = Collections.emptyList();
+        if (driverBoolProperty(READ_COUNTERS_WITH_TABLE_ENTRIES,
+                               DEFAULT_READ_COUNTERS_WITH_TABLE_ENTRIES)) {
+            return tableEntries.stream().collect(Collectors.toMap(c -> c, PiTableEntry::counter));
         } else {
+            Collection<PiCounterCell> cells;
             Set<PiCounterCellId> cellIds = tableEntries.stream()
                     // Ignore counter for default entry.
                     .filter(e -> !e.isDefaultAction())
                     .filter(e -> tableHasCounter(e.table()))
                     .map(PiCounterCellId::ofDirect)
                     .collect(Collectors.toSet());
-            cellDatas = getFutureWithDeadline(client.readCounterCells(cellIds, pipeconf),
+            cells = getFutureWithDeadline(client.readCounterCells(cellIds, pipeconf),
                                               "reading table counters", Collections.emptyList());
+            return cells.stream()
+                    .collect(Collectors.toMap(c -> c.cellId().tableEntry(), PiCounterCell::data));
         }
-        return cellDatas.stream()
-                .collect(Collectors.toMap(c -> c.cellId().tableEntry(), c -> c));
-
     }
 
     private boolean tableHasCounter(PiTableId tableId) {
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 ec8d5cf..0e39230 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
@@ -28,7 +28,7 @@
 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.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.service.PiPipeconfService;
 import org.onosproject.p4runtime.api.P4RuntimeClient;
@@ -112,7 +112,7 @@
             counterCellIds.add(PiCounterCellId.ofIndirect(egressCounterId(), p));
         });
 
-        Collection<PiCounterCellData> counterEntryResponse;
+        Collection<PiCounterCell> counterEntryResponse;
         try {
             counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
         } catch (InterruptedException | ExecutionException e) {
@@ -121,25 +121,25 @@
             return Collections.emptyList();
         }
 
-        counterEntryResponse.forEach(counterData -> {
-            if (counterData.cellId().counterType() != INDIRECT) {
-                log.warn("Invalid counter data type {}, skipping", counterData.cellId().counterType());
+        counterEntryResponse.forEach(counterCell -> {
+            if (counterCell.cellId().counterType() != INDIRECT) {
+                log.warn("Invalid counter data type {}, skipping", counterCell.cellId().counterType());
                 return;
             }
-            PiCounterCellId indCellId = counterData.cellId();
+            PiCounterCellId indCellId = counterCell.cellId();
             if (!portStatBuilders.containsKey(indCellId.index())) {
-                log.warn("Unrecognized counter index {}, skipping", counterData);
+                log.warn("Unrecognized counter index {}, skipping", counterCell);
                 return;
             }
             DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
-            if (counterData.cellId().counterId().equals(ingressCounterId())) {
-                statsBuilder.setPacketsReceived(counterData.packets());
-                statsBuilder.setBytesReceived(counterData.bytes());
-            } else if (counterData.cellId().counterId().equals(egressCounterId())) {
-                statsBuilder.setPacketsSent(counterData.packets());
-                statsBuilder.setBytesSent(counterData.bytes());
+            if (counterCell.cellId().counterId().equals(ingressCounterId())) {
+                statsBuilder.setPacketsReceived(counterCell.data().packets());
+                statsBuilder.setBytesReceived(counterCell.data().bytes());
+            } else if (counterCell.cellId().counterId().equals(egressCounterId())) {
+                statsBuilder.setPacketsSent(counterCell.data().packets());
+                statsBuilder.setBytesSent(counterCell.data().bytes());
             } else {
-                log.warn("Unrecognized counter ID {}, skipping", counterData);
+                log.warn("Unrecognized counter ID {}, skipping", counterCell);
             }
 
         });
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 8c1d7d9..f30ec28 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
@@ -25,7 +25,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiMeterCellConfig;
 import org.onosproject.net.pi.runtime.PiMeterCellId;
@@ -178,7 +178,7 @@
      * @param pipeconf   pipeconf
      * @return list of counter data
      */
-    CompletableFuture<List<PiCounterCellData>> readAllCounterCells(
+    CompletableFuture<List<PiCounterCell>> readAllCounterCells(
             Set<PiCounterId> counterIds, PiPipeconf pipeconf);
 
     /**
@@ -189,7 +189,7 @@
      * @param pipeconf pipeconf
      * @return list of counter data
      */
-    CompletableFuture<List<PiCounterCellData>> readCounterCells(
+    CompletableFuture<List<PiCounterCell>> readCounterCells(
             Set<PiCounterCellId> cellIds, PiPipeconf pipeconf);
 
     /**
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 e883821..6c29062 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
@@ -20,7 +20,7 @@
 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.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.slf4j.Logger;
@@ -135,8 +135,8 @@
      * @param pipeconf pipeconf
      * @return collection of PI counter cell data
      */
-    static List<PiCounterCellData> decodeCounterEntities(List<Entity> entities,
-                                                         PiPipeconf pipeconf) {
+    static List<PiCounterCell> decodeCounterEntities(List<Entity> entities,
+                                                     PiPipeconf pipeconf) {
 
         final P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
 
@@ -248,9 +248,9 @@
         }
     }
 
-    private static PiCounterCellData decodeCounterEntity(Entity entity,
-                                                         PiPipeconf pipeconf,
-                                                         P4InfoBrowser browser)
+    private static PiCounterCell decodeCounterEntity(Entity entity,
+                                                     PiPipeconf pipeconf,
+                                                     P4InfoBrowser browser)
             throws EncodeException, P4InfoBrowser.NotFoundException {
 
         CounterData counterData;
@@ -276,8 +276,8 @@
                     entity.getEntityCase().name()));
         }
 
-        return new PiCounterCellData(piCellId,
-                                     counterData.getPacketCount(),
-                                     counterData.getByteCount());
+        return new PiCounterCell(piCellId,
+                                 counterData.getPacketCount(),
+                                 counterData.getByteCount());
     }
 }
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 fa85056..a06d67e 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
@@ -43,7 +43,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiActionGroupMember;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
-import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCell;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
 import org.onosproject.net.pi.runtime.PiMeterCellConfig;
 import org.onosproject.net.pi.runtime.PiMeterCellId;
@@ -281,15 +281,15 @@
     }
 
     @Override
-    public CompletableFuture<List<PiCounterCellData>> readCounterCells(Set<PiCounterCellId> cellIds,
-                                                                       PiPipeconf pipeconf) {
+    public CompletableFuture<List<PiCounterCell>> readCounterCells(Set<PiCounterCellId> cellIds,
+                                                                   PiPipeconf pipeconf) {
         return supplyInContext(() -> doReadCounterCells(Lists.newArrayList(cellIds), pipeconf),
                                "readCounterCells-" + cellIds.hashCode());
     }
 
     @Override
-    public CompletableFuture<List<PiCounterCellData>> readAllCounterCells(Set<PiCounterId> counterIds,
-                                                                          PiPipeconf pipeconf) {
+    public CompletableFuture<List<PiCounterCell>> readAllCounterCells(Set<PiCounterId> counterIds,
+                                                                      PiPipeconf pipeconf) {
         return supplyInContext(() -> doReadAllCounterCells(Lists.newArrayList(counterIds), pipeconf),
                                "readAllCounterCells-" + counterIds.hashCode());
     }
@@ -559,6 +559,7 @@
                                 TableEntry.newBuilder()
                                         .setTableId(tableId)
                                         .setIsDefaultAction(defaultEntries)
+                                        .setCounterData(P4RuntimeOuterClass.CounterData.getDefaultInstance())
                                         .build())
                         .build())
                 .build());
@@ -651,21 +652,21 @@
         isClientMaster.set(isMaster);
     }
 
-    private List<PiCounterCellData> doReadAllCounterCells(
+    private List<PiCounterCell> doReadAllCounterCells(
             List<PiCounterId> counterIds, PiPipeconf pipeconf) {
         return doReadCounterEntities(
                 CounterEntryCodec.readAllCellsEntities(counterIds, pipeconf),
                 pipeconf);
     }
 
-    private List<PiCounterCellData> doReadCounterCells(
+    private List<PiCounterCell> doReadCounterCells(
             List<PiCounterCellId> cellIds, PiPipeconf pipeconf) {
         return doReadCounterEntities(
                 CounterEntryCodec.encodePiCounterCellIds(cellIds, pipeconf),
                 pipeconf);
     }
 
-    private List<PiCounterCellData> doReadCounterEntities(
+    private List<PiCounterCell> doReadCounterEntities(
             List<Entity> counterEntities, PiPipeconf pipeconf) {
 
         if (counterEntities.size() == 0) {
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 d5d909f..7e2df98 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
@@ -29,6 +29,7 @@
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionGroupMemberId;
 import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
 import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
@@ -40,6 +41,7 @@
 import org.slf4j.Logger;
 import p4.config.v1.P4InfoOuterClass;
 import p4.v1.P4RuntimeOuterClass.Action;
+import p4.v1.P4RuntimeOuterClass.CounterData;
 import p4.v1.P4RuntimeOuterClass.FieldMatch;
 import p4.v1.P4RuntimeOuterClass.TableAction;
 import p4.v1.P4RuntimeOuterClass.TableEntry;
@@ -249,6 +251,11 @@
             }
         }
 
+        // Counter.
+        if (piTableEntry.counter() != null) {
+            tableEntryMsgBuilder.setCounterData(encodeCounter(piTableEntry.counter()));
+        }
+
         return tableEntryMsgBuilder.build();
     }
 
@@ -281,6 +288,9 @@
         // Match key for field matches.
         piTableEntryBuilder.withMatchKey(decodeFieldMatchMsgs(tableEntryMsg.getMatchList(), tableInfo, browser));
 
+        // Counter.
+        piTableEntryBuilder.withCounterCellData(decodeCounter(tableEntryMsg.getCounterData()));
+
         return piTableEntryBuilder.build();
     }
 
@@ -505,4 +515,13 @@
         }
         return PiAction.builder().withId(id).withParameters(params).build();
     }
-}
+
+    static CounterData encodeCounter(PiCounterCellData piCounterCellData) {
+        return CounterData.newBuilder().setPacketCount(piCounterCellData.packets())
+                .setByteCount(piCounterCellData.bytes()).build();
+    }
+
+    static PiCounterCellData decodeCounter(CounterData counterData) {
+        return new PiCounterCellData(counterData.getPacketCount(), counterData.getByteCount());
+    }
+}
\ No newline at end of file
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 0799108..d9e5f9a 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
@@ -32,11 +32,13 @@
 import org.onosproject.net.pi.runtime.PiAction;
 import org.onosproject.net.pi.runtime.PiActionGroupId;
 import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiMatchKey;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import p4.v1.P4RuntimeOuterClass.Action;
+import p4.v1.P4RuntimeOuterClass.CounterData;
 import p4.v1.P4RuntimeOuterClass.TableEntry;
 
 import java.net.URL;
@@ -72,6 +74,9 @@
     private static final String ETHER_TYPE = "etherType";
     private static final String ECMP_GROUP_ID = "ecmp_group_id";
 
+    private static final long PACKETS = 10;
+    private static final long BYTES = 100;
+
     private final Random rand = new Random();
     private final URL p4InfoUrl = this.getClass().getResource("/test.p4info");
 
@@ -94,6 +99,7 @@
     private final PiActionId outActionId = PiActionId.of(SET_EGRESS_PORT);
     private final PiTableId tableId = PiTableId.of(TABLE_0);
     private final PiTableId ecmpTableId = PiTableId.of(TABLE_ECMP);
+    private final PiCounterCellData counterCellData = new PiCounterCellData(PACKETS, BYTES);
 
     private final PiTableEntry piTableEntry = PiTableEntry
             .builder()
@@ -111,6 +117,7 @@
                                 .build())
             .withPriority(1)
             .withCookie(2)
+            .withCounterCellData(counterCellData)
             .build();
 
     private final PiTableEntry piTableEntryWithoutAction = PiTableEntry
@@ -124,6 +131,7 @@
                                   .build())
             .withPriority(1)
             .withCookie(2)
+            .withCounterCellData(counterCellData)
             .build();
 
     private final PiTableEntry piTableEntryWithGroupAction = PiTableEntry
@@ -135,6 +143,7 @@
             .withAction(PiActionGroupId.of(1))
             .withPriority(1)
             .withCookie(2)
+            .withCounterCellData(counterCellData)
             .build();
 
     public TableEntryEncoderTest() throws ImmutableByteSequence.ByteSequenceTrimException {
@@ -198,6 +207,12 @@
         byte[] encodedActionParam = actionMsg.getParams(0).getValue().toByteArray();
         assertThat(encodedActionParam, is(portValue.asArray()));
 
+        // Counter
+        CounterData counterData = tableEntryMsg.getCounterData();
+        PiCounterCellData encodedCounterData = new PiCounterCellData(counterData.getPacketCount(),
+                                                                     counterData.getByteCount());
+        assertThat(encodedCounterData, is(counterCellData));
+
         // TODO: improve, assert other field match types (ternary, LPM)
     }
 
@@ -257,6 +272,12 @@
         // no action
         assertThat(tableEntryMsg.hasAction(), is(false));
 
+        // Counter
+        CounterData counterData = tableEntryMsg.getCounterData();
+        PiCounterCellData encodedCounterData = new PiCounterCellData(counterData.getPacketCount(),
+                                                                     counterData.getByteCount());
+        assertThat(encodedCounterData, is(counterCellData));
+
         // TODO: improve, assert other field match types (ternary, LPM)
     }
 }