[SDFAB-187] Add UpfProgrammable interface in ONOS core

Change-Id: Icef23a14015bb0ebe33ebe57eadecaaadc8eebd3
(cherry picked from commit 5e66f98ebb5541ba7c7fbe2b7b7cb5f7beb8d885)
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java
new file mode 100644
index 0000000..3cd7c8c
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2021-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 org.onlab.packet.Ip4Address;
+import org.onlab.util.ImmutableByteSequence;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A single Forwarding Action Rule (FAR), an entity described in the 3GPP
+ * specifications (although that does not mean that this class is 3GPP
+ * compliant). An instance of this class will be generated by a logical switch
+ * write request to the database-style FAR P4 table, and the resulting instance
+ * should contain all the information needed to reproduce that logical switch
+ * FAR in the event of a client read request. The instance should also contain
+ * sufficient information (or expose the means to retrieve such information) to
+ * generate the corresponding dataplane forwarding state that implements the FAR.
+ */
+public final class ForwardingActionRule {
+    // Match Keys
+    private final ImmutableByteSequence sessionId;  // The PFCP session identifier that created this FAR
+    private final int farId;  // PFCP session-local identifier for this FAR
+    // Action parameters
+    private final boolean notifyFlag;  // Should this FAR notify the control plane when it sees a packet?
+    private final boolean dropFlag;
+    private final boolean bufferFlag;
+    private final GtpTunnel tunnel;  // The GTP tunnel that this FAR should encapsulate packets with (if downlink)
+
+    private static final int SESSION_ID_BITWIDTH = 96;
+
+    private ForwardingActionRule(ImmutableByteSequence sessionId, Integer farId,
+                                 boolean notifyFlag, GtpTunnel tunnel, boolean dropFlag, boolean bufferFlag) {
+        this.sessionId = sessionId;
+        this.farId = farId;
+        this.notifyFlag = notifyFlag;
+        this.tunnel = tunnel;
+        this.dropFlag = dropFlag;
+        this.bufferFlag = bufferFlag;
+    }
+
+    /**
+     * Return a new instance of this FAR with the action parameters stripped, leaving only the match keys.
+     *
+     * @return a new FAR with only match keys
+     */
+    public ForwardingActionRule withoutActionParams() {
+        return ForwardingActionRule.builder()
+                .setFarId(farId)
+                .withSessionId(sessionId)
+                .build();
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Return a string representing the dataplane action applied by this FAR.
+     *
+     * @return a string representing the FAR action
+     */
+    public String actionString() {
+        String actionName;
+        String actionParams = "";
+        if (dropFlag) {
+            actionName = "Drop";
+        } else if (bufferFlag) {
+            actionName = "Buffer";
+        } else if (tunnel != null) {
+            actionName = "Encap";
+            actionParams = String.format("Src=%s, SPort=%d, TEID=%s, Dst=%s",
+                    tunnel.src().toString(), tunnel.srcPort(), tunnel.teid().toString(), tunnel.dst().toString());
+        } else {
+            actionName = "Forward";
+        }
+        if (notifyFlag) {
+            actionName += "+NotifyCP";
+        }
+
+        return String.format("%s(%s)", actionName, actionParams);
+    }
+
+    @Override
+    public String toString() {
+        String matchKeys = String.format("ID=%d, SEID=%s", farId, sessionId.toString());
+        String actionString = actionString();
+
+        return String.format("FAR{Match(%s) -> %s}", matchKeys, actionString);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        ForwardingActionRule that = (ForwardingActionRule) obj;
+
+        // Safe comparisons between potentially null objects
+        return (this.dropFlag == that.dropFlag &&
+                this.bufferFlag == that.bufferFlag &&
+                this.notifyFlag == that.notifyFlag &&
+                this.farId == that.farId &&
+                Objects.equals(this.tunnel, that.tunnel) &&
+                Objects.equals(this.sessionId, that.sessionId));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(sessionId, farId, notifyFlag, tunnel, dropFlag, bufferFlag);
+    }
+
+    /**
+     * Get the ID of the PFCP Session that produced this FAR.
+     *
+     * @return PFCP session ID
+     */
+    public ImmutableByteSequence sessionId() {
+        return sessionId;
+    }
+
+    /**
+     * Get the PFCP session-local ID of the FAR that should apply to packets that match this PDR.
+     *
+     * @return PFCP session-local FAR ID
+     */
+    public int farId() {
+        return farId;
+    }
+
+    /**
+     * True if this FAR does not drop packets.
+     *
+     * @return true if FAR is forwards
+     */
+    public boolean forwards() {
+        return !dropFlag;
+    }
+
+    /**
+     * True if this FAR encapsulates packets in a GTP tunnel, and false otherwise.
+     *
+     * @return true is FAR encapsulates
+     */
+    public boolean encaps() {
+        return tunnel != null;
+    }
+
+    /**
+     * Returns true if this FAR drops packets, and false otherwise.
+     *
+     * @return true if this FAR drops
+     */
+    public boolean drops() {
+        return dropFlag;
+    }
+
+    /**
+     * Returns true if this FAR notifies the control plane on receiving a packet, and false otherwise.
+     *
+     * @return true if this FAR notifies the cp
+     */
+    public boolean notifies() {
+        return notifyFlag;
+    }
+
+
+    /**
+     * Returns true if this FAR buffers incoming packets, and false otherwise.
+     *
+     * @return true if this FAR buffers
+     */
+    public boolean buffers() {
+        return bufferFlag;
+    }
+
+    /**
+     * A description of the tunnel that this FAR will encapsulate packets with, if it is a downlink FAR. If the FAR
+     * is uplink, there will be no such tunnel and this method wil return null.
+     *
+     * @return A GtpTunnel instance containing a tunnel sourceIP, destIP, and GTPU TEID, or null if the FAR is uplink.
+     */
+    public GtpTunnel tunnel() {
+        return tunnel;
+    }
+
+    /**
+     * Get the source UDP port of the GTP tunnel that this FAR will encapsulate packets with.
+     *
+     * @return GTP tunnel source UDP port
+     */
+    public Short tunnelSrcPort() {
+        return tunnel != null ? tunnel.srcPort() : null;
+    }
+
+    /**
+     * Get the source IP of the GTP tunnel that this FAR will encapsulate packets with.
+     *
+     * @return GTP tunnel source IP
+     */
+    public Ip4Address tunnelSrc() {
+        if (tunnel == null) {
+            return null;
+        }
+        return tunnel.src();
+    }
+
+    /**
+     * Get the destination IP of the GTP tunnel that this FAR will encapsulate packets with.
+     *
+     * @return GTP tunnel destination IP
+     */
+    public Ip4Address tunnelDst() {
+        if (tunnel == null) {
+            return null;
+        }
+        return tunnel.dst();
+    }
+
+    /**
+     * Get the identifier of the GTP tunnel that this FAR will encapsulate packets with.
+     *
+     * @return GTP tunnel ID
+     */
+    public ImmutableByteSequence teid() {
+        if (tunnel == null) {
+            return null;
+        }
+        return tunnel.teid();
+    }
+
+    public static class Builder {
+        private ImmutableByteSequence sessionId = null;
+        private Integer farId = null;
+        private GtpTunnel tunnel = null;
+        private boolean dropFlag = false;
+        private boolean bufferFlag = false;
+        private boolean notifyCp = false;
+
+        public Builder() {
+        }
+
+        /**
+         * Set the ID of the PFCP session that created this FAR.
+         *
+         * @param sessionId PFC session ID
+         * @return This builder object
+         */
+        public Builder withSessionId(ImmutableByteSequence sessionId) {
+            this.sessionId = sessionId;
+            return this;
+        }
+
+        /**
+         * Set the ID of the PFCP session that created this FAR.
+         *
+         * @param sessionId PFC session ID
+         * @return This builder object
+         */
+        public Builder withSessionId(long sessionId) {
+            try {
+                this.sessionId = ImmutableByteSequence.copyFrom(sessionId).fit(SESSION_ID_BITWIDTH);
+            } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+                // This error is literally impossible
+            }
+            return this;
+        }
+
+        /**
+         * Set the PFCP Session-local ID of this FAR.
+         *
+         * @param farId PFCP session-local FAR ID
+         * @return This builder object
+         */
+        public Builder setFarId(int farId) {
+            this.farId = farId;
+            return this;
+        }
+
+        /**
+         * Make this FAR forward incoming packets.
+         *
+         * @param flag the flag value to set
+         * @return This builder object
+         */
+        public Builder setForwardFlag(boolean flag) {
+            this.dropFlag = !flag;
+            return this;
+        }
+
+        /**
+         * Make this FAR drop incoming packets.
+         *
+         * @param flag the flag value to set
+         * @return This builder object
+         */
+        public Builder setDropFlag(boolean flag) {
+            this.dropFlag = flag;
+            return this;
+        }
+
+        /**
+         * Make this FAR buffer incoming packets.
+         *
+         * @param flag the flag value to set
+         * @return This builder object
+         */
+        public Builder setBufferFlag(boolean flag) {
+            this.bufferFlag = flag;
+            return this;
+        }
+
+        /**
+         * Set a flag specifying if the control plane should be notified when this FAR is hit.
+         *
+         * @param notifyCp true if FAR notifies control plane
+         * @return This builder object
+         */
+        public Builder setNotifyFlag(boolean notifyCp) {
+            this.notifyCp = notifyCp;
+            return this;
+        }
+
+        /**
+         * Set the GTP tunnel that this FAR should encapsulate packets with.
+         *
+         * @param tunnel GTP tunnel
+         * @return This builder object
+         */
+        public Builder setTunnel(GtpTunnel tunnel) {
+            this.tunnel = tunnel;
+            return this;
+        }
+
+        /**
+         * Set the unidirectional GTP tunnel that this FAR should encapsulate packets with.
+         *
+         * @param src  GTP tunnel source IP
+         * @param dst  GTP tunnel destination IP
+         * @param teid GTP tunnel ID
+         * @return This builder object
+         */
+        public Builder setTunnel(Ip4Address src, Ip4Address dst, ImmutableByteSequence teid) {
+            return this.setTunnel(GtpTunnel.builder()
+                    .setSrc(src)
+                    .setDst(dst)
+                    .setTeid(teid)
+                    .build());
+        }
+
+        /**
+         * Set the unidirectional GTP tunnel that this FAR should encapsulate packets with.
+         *
+         * @param src     GTP tunnel source IP
+         * @param dst     GTP tunnel destination IP
+         * @param teid    GTP tunnel ID
+         * @param srcPort GTP tunnel UDP source port (destination port is hardcoded as 2152)
+         * @return This builder object
+         */
+        public Builder setTunnel(Ip4Address src, Ip4Address dst, ImmutableByteSequence teid, short srcPort) {
+            return this.setTunnel(GtpTunnel.builder()
+                    .setSrc(src)
+                    .setDst(dst)
+                    .setTeid(teid)
+                    .setSrcPort(srcPort)
+                    .build());
+        }
+
+        public ForwardingActionRule build() {
+            // All match keys are required
+            checkNotNull(sessionId, "Session ID is required");
+            checkNotNull(farId, "FAR ID is required");
+            return new ForwardingActionRule(sessionId, farId, notifyCp, tunnel, dropFlag, bufferFlag);
+        }
+    }
+}