[SDFAB-831] Add UPF meters to UpfProgrammable APIs

UPF meters can be of type session or application.
Also, add meter index to sessions and terminations UPF entities.

Change-Id: I8babfca35341a21b234d8eb6edaa2e1c02684210
(cherry picked from commit b25299afaf824a8d352297224e5b9a1285901d00)
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfMeter.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfMeter.java
new file mode 100644
index 0000000..1a3e799
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfMeter.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2022-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.behaviour.upf;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.onosproject.net.meter.Band;
+import org.onosproject.net.meter.DefaultBand;
+
+import java.util.Map;
+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.APPLICATION_METER;
+import static org.onosproject.net.behaviour.upf.UpfEntityType.SESSION_METER;
+import static org.onosproject.net.meter.Band.Type.MARK_RED;
+import static org.onosproject.net.meter.Band.Type.MARK_YELLOW;
+
+/**
+ * A structure representing a UPF meter, either for metering session (UE) or
+ * application traffic.
+ * UPF meters represent PFCP QER MBR and GBR information.
+ * UPF meters of type session support only the peak band.
+ * UPF meters of type application support both peak and committed bands.
+ */
+public final class UpfMeter implements UpfEntity {
+    private final int cellId;
+    private final ImmutableMap<Band.Type, Band> meterBands;
+    private final UpfEntityType type;
+
+    private UpfMeter(int cellId, Map<Band.Type, Band> meterBands, UpfEntityType type) {
+        this.cellId = cellId;
+        this.meterBands = ImmutableMap.copyOf(meterBands);
+        this.type = type;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("UpfMeter(type=%s, index=%d, committed=%s, peak=%s)",
+                             type, cellId, committedBand().orElse(null),
+                             peakBand().orElse(null));
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+
+        if (object == null) {
+            return false;
+        }
+
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        UpfMeter that = (UpfMeter) object;
+        return this.type.equals(that.type) &&
+                this.cellId == that.cellId &&
+                this.meterBands.equals(that.meterBands);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, cellId, meterBands);
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return this.type;
+    }
+
+    /**
+     * Get the meter cell index of this meter.
+     *
+     * @return the cell index
+     */
+    public int cellId() {
+        return this.cellId;
+    }
+
+    /**
+     * Get the committed band of this meter.
+     *
+     * @return the committed band, Empty if none
+     */
+    public Optional<Band> committedBand() {
+        return Optional.ofNullable(meterBands.getOrDefault(MARK_YELLOW, null));
+    }
+
+    /**
+     * Get the peak band of this meter.
+     *
+     * @return the peak band, Empty if none
+     */
+    public Optional<Band> peakBand() {
+        return Optional.ofNullable(meterBands.getOrDefault(MARK_RED, null));
+    }
+
+    /**
+     * Check if this UPF meter is for sessions (UE) traffic.
+     *
+     * @return true if the meter is for session traffic
+     */
+    public boolean isSession() {
+        return type.equals(SESSION_METER);
+    }
+
+    /**
+     * Check if this UPF meter is for application traffic.
+     *
+     * @return true if the meter is for application traffic
+     */
+    public boolean isApplication() {
+        return type.equals(APPLICATION_METER);
+    }
+
+    /**
+     * Check if this UPF meter is a reset.
+     *
+     * @return true if this represents a meter reset.
+     */
+    public boolean isReset() {
+        return meterBands.isEmpty();
+    }
+
+    /**
+     * Return a session UPF meter with no bands. Used to reset the meter.
+     *
+     * @param cellId the meter cell index of this meter
+     * @return a UpfMeter of type session with no bands
+     */
+    public static UpfMeter resetSession(int cellId) {
+        return new UpfMeter(cellId, Maps.newHashMap(), SESSION_METER);
+    }
+
+    /**
+     * Return an application UPF meter with no bands. Used to reset the meter.
+     *
+     * @param cellId the meter cell index of this meter
+     * @return a UpfMeter of type application with no bands
+     */
+    public static UpfMeter resetApplication(int cellId) {
+        return new UpfMeter(cellId, Maps.newHashMap(), APPLICATION_METER);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Builder of UpfMeter object. Use {@link #resetApplication(int)} and
+     * {@link #resetSession(int)} to reset the meter config.
+     */
+    public static class Builder {
+        private Integer cellId = null;
+        private Map<Band.Type, Band> bands = Maps.newHashMap();
+        private UpfEntityType type;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the meter cell index of this meter.
+         *
+         * @param cellId the meter cell index
+         * @return this builder object
+         */
+        public Builder setCellId(int cellId) {
+            this.cellId = cellId;
+            return this;
+        }
+
+        /**
+         * Set the committed band of this meter.
+         * Valid only for meter of type application.
+         *
+         * @param cir    the Committed Information Rate in bytes/s
+         * @param cburst the Committed Burst in bytes
+         * @return this builder object
+         */
+        public Builder setCommittedBand(long cir, long cburst) {
+            this.bands.put(MARK_YELLOW,
+                           DefaultBand.builder()
+                                   .ofType(MARK_YELLOW)
+                                   .withRate(cir)
+                                   .burstSize(cburst)
+                                   .build()
+            );
+            return this;
+        }
+
+        /**
+         * Set the peak band of this meter.
+         *
+         * @param pir    the Peak Information Rate in bytes/s
+         * @param pburst the Peak Burst in bytes
+         * @return this builder object
+         */
+        public Builder setPeakBand(long pir, long pburst) {
+            this.bands.put(MARK_RED,
+                           DefaultBand.builder()
+                                   .ofType(MARK_RED)
+                                   .withRate(pir)
+                                   .burstSize(pburst)
+                                   .build()
+            );
+            return this;
+        }
+
+        /**
+         * Make this meter a session meter.
+         *
+         * @return this builder object
+         */
+        public Builder setSession() {
+            this.type = SESSION_METER;
+            return this;
+        }
+
+        /**
+         * Make this meter an application meter.
+         *
+         * @return this builder object
+         */
+        public Builder setApplication() {
+            this.type = APPLICATION_METER;
+            return this;
+        }
+
+        public UpfMeter build() {
+            checkNotNull(type, "A meter type must be assigned");
+            switch (type) {
+                case SESSION_METER:
+                    checkArgument(!bands.containsKey(MARK_YELLOW),
+                                  "Committed band can not be provided for session meter!");
+                    break;
+                case APPLICATION_METER:
+                    checkArgument((bands.containsKey(MARK_YELLOW) && bands.containsKey(MARK_RED)) || bands.isEmpty(),
+                                  "Bands (committed and peak) must be provided together or not at all!");
+                    break;
+                default:
+                    // I should never reach this point
+                    throw new IllegalArgumentException("Invalid meter type, I should never reach this point");
+            }
+            checkNotNull(cellId, "Meter cell ID must be provided!");
+            return new UpfMeter(cellId, bands, type);
+        }
+    }
+}