BMP statistics record decoder

Change-Id: Iafee32f83e97fd1fb2b29433eaf8b3774c8dd2ac
diff --git a/apps/bgpmonitoring/app/src/main/java/org/onosproject/bgpmonitoring/type/BmpStatsReport.java b/apps/bgpmonitoring/app/src/main/java/org/onosproject/bgpmonitoring/type/BmpStatsReport.java
new file mode 100644
index 0000000..54f46c1
--- /dev/null
+++ b/apps/bgpmonitoring/app/src/main/java/org/onosproject/bgpmonitoring/type/BmpStatsReport.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2024-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.bgpmonitoring.type;
+
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.IntStream;
+import java.util.function.BiPredicate;
+import java.nio.ByteBuffer;
+
+import org.onosproject.bgpmonitoring.StatsMessage;
+import org.onosproject.bgpmonitoring.BmpStats;
+import org.onosproject.bgpmonitoring.PerPeer;
+import org.onosproject.bgpmonitoring.BmpParseException;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * An ongoing dump of statistics that can be used
+ * by the monitoring station as a high-level indication of the
+ * activity going on in the router.
+ * <p>
+ * These messages contain information that could be used by the
+ * monitoring station to observe interesting events that occur on the
+ * router.
+ * <p>
+ * Transmission of SR messages could be timer triggered or event driven
+ * (for example, when a significant event occurs or a threshold is
+ * reached).  This specification does not impose any timing restrictions
+ * on when and on what event these reports have to be transmitted.  It
+ * is left to the implementation to determine transmission timings --
+ * however, configuration control should be provided of the timer and/or
+ * threshold values.  This document only specifies the form and content
+ * of SR messages.
+ * <p>
+ * Following the common BMP header and per-peer header is a 4-byte field
+ * that indicates the number of counters in the stats message where each
+ * counter is encoded as a TLV.
+ * <p>
+ * Stat Type = 1: (32-bit Counter) Number of (known) duplicate prefix
+ * advertisements.
+ * <p>
+ * Stat Type = 2: (32-bit Counter) Number of (known) duplicate
+ * withdraws.
+ * <p>
+ * Stat Type = 3: (32-bit Counter) Number of updates invalidated due
+ * to CLUSTER_LIST loop.
+ * <p>
+ * Stat Type = 4: (32-bit Counter) Number of updates invalidated due
+ * to AS_PATH loop.
+ * <p>
+ * Stat Type = 5: (32-bit Counter) Number of updates invalidated due
+ * to ORIGINATOR_ID.
+ * <p>
+ * Stat Type = 6: (32-bit Counter) Number of updates invalidated due
+ * to AS_CONFED loop.
+ * <p>
+ * Stat Type = 7: (64-bit Gauge) Number of routes in Adj-RIBs-In.
+ * <p>
+ * Stat Type = 8: (64-bit Gauge) Number of routes in Loc-RIB.
+ * <p>
+ * Stat Type = 9: Number of routes in per-AFI/SAFI Adj-RIB-In.  The
+ * value is structured as: 2-byte Address Family Identifier (AFI),
+ * 1-byte Subsequent Address Family Identifier (SAFI), followed by a
+ * 64-bit Gauge.
+ * <p>
+ * Stat Type = 10: Number of routes in per-AFI/SAFI Loc-RIB.  The
+ * value is structured as: 2-byte AFI, 1-byte SAFI, followed by a
+ * 64-bit Gauge.
+ * <p>
+ * Stat Type = 11: (32-bit Counter) Number of updates subjected to
+ * treat-as-withdraw treatment [RFC7606].
+ * <p>
+ * Stat Type = 12: (32-bit Counter) Number of prefixes subjected to
+ * treat-as-withdraw treatment [RFC7606].
+ * <p>
+ * Stat Type = 13: (32-bit Counter) Number of duplicate update
+ * messages received.
+ * <p>
+ * Although the current specification only specifies 4-byte counters and
+ * 8-byte gauges as "Stat Data", this does not preclude future versions
+ * from incorporating more complex TLV-type "Stat Data" (for example,
+ * one that can carry prefix-specific data).  SR messages are optional.
+ * However, if an SR message is transmitted, at least one statistic MUST
+ * be carried in it.
+ */
+public final class BmpStatsReport extends StatsMessage {
+
+
+/*
+      0                   1                   2                   3
+      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |                        Stats Count                            |
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+     Each counter is encoded as follows:
+
+      0                   1                   2                   3
+      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |         Stat Type             |          Stat Len             |
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     |                        Stat Data                              |
+     ~                                                               ~
+     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+    private PerPeer perPeer;
+    private int statsCount;
+    private List<BmpStats> stats;
+
+    private BmpStatsReport(Builder builder) {
+        this.perPeer = builder.perPeer;
+        this.statsCount = builder.statsCount;
+        this.stats = builder.stats;
+
+    }
+
+    /**
+     * Returns number of BMP statistics records.
+     *
+     * @return number of BMP statistics records
+     */
+    public int getStatsCount() {
+        return statsCount;
+    }
+
+    /**
+     * Returns BGP statistics.
+     *
+     * @return BGP statistics
+     */
+    public List<BmpStats> getStats() {
+        return stats;
+    }
+
+    /**
+     * Returns BMP Peer Header of BMP Message.
+     *
+     * @return BMP Peer Header of BMP Message
+     */
+    @Override
+    public PerPeer getPerPeer() {
+        return perPeer;
+    }
+
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        BmpStatsReport that = (BmpStatsReport) o;
+        return statsCount == that.statsCount &&
+                Objects.equals(stats, that.stats);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(statsCount, stats);
+    }
+
+
+    /**
+     * Data deserializer function for BMP statistics.
+     *
+     * @return data deserializer function
+     */
+    public static Deserializer<BmpStatsReport> deserializer() {
+        return (data, offset, length) -> {
+            BiPredicate<ByteBuffer, Integer> isValidBuffer = (b, l)
+                    -> b.hasRemaining() && b.remaining() >= l;
+
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            if (!isValidBuffer.test(bb,
+                    STATS_REPORT_HEADER_MIN_LENGTH +
+                            PerPeerPacket.PEER_HEADER_MIN_LENGTH +
+                            BmpStatistics.STATISTICS_HEADER_MIN_LENGTH)) {
+                throw new BmpParseException("Invalid bmp statistics message buffer size.");
+            }
+
+
+            byte[] perPeer = new byte[PerPeerPacket.PEER_HEADER_MIN_LENGTH];
+            bb.get(perPeer);
+
+            Builder builder = new Builder()
+                    .perPeer(PerPeerPacket.deserializer().deserialize(perPeer,
+                            0, PerPeerPacket.PEER_HEADER_MIN_LENGTH))
+                    .statsCount(bb.getInt());
+
+            IntStream.range(0, builder.statsCount)
+                    .forEach(index -> {
+                        if (bb.hasRemaining()) {
+                            int statsType = bb.getShort();
+                            int newLength = bb.getShort();
+                            bb.position(bb.position() - BmpStatistics.STATISTICS_HEADER_MIN_LENGTH);
+                            int statsLength = newLength + BmpStatistics.STATISTICS_HEADER_MIN_LENGTH;
+                            if (bb.remaining() < statsLength) {
+                                throw new BmpParseException("Invalid bmp statistics message buffer size.");
+                            }
+                            byte[] statsBytes = new byte[statsLength];
+                            bb.get(statsBytes);
+                            try {
+                                builder.stats((BmpStats) BmpStatistics.deserializer()
+                                        .deserialize(statsBytes, 0, statsLength));
+                            } catch (DeserializationException ex) {
+                                throw new BmpParseException(ex);
+                            }
+                        }
+                    });
+
+            return builder.build();
+        };
+    }
+
+
+    @Override
+    public String toString() {
+
+        return MoreObjects.toStringHelper(getClass())
+                .add("perPeer", perPeer)
+                .add("statsCount", statsCount)
+                .add("stats", stats)
+                .toString();
+    }
+
+    /**
+     * Builder for BMP statistics message.
+     */
+    private static class Builder {
+
+        private PerPeer perPeer;
+
+        private int statsCount;
+
+        private List<BmpStats> stats = Lists.newArrayList();
+
+
+        /**
+         * Setter bmp per peer header.
+         *
+         * @param perPeer bmp per peer header.
+         * @return this class builder.
+         */
+        public Builder perPeer(PerPeer perPeer) {
+            this.perPeer = perPeer;
+            return this;
+        }
+
+
+        /**
+         * Setter bmp statistics record count.
+         *
+         * @param statsCount bmp statistics record count.
+         * @return this class builder.
+         */
+        public Builder statsCount(int statsCount) {
+            this.statsCount = statsCount;
+            return this;
+        }
+
+        /**
+         * Setter bmp statistics records.
+         *
+         * @param stats bmp statistics records.
+         * @return this class builder.
+         */
+        public Builder stats(List<BmpStats> stats) {
+            this.stats = stats;
+            return this;
+        }
+
+        /**
+         * Setter bmp statistics record.
+         *
+         * @param stats bmp statistics record.
+         * @return this class builder.
+         */
+        public Builder stats(BmpStats stats) {
+            this.stats.add(stats);
+            return this;
+        }
+
+        /**
+         * Checks arguments for bmp statistics record.
+         */
+        private void checkArguments() {
+            checkState(perPeer != null, "Invalid bmp statistics per peer buffer.");
+            checkState(statsCount != 0, "Invalid bmp statistics records.");
+        }
+
+        /**
+         * Builds BMP statistics message.
+         *
+         * @return BMP statistics message.
+         */
+        public BmpStatsReport build() {
+            checkArguments();
+            return new BmpStatsReport(this);
+        }
+    }
+
+}