[SDFAB-1177] Separate ingress and egress UpfCounter

Change-Id: I957754bc3f12d2e8f6d9d5748bb0b8c2b01a924c
(cherry picked from commit db0e125ac2515dbb711efdeebc3948b28e9e392b)
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
index eb41a04..c395009 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
@@ -16,34 +16,40 @@
 
 package org.onosproject.net.behaviour.upf;
 
-
 import com.google.common.annotations.Beta;
 
 import java.util.Objects;
+import java.util.Optional;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.behaviour.upf.UpfEntityType.COUNTER;
+import static org.onosproject.net.behaviour.upf.UpfEntityType.EGRESS_COUNTER;
+import static org.onosproject.net.behaviour.upf.UpfEntityType.INGRESS_COUNTER;
 
 /**
- * A structure for compactly passing UPF counter values for a given counter ID.
- * Contains four counts: Ingress Packets, Ingress Bytes, Egress Packets, Egress Bytes.
- * UpfCounter can be used ONLY on {@code apply} and {@code readAll} calls in the
- * {@link UpfDevice} interface.
+ * A structure for compactly passing UPF counter (ingress, egress or both) values
+ * for a given counter ID. Contains four counts: Ingress Packets, Ingress Bytes,
+ * Egress Packets, Egress Bytes. UpfCounter can be used ONLY on {@code apply}
+ * and {@code readAll} calls in the {@link UpfDevice} interface.
  */
 @Beta
 public final class UpfCounter implements UpfEntity {
     private final int cellId;
-    private final long ingressPkts;
-    private final long ingressBytes;
-    private final long egressPkts;
-    private final long egressBytes;
+    private final Long ingressPkts;
+    private final Long ingressBytes;
+    private final Long egressPkts;
+    private final Long egressBytes;
+    private final UpfEntityType type;
 
-    private UpfCounter(int cellId, long ingressPkts, long ingressBytes,
-                       long egressPkts, long egressBytes) {
+    private UpfCounter(int cellId, Long ingressPkts, Long ingressBytes,
+                       Long egressPkts, Long egressBytes, UpfEntityType type) {
         this.cellId = cellId;
         this.ingressPkts = ingressPkts;
         this.ingressBytes = ingressBytes;
         this.egressPkts = egressPkts;
         this.egressBytes = egressBytes;
+        this.type = type;
     }
 
     public static Builder builder() {
@@ -52,8 +58,19 @@
 
     @Override
     public String toString() {
-        return String.format("UpfStats(cell_id=%d, ingress=(%dpkts,%dbytes), egress=(%dpkts,%dbytes))",
-                cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
+        switch (this.type) {
+            case COUNTER:
+                return String.format("UpfStats(cell_id=%d, ingress=(%dpkts,%dbytes), egress=(%dpkts,%dbytes))",
+                                     cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
+            case INGRESS_COUNTER:
+                return String.format("UpfIngressCounter(cell_id=%d, packets=%d, bytes=%d))",
+                                     cellId, ingressPkts, ingressBytes);
+            case EGRESS_COUNTER:
+                return String.format("UpfEgressCounter(cell_id=%d, packets=%d, bytes=%d))",
+                                     cellId, egressPkts, egressBytes);
+            default:
+                throw new IllegalStateException("I should never reach this point!");
+        }
     }
 
     @Override
@@ -68,7 +85,7 @@
             return false;
         }
         UpfCounter that = (UpfCounter) object;
-        return this.cellId == that.cellId;
+        return this.cellId == that.cellId && this.type == that.type;
     }
 
     /**
@@ -80,16 +97,15 @@
      */
     public boolean exactlyEquals(UpfCounter that) {
         return this.equals(that) &&
-                this.ingressPkts == that.ingressPkts &&
-                this.ingressBytes == that.ingressBytes &&
-                this.egressPkts == that.egressPkts &&
-                this.egressBytes == that.egressBytes;
+                (this.ingressPkts == that.ingressPkts || this.ingressPkts.equals(that.ingressPkts)) &&
+                (this.ingressBytes == that.ingressBytes || this.ingressBytes.equals(that.ingressBytes)) &&
+                (this.egressPkts == that.egressPkts || this.egressPkts.equals(that.egressPkts)) &&
+                (this.egressBytes == that.egressBytes || this.egressBytes.equals(that.egressBytes));
     }
 
-
     @Override
     public int hashCode() {
-        return Objects.hash(cellId);
+        return Objects.hash(cellId, type);
     }
 
     /**
@@ -103,57 +119,91 @@
 
     /**
      * Get the number of packets that hit this counter in the dataplane ingress pipeline.
+     * Return a value only if the counter is of type {@code UpfEntityType.COUNTER}
+     * or {@code UpfEntityType.INGRESS_COUNTER}, otherwise an empty Optional.
      *
-     * @return ingress packet count
+     * @return ingress packet count or empty if this is of type {@code UpfEntityType.EGRESS_COUNTER}
      */
-    public long getIngressPkts() {
-        return ingressPkts;
+    public Optional<Long> getIngressPkts() {
+        return Optional.ofNullable(ingressPkts);
     }
 
     /**
      * Get the number of packets that hit this counter in the dataplane egress pipeline.
+     * Return a value only if the counter is of type {@code UpfEntityType.COUNTER}
+     * or {@code UpfEntityType.EGRESS_COUNTER}, otherwise an empty Optional.
      *
-     * @return egress packet count
+     * @return egress packet count or empty if this is of type {@code UpfEntityType.INGRESS_COUNTER}
      */
-    public long getEgressPkts() {
-        return egressPkts;
+    public Optional<Long> getEgressPkts() {
+        return Optional.ofNullable(egressPkts);
     }
 
     /**
      * Get the number of packet bytes that hit this counter in the dataplane ingress pipeline.
+     * Return value only if the counter is of type {{@code UpfEntityType.COUNTER}
+     * or {@code UpfEntityType.INGRESS_COUNTER}, otherwise an empty Optional.
      *
-     * @return ingress byte count
+     * @return ingress byte count or empty if this is of type {@code UpfEntityType.EGRESS_COUNTER}
      */
-    public long getIngressBytes() {
-        return ingressBytes;
+    public Optional<Long> getIngressBytes() {
+        return Optional.ofNullable(ingressBytes);
     }
 
     /**
      * Get the number of packet bytes that hit this counter in the dataplane egress pipeline.
+     * Return a value only if the counter is of type {@code UpfEntityType.COUNTER}
+     * or {@code UpfEntityType.EGRESS_COUNTER}, otherwise an empty Optional.
      *
-     * @return egress byte count
+     * @return egress byte count or empty if this is of type {@code UpfEntityType.INGRESS_COUNTER}
      */
-    public long getEgressBytes() {
-        return egressBytes;
+    public Optional<Long> getEgressBytes() {
+        return Optional.ofNullable(egressBytes);
     }
 
     @Override
     public UpfEntityType type() {
-        return UpfEntityType.COUNTER;
+        return type;
     }
 
+    /**
+     * Sum the content of the given UpfCounter to the counter values contained
+     * in this instance.
+     *
+     * @param that The UpfCounter to sum to this instance
+     * @return a new UpfCounter instance with sum counters.
+     * @throws IllegalArgumentException if the given UpfCounter is not referring
+     *                                  to the same type and id as this
+     */
+    public UpfCounter sum(UpfCounter that) throws IllegalArgumentException {
+        if (!this.equals(that)) {
+            throw new IllegalArgumentException(
+                    "The given UpfCounter is not of the same type or refers to a different index");
+        }
+        UpfCounter.Builder builder = UpfCounter.builder().withCellId(this.getCellId());
+        if (this.type.equals(UpfEntityType.COUNTER) || this.type.equals(UpfEntityType.INGRESS_COUNTER)) {
+            builder.setIngress(this.ingressPkts + that.ingressPkts,
+                               this.ingressBytes + that.ingressBytes);
+        }
+        if (this.type.equals(UpfEntityType.COUNTER) || this.type.equals(UpfEntityType.EGRESS_COUNTER)) {
+            builder.setEgress(this.egressPkts + that.egressPkts,
+                              this.egressBytes + that.egressBytes);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Builder for UpfCounter.
+     */
     public static class Builder {
         private Integer cellId;
-        private long ingressPkts;
-        private long ingressBytes;
-        private long egressPkts;
-        private long egressBytes;
+        private Long ingressPkts;
+        private Long ingressBytes;
+        private Long egressPkts;
+        private Long egressBytes;
+        private UpfEntityType type = COUNTER;
 
         public Builder() {
-            this.ingressPkts = 0;
-            this.ingressBytes = 0;
-            this.egressPkts = 0;
-            this.egressBytes = 0;
         }
 
         /**
@@ -193,9 +243,51 @@
             return this;
         }
 
+        /**
+         * Set the counter as ingress only counter.
+         *
+         * @return This builder
+         */
+        public Builder isIngressCounter() {
+            this.type = INGRESS_COUNTER;
+            return this;
+        }
+
+        /**
+         * Set the counter as egress only counter.
+         *
+         * @return This builder
+         */
+        public Builder isEgressCounter() {
+            this.type = EGRESS_COUNTER;
+            return this;
+        }
+
         public UpfCounter build() {
             checkNotNull(cellId, "CellID must be provided");
-            return new UpfCounter(cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
+            switch (type) {
+                case INGRESS_COUNTER:
+                    checkArgument(this.ingressBytes != null && this.ingressPkts != null,
+                                  "Ingress counter values must be provided");
+                    this.egressBytes = null;
+                    this.egressPkts = null;
+                    break;
+                case EGRESS_COUNTER:
+                    checkArgument(this.egressBytes != null && this.egressPkts != null,
+                                  "Egress counter values must be provided");
+                    this.ingressBytes = null;
+                    this.ingressPkts = null;
+                    break;
+                case COUNTER:
+                    checkArgument(this.ingressBytes != null && this.ingressPkts != null &&
+                                          this.egressBytes != null && this.egressPkts != null,
+                                  "Ingress and egress counter values must be provided");
+                    break;
+                default:
+                    // I should never reach this point
+                    throw new IllegalArgumentException("I should never reach this point!");
+            }
+            return new UpfCounter(cellId, ingressPkts, ingressBytes, egressPkts, egressBytes, type);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java
index 2b17421..738d6b6 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfDevice.java
@@ -52,27 +52,31 @@
     Collection<? extends UpfEntity> readAll(UpfEntityType entityType) throws UpfProgrammableException;
 
     /**
-     * Reads the given UPF counter ID from the UPF-programmable device.
+     * Reads the given UPF counter type and index from the UPF-programmable device.
      *
-     * @param counterId The counter ID from which to read.
+     * @param counterIdx The counter index from which to read.
+     * @param type       {@link UpfEntityType} of UPF counter to read
+     *                   ({@code COUNTER, INGRESS_COUNTER, EGRESS_COUNTER})
      * @return The content of the UPF counter.
      * @throws UpfProgrammableException if the counter ID is out of bounds.
      */
-    UpfCounter readCounter(int counterId) throws UpfProgrammableException;
+    UpfCounter readCounter(int counterIdx, UpfEntityType type) throws UpfProgrammableException;
 
     /**
-     * Reads the UPF counter contents for all indices that are valid on the
-     * UPF-programmable device. {@code maxCounterId} parameter is used to limit
-     * the number of counters retrieved from the UPF. If the limit given is
+     * Reads the given UPF counter type contents for all indices that are valid
+     * on the UPF-programmable device. {@code maxCounterId} parameter is used to
+     * limit the number of counters retrieved from the UPF. If the limit given is
      * larger than the physical limit, the physical limit will be used.
      * A limit of -1 removes limitations, and it is equivalent of calling
-     * {@link #readAll(UpfEntityType)} passing the {@code COUNTER} {@link UpfEntityType}.
+     * {@link #readAll(UpfEntityType)} passing the given {@link UpfEntityType}.
      *
-     * @param maxCounterId Maximum counter ID to retrieve from the UPF device.
+     * @param maxCounterIdx Maximum counter index to retrieve from the UPF device.
+     * @param type          {@link UpfEntityType} of UPF counter to read
+     *                      ({@code COUNTER, INGRESS_COUNTER, EGRESS_COUNTER})
      * @return A collection of UPF counters for all valid hardware counter cells.
      * @throws UpfProgrammableException if the counters are unable to be read.
      */
-    Collection<UpfCounter> readCounters(long maxCounterId) throws UpfProgrammableException;
+    Collection<UpfCounter> readCounters(long maxCounterIdx, UpfEntityType type) throws UpfProgrammableException;
 
     /**
      * Deletes the given UPF entity from the UPF-programmable device.
@@ -97,6 +101,7 @@
      * the UPF-programmable device. For entities that have a direction,returns
      * the total amount of entities including both the downlink and the uplink
      * directions.
+     *
      * @param entityType The type of UPF programmable entities to retrieve the size from.
      * @return The total number of supported UPF entities.
      * @throws UpfProgrammableException if the operation is not supported on the given UPF entity.
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java
index 07890f7..e13a3c8 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java
@@ -29,6 +29,8 @@
     SESSION_DOWNLINK("session_downlink"),
     SESSION_UPLINK("session_downlink"),
     TUNNEL_PEER("tunnel_peer"),
+    INGRESS_COUNTER("ingress_counter"),
+    EGRESS_COUNTER("egress_counter"),
     COUNTER("counter"),
     APPLICATION("application"),
     SESSION_METER("session_meter"),