Refactor of UpfProgrammable APIs

Change-Id: I792659ad4a163d7115d7320bb33c11534edd484a
Signed-off-by: Daniele Moro <daniele@opennetworking.org>
(cherry picked from commit a57652d92bdd01b1e77bffbac78a44f96fb385f3)
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
deleted file mode 100644
index bfed1ce..0000000
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/ForwardingActionRule.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * 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);
-        }
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnel.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnel.java
deleted file mode 100644
index bc262c7..0000000
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnel.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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 structure representing a unidirectional GTP tunnel.
- */
-public final class GtpTunnel {
-    private final Ip4Address src;  // The source address of the unidirectional tunnel
-    private final Ip4Address dst;  // The destination address of the unidirectional tunnel
-    private final ImmutableByteSequence teid;  // Tunnel Endpoint Identifier
-    private final short srcPort;  // Tunnel destination port, default 2152
-
-    private GtpTunnel(Ip4Address src, Ip4Address dst, ImmutableByteSequence teid,
-                      Short srcPort) {
-        this.src = src;
-        this.dst = dst;
-        this.teid = teid;
-        this.srcPort = srcPort;
-    }
-
-    public static GtpTunnelBuilder builder() {
-        return new GtpTunnelBuilder();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("GTP-Tunnel(%s -> %s, TEID:%s)",
-                             src.toString(), dst.toString(), teid.toString());
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        GtpTunnel that = (GtpTunnel) obj;
-
-        return (this.src.equals(that.src) &&
-                this.dst.equals(that.dst) &&
-                this.teid.equals(that.teid) &&
-                (this.srcPort == that.srcPort));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(src, dst, teid, srcPort);
-    }
-
-    /**
-     * Get the source IP address of this unidirectional GTP tunnel.
-     *
-     * @return tunnel source IP
-     */
-    public Ip4Address src() {
-        return this.src;
-    }
-
-    /**
-     * Get the destination address of this unidirectional GTP tunnel.
-     *
-     * @return tunnel destination IP
-     */
-    public Ip4Address dst() {
-        return this.dst;
-    }
-
-    /**
-     * Get the ID of this unidirectional GTP tunnel.
-     *
-     * @return tunnel ID
-     */
-    public ImmutableByteSequence teid() {
-        return this.teid;
-    }
-
-
-    /**
-     * Get the source L4 port of this unidirectional GTP tunnel.
-     *
-     * @return tunnel source port
-     */
-    public Short srcPort() {
-        return this.srcPort;
-    }
-
-    public static class GtpTunnelBuilder {
-        private Ip4Address src;
-        private Ip4Address dst;
-        private ImmutableByteSequence teid;
-        private short srcPort = 2152;  // Default value is equal to GTP tunnel dst port
-
-        public GtpTunnelBuilder() {
-            this.src = null;
-            this.dst = null;
-            this.teid = null;
-        }
-
-        /**
-         * Set the source IP address of the unidirectional GTP tunnel.
-         *
-         * @param src GTP tunnel source IP
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setSrc(Ip4Address src) {
-            this.src = src;
-            return this;
-        }
-
-        /**
-         * Set the destination IP address of the unidirectional GTP tunnel.
-         *
-         * @param dst GTP tunnel destination IP
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setDst(Ip4Address dst) {
-            this.dst = dst;
-            return this;
-        }
-
-        /**
-         * Set the identifier of this unidirectional GTP tunnel.
-         *
-         * @param teid tunnel ID
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setTeid(ImmutableByteSequence teid) {
-            this.teid = teid;
-            return this;
-        }
-
-        /**
-         * Set the identifier of this unidirectional GTP tunnel.
-         *
-         * @param teid tunnel ID
-         * @return This builder object
-         */
-        public GtpTunnelBuilder setTeid(long teid) {
-            this.teid = ImmutableByteSequence.copyFrom(teid);
-            return this;
-        }
-
-        /**
-         * Set the source port of this unidirectional GTP tunnel.
-         *
-         * @param srcPort tunnel source port
-         * @return this builder object
-         */
-        public GtpTunnelBuilder setSrcPort(short srcPort) {
-            this.srcPort = srcPort;
-            return this;
-        }
-
-        public GtpTunnel build() {
-            checkNotNull(src, "Tunnel source address cannot be null");
-            checkNotNull(dst, "Tunnel destination address cannot be null");
-            checkNotNull(teid, "Tunnel TEID cannot be null");
-            return new GtpTunnel(this.src, this.dst, this.teid, srcPort);
-        }
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnelPeer.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnelPeer.java
new file mode 100644
index 0000000..a3cf662
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/GtpTunnelPeer.java
@@ -0,0 +1,187 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A structure representing a GTP tunnel peer.
+ * The GTP Tunnel Peer is used by UPF to identify a second end of a GTP tunnel.
+ * The source and destination tunnel IPv4 addresses, and source UDP port are set
+ * based on the information from this structure.
+ */
+@Beta
+public final class GtpTunnelPeer implements UpfEntity {
+    // Match keys
+    private final byte tunPeerId;
+    // Action parameters
+    private final Ip4Address src;  // The source address of the unidirectional tunnel
+    private final Ip4Address dst;  // The destination address of the unidirectional tunnel
+    private final short srcPort;   // Tunnel source port, default 2152
+
+    private GtpTunnelPeer(byte tunPeerId, Ip4Address src, Ip4Address dst, short srcPort) {
+        this.tunPeerId = tunPeerId;
+        this.src = src;
+        this.dst = dst;
+        this.srcPort = srcPort;
+    }
+
+    public static GtpTunnelPeer.Builder builder() {
+        return new GtpTunnelPeer.Builder();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("GTP-Tunnel-Peer(%s -> src:%s, dst:%s srcPort:%s)",
+                             tunPeerId, src.toString(), dst.toString(), srcPort);
+    }
+
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+
+        if (object == null) {
+            return false;
+        }
+
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        GtpTunnelPeer that = (GtpTunnelPeer) object;
+        return (this.tunPeerId == that.tunPeerId &&
+                this.src.equals(that.src) &&
+                this.dst.equals(that.dst) &&
+                (this.srcPort == that.srcPort));
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(src, dst, srcPort);
+    }
+
+    /**
+     * Get the ID of the GTP tunnel peer.
+     *
+     * @return GTP tunnel peer ID
+     */
+    public byte tunPeerId() {
+        return tunPeerId;
+    }
+
+    /**
+     * Get the source IP address of this unidirectional GTP tunnel.
+     *
+     * @return tunnel source IP
+     */
+    public Ip4Address src() {
+        return this.src;
+    }
+
+    /**
+     * Get the destination address of this unidirectional GTP tunnel.
+     *
+     * @return tunnel destination IP
+     */
+    public Ip4Address dst() {
+        return this.dst;
+    }
+
+    /**
+     * Get the source L4 port of this unidirectional GTP tunnel.
+     *
+     * @return tunnel source port
+     */
+    public short srcPort() {
+        return this.srcPort;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.TUNNEL_PEER;
+    }
+
+    public static class Builder {
+        private Byte tunPeerId = null;
+        private Ip4Address src = null;
+        private Ip4Address dst = null;
+        private short srcPort = 2152;  // Default value is equal to GTP tunnel dst port
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the ID of the GTP Tunnel peer.
+         *
+         * @param tunPeerId GTP tunnel peer ID
+         * @return This builder object
+         */
+        public GtpTunnelPeer.Builder withTunnelPeerId(byte tunPeerId) {
+            this.tunPeerId = tunPeerId;
+            return this;
+        }
+
+        /**
+         * Set the source IP address of the unidirectional GTP tunnel.
+         *
+         * @param src GTP tunnel source IP
+         * @return This builder object
+         */
+        public GtpTunnelPeer.Builder withSrcAddr(Ip4Address src) {
+            this.src = src;
+            return this;
+        }
+
+        /**
+         * Set the destination IP address of the unidirectional GTP tunnel.
+         *
+         * @param dst GTP tunnel destination IP
+         * @return This builder object
+         */
+        public GtpTunnelPeer.Builder withDstAddr(Ip4Address dst) {
+            this.dst = dst;
+            return this;
+        }
+
+        /**
+         * Set the source port of this unidirectional GTP tunnel.
+         *
+         * @param srcPort tunnel source port
+         * @return this builder object
+         */
+        public GtpTunnelPeer.Builder withSrcPort(short srcPort) {
+            this.srcPort = srcPort;
+            return this;
+        }
+
+        public GtpTunnelPeer build() {
+            checkArgument(tunPeerId != null, "Tunnel Peer ID must be provided");
+            checkArgument(src != null, "Tunnel source address cannot be null");
+            checkArgument(dst != null, "Tunnel destination address cannot be null");
+            return new GtpTunnelPeer(this.tunPeerId, this.src, this.dst, srcPort);
+        }
+
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PacketDetectionRule.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/PacketDetectionRule.java
deleted file mode 100644
index f607d87..0000000
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PacketDetectionRule.java
+++ /dev/null
@@ -1,512 +0,0 @@
-/*
- * 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.checkArgument;
-
-/**
- * A single Packet Detection Rule (PDR), 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 PDR P4 table, and the resulting instance
- * should contain all the information needed to reproduce that logical switch
- * PDR 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 PDR.
- */
-public final class PacketDetectionRule {
-    // Match keys
-    private final Ip4Address ueAddr;  // The UE IP address that this PDR matches on
-    private final ImmutableByteSequence teid;  // The Tunnel Endpoint ID that this PDR matches on
-    private final Ip4Address tunnelDst;  // The tunnel destination address that this PDR matches on
-    // Action parameters
-    private final ImmutableByteSequence sessionId;  // The ID of the PFCP session that created this PDR
-    private final Integer ctrId;  // Counter ID unique to this PDR
-    private final Integer farId;  // The PFCP session-local ID of the FAR that should apply after this PDR hits
-    private final Type type;
-
-    private final Byte qfi; // QoS Flow Identifier of this PDR
-    private final boolean qfiPush; // True when QFI should be pushed to the packet
-    private final boolean qfiMatch; // True when QFI should be matched
-
-    private static final int SESSION_ID_BITWIDTH = 96;
-
-    private PacketDetectionRule(ImmutableByteSequence sessionId, Integer ctrId,
-                                Integer farId, Ip4Address ueAddr, Byte qfi,
-                                ImmutableByteSequence teid, Ip4Address tunnelDst,
-                                Type type, boolean qfiPush, boolean qfiMatch) {
-        this.ueAddr = ueAddr;
-        this.teid = teid;
-        this.tunnelDst = tunnelDst;
-        this.sessionId = sessionId;
-        this.ctrId = ctrId;
-        this.farId = farId;
-        this.qfi = qfi;
-        this.type = type;
-        this.qfiPush = qfiPush;
-        this.qfiMatch = qfiMatch;
-    }
-
-    public static Builder builder() {
-        return new Builder();
-    }
-
-    /**
-     * Return a string representing the match conditions of this PDR.
-     *
-     * @return a string representing the PDR match conditions
-     */
-    public String matchString() {
-        if (matchesEncapped()) {
-            return matchQfi() ?
-                    String.format("Match(Dst=%s, TEID=%s, QFI=%s)", tunnelDest(), teid(), qfi()) :
-                    String.format("Match(Dst=%s, TEID=%s)", tunnelDest(), teid());
-        } else {
-            return String.format("Match(Dst=%s, !GTP)", ueAddress());
-        }
-    }
-
-    @Override
-    public String toString() {
-        String actionParams = "";
-        if (hasActionParameters()) {
-            actionParams = hasQfi() && !matchQfi() ?
-                    String.format("SEID=%s, FAR=%d, CtrIdx=%d, QFI=%s",
-                                  sessionId.toString(), farId, ctrId, qfi) :
-                    String.format("SEID=%s, FAR=%d, CtrIdx=%d",
-                                  sessionId.toString(), farId, ctrId);
-            actionParams = pushQfi() ? String.format("%s, QFI_PUSH", actionParams) : actionParams;
-        }
-
-        return String.format("PDR{%s -> LoadParams(%s)}",
-                             matchString(), actionParams);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) {
-            return true;
-        }
-        if (obj == null) {
-            return false;
-        }
-        if (getClass() != obj.getClass()) {
-            return false;
-        }
-        PacketDetectionRule that = (PacketDetectionRule) obj;
-
-        // Safe comparisons between potentially null objects
-        return (this.type.equals(that.type) &&
-                Objects.equals(this.teid, that.teid) &&
-                Objects.equals(this.tunnelDst, that.tunnelDst) &&
-                Objects.equals(this.ueAddr, that.ueAddr) &&
-                Objects.equals(this.ctrId, that.ctrId) &&
-                Objects.equals(this.sessionId, that.sessionId) &&
-                Objects.equals(this.farId, that.farId) &&
-                Objects.equals(this.qfi, that.qfi) &&
-                Objects.equals(this.qfiPush, that.qfiPush) &&
-                Objects.equals(this.qfiMatch, that.qfiMatch));
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(ueAddr, teid, tunnelDst, sessionId, ctrId, farId,
-                            qfi, type);
-    }
-
-    /**
-     * Instances created as a result of DELETE write requests will not have
-     * action parameters, only match keys. This method should be used to avoid
-     * null pointer exceptions in those instances.
-     *
-     * @return true if this instance has PDR action parameters, false otherwise.
-     */
-    public boolean hasActionParameters() {
-        return type == Type.MATCH_ENCAPPED || type == Type.MATCH_UNENCAPPED;
-    }
-
-    /**
-     * Return a new instance of this PDR with the action parameters stripped,
-     * leaving only the match keys.
-     *
-     * @return a new PDR with only match keys
-     */
-    public PacketDetectionRule withoutActionParams() {
-        if (matchesEncapped()) {
-            PacketDetectionRule.Builder pdrBuilder = PacketDetectionRule.builder()
-                    .withTeid(teid)
-                    .withTunnelDst(tunnelDst);
-            if (this.hasQfi() && this.matchQfi()) {
-                pdrBuilder.withQfiMatch();
-                pdrBuilder.withQfi(qfi);
-            }
-            return pdrBuilder.build();
-        } else {
-            return PacketDetectionRule.builder()
-                    .withUeAddr(ueAddr).build();
-        }
-    }
-
-    /**
-     * True if this PDR matches on packets received with a GTP header, and false
-     * otherwise.
-     *
-     * @return true if the PDR matches only encapsulated packets
-     */
-    public boolean matchesEncapped() {
-        return type == Type.MATCH_ENCAPPED ||
-                type == Type.MATCH_ENCAPPED_NO_ACTION;
-    }
-
-    /**
-     * True if this PDR matches on packets received without a GTP header, and
-     * false otherwise.
-     *
-     * @return true if the PDR matches only un-encapsulated packets
-     */
-    public boolean matchesUnencapped() {
-        return type == Type.MATCH_UNENCAPPED ||
-                type == Type.MATCH_UNENCAPPED_NO_ACTION;
-    }
-
-    /**
-     * Get the ID of the PFCP session that produced this PDR.
-     *
-     * @return PFCP session ID
-     */
-    public ImmutableByteSequence sessionId() {
-        return sessionId;
-    }
-
-    /**
-     * Get the UE IP address that this PDR matches on.
-     *
-     * @return UE IP address
-     */
-    public Ip4Address ueAddress() {
-        return ueAddr;
-    }
-
-    /**
-     * Get the identifier of the GTP tunnel that this PDR matches on.
-     *
-     * @return GTP tunnel ID
-     */
-    public ImmutableByteSequence teid() {
-        return teid;
-    }
-
-    /**
-     * Get the destination IP of the GTP tunnel that this PDR matches on.
-     *
-     * @return GTP tunnel destination IP
-     */
-    public Ip4Address tunnelDest() {
-        return tunnelDst;
-    }
-
-    /**
-     * Get the dataplane PDR counter cell ID that this PDR is assigned.
-     *
-     * @return PDR counter cell ID
-     */
-    public int counterId() {
-        return ctrId;
-    }
-
-    /**
-     * Get the PFCP session-local ID of the far that should apply to packets
-     * that this PDR matches.
-     *
-     * @return PFCP session-local FAR ID
-     */
-    public int farId() {
-        return farId;
-    }
-
-    /**
-     * Get the QoS Flow Identifier for this PDR.
-     *
-     * @return QoS Flow Identifier
-     */
-    public byte qfi() {
-        return qfi;
-    }
-
-    /**
-     * Returns whether QFi should be pushed to the packet.
-     *
-     * @return True if the QFI should be pushed to the packet, false otherwise
-     */
-    public boolean pushQfi() {
-        return qfiPush;
-    }
-
-    /**
-     * Returns whether QFI should be matched on the packet or not.
-     *
-     * @return True if QFI should be matched on the packet, false otherwise
-     */
-    public boolean matchQfi() {
-        return qfiMatch;
-    }
-
-    /**
-     * Checks if the PDR has a QFI to match upon or to push on the packet.
-     *
-     * @return True if the PDR has a QFI, false otherwise
-     */
-    public boolean hasQfi() {
-        return qfi != null;
-    }
-
-    private enum Type {
-        /**
-         * Match on packets that are encapsulated in a GTP tunnel.
-         */
-        MATCH_ENCAPPED,
-        /**
-         * Match on packets that are not encapsulated in a GTP tunnel.
-         */
-        MATCH_UNENCAPPED,
-        /**
-         * For PDRs that match on encapsulated packets but do not yet have any
-         * action parameters set.
-         * These are usually built in the context of P4Runtime DELETE write requests.
-         */
-        MATCH_ENCAPPED_NO_ACTION,
-        /**
-         * For PDRs that match on unencapsulated packets but do not yet have any
-         * action parameters set.
-         * These are usually built in the context of P4Runtime DELETE write requests.
-         */
-        MATCH_UNENCAPPED_NO_ACTION
-    }
-
-    public static class Builder {
-        private ImmutableByteSequence sessionId = null;
-        private Integer ctrId = null;
-        private Integer localFarId = null;
-        private Integer schedulingPriority = null;
-        private Ip4Address ueAddr = null;
-        private ImmutableByteSequence teid = null;
-        private Ip4Address tunnelDst = null;
-        private Byte qfi = null;
-        private boolean qfiPush = false;
-        private boolean qfiMatch = false;
-
-        public Builder() {
-
-        }
-
-        /**
-         * Set the ID of the PFCP session that produced this PDR.
-         *
-         * @param sessionId PFCP session ID
-         * @return This builder object
-         */
-        public Builder withSessionId(ImmutableByteSequence sessionId) {
-            this.sessionId = sessionId;
-            return this;
-        }
-
-        /**
-         * Set the ID of the PFCP session that produced this PDR.
-         *
-         * @param sessionId PFCP 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 UE IP address that this PDR matches on.
-         *
-         * @param ueAddr UE IP address
-         * @return This builder object
-         */
-        public Builder withUeAddr(Ip4Address ueAddr) {
-            this.ueAddr = ueAddr;
-            return this;
-        }
-
-        /**
-         * Set the dataplane PDR counter cell ID that this PDR is assigned.
-         *
-         * @param ctrId PDR counter cell ID
-         * @return This builder object
-         */
-        public Builder withCounterId(int ctrId) {
-            this.ctrId = ctrId;
-            return this;
-        }
-
-
-        /**
-         * Set the PFCP session-local ID of the far that should apply to packets that this PDR matches.
-         *
-         * @param localFarId PFCP session-local FAR ID
-         * @return This builder object
-         */
-        public Builder withLocalFarId(int localFarId) {
-            this.localFarId = localFarId;
-            return this;
-        }
-
-        /**
-         * Set the QoS Flow Identifier for this PDR.
-         *
-         * @param qfi GTP Tunnel QFI
-         * @return This builder object
-         */
-        public Builder withQfi(byte qfi) {
-            this.qfi = qfi;
-            return this;
-        }
-
-        /**
-         * The QoS Flow Identifier should be pushed to the packet.
-         * This is valid for downstream packets and for 5G traffic only.
-         *
-         * @return This builder object
-         */
-        public Builder withQfiPush() {
-            this.qfiPush = true;
-            return this;
-        }
-
-        /**
-         * The QoS Flow Identifier should be matched to the packet.
-         * This is valid for upstream packets and for 5G traffic only.
-         *
-         * @return This builder object
-         */
-        public Builder withQfiMatch() {
-            this.qfiMatch = true;
-            return this;
-        }
-
-        /**
-         * Set the identifier of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid GTP tunnel ID
-         * @return This builder object
-         */
-        public Builder withTeid(int teid) {
-            this.teid = ImmutableByteSequence.copyFrom(teid);
-            return this;
-        }
-
-        /**
-         * Set the identifier of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid GTP tunnel ID
-         * @return This builder object
-         */
-        public Builder withTeid(ImmutableByteSequence teid) {
-            this.teid = teid;
-            return this;
-        }
-
-        /**
-         * Set the destination IP of the GTP tunnel that this PDR matches on.
-         *
-         * @param tunnelDst GTP tunnel destination IP
-         * @return This builder object
-         */
-        public Builder withTunnelDst(Ip4Address tunnelDst) {
-            this.tunnelDst = tunnelDst;
-            return this;
-        }
-
-        /**
-         * Set the tunnel ID and destination IP of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid      GTP tunnel ID
-         * @param tunnelDst GTP tunnel destination IP
-         * @return This builder object
-         */
-        public Builder withTunnel(ImmutableByteSequence teid, Ip4Address tunnelDst) {
-            this.teid = teid;
-            this.tunnelDst = tunnelDst;
-            return this;
-        }
-
-        /**
-         * Set the tunnel ID, destination IP and QFI of the GTP tunnel that this PDR matches on.
-         *
-         * @param teid      GTP tunnel ID
-         * @param tunnelDst GTP tunnel destination IP
-         * @param qfi       GTP QoS Flow Identifier
-         * @return This builder object
-         */
-        public Builder withTunnel(ImmutableByteSequence teid, Ip4Address tunnelDst, byte qfi) {
-            this.teid = teid;
-            this.tunnelDst = tunnelDst;
-            this.qfi = qfi;
-            return this;
-        }
-
-        public PacketDetectionRule build() {
-            // Some match keys are required.
-            checkArgument(
-                    (ueAddr != null && teid == null && tunnelDst == null) ||
-                            (ueAddr == null && teid != null && tunnelDst != null),
-                    "Either a UE address or a TEID and Tunnel destination must be provided, but not both.");
-            // Action parameters are optional but must be all provided together if they are provided
-            checkArgument(
-                    (sessionId != null && ctrId != null && localFarId != null) ||
-                            (sessionId == null && ctrId == null && localFarId == null),
-                    "PDR action parameters must be provided together or not at all.");
-            checkArgument(!qfiPush || !qfiMatch,
-                          "Either match of push QFI can be true, not both.");
-            checkArgument(!qfiPush || qfi != null,
-                          "A QFI must be provided when pushing QFI to the packet.");
-            checkArgument(!qfiMatch || qfi != null,
-                          "A QFI must be provided when matching QFI on the packet.");
-            Type type;
-            if (teid != null) {
-                if (sessionId != null) {
-                    type = Type.MATCH_ENCAPPED;
-                } else {
-                    type = Type.MATCH_ENCAPPED_NO_ACTION;
-                }
-            } else {
-                if (sessionId != null) {
-                    type = Type.MATCH_UNENCAPPED;
-                } else {
-                    type = Type.MATCH_UNENCAPPED_NO_ACTION;
-                }
-            }
-            return new PacketDetectionRule(sessionId, ctrId, localFarId, ueAddr,
-                                           qfi, teid, tunnelDst, type,
-                                           qfiPush, qfiMatch);
-        }
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionDownlink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionDownlink.java
new file mode 100644
index 0000000..c35410b
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionDownlink.java
@@ -0,0 +1,201 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Session on the UPF-programmable device.
+ * Provides means to set up the UE Session in the downlink direction.
+ */
+@Beta
+public final class SessionDownlink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address ueAddress;
+    // Action parameters
+    private final Byte tunPeerId;
+    private final boolean buffering;
+    private final boolean dropping;
+
+    private SessionDownlink(Ip4Address ipv4Address,
+                            Byte tunPeerId,
+                            boolean buffering,
+                            boolean drop) {
+        this.ueAddress = ipv4Address;
+        this.tunPeerId = tunPeerId;
+        this.buffering = buffering;
+        this.dropping = drop;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object == null) {
+            return false;
+        }
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        SessionDownlink that = (SessionDownlink) object;
+
+        return this.buffering == that.buffering &&
+                this.dropping == that.dropping &&
+                Objects.equals(ueAddress, that.ueAddress) &&
+                Objects.equals(tunPeerId, that.tunPeerId);
+    }
+
+    public int hashCode() {
+        return java.util.Objects.hash(ueAddress, tunPeerId, buffering, dropping);
+    }
+
+    @Override
+    public String toString() {
+        return "UESessionDL{" + matchString() + " -> " + actionString();
+    }
+
+    private String matchString() {
+        return "Match(ue_addr=" + this.ueAddress() + ")";
+    }
+
+    private String actionString() {
+        StringBuilder actionStrBuilder = new StringBuilder("(");
+        if (this.needsBuffering() && this.needsDropping()) {
+            actionStrBuilder.append("BUFF+DROP, ");
+        } else if (this.needsBuffering()) {
+            actionStrBuilder.append("BUFF, ");
+        } else if (this.needsDropping()) {
+            actionStrBuilder.append("DROP, ");
+        } else {
+            actionStrBuilder.append("FWD, ");
+        }
+       return actionStrBuilder.append(" tun_peer=").append(this.tunPeerId()).append(")")
+               .toString();
+    }
+
+    /**
+     * True if this UE Session needs buffering of the downlink traffic.
+     *
+     * @return true if the UE Session needs buffering.
+     */
+    public boolean needsBuffering() {
+        return buffering;
+    }
+
+    /**
+     * True if this UE Session needs dropping of the downlink traffic.
+     *
+     * @return true if the UE Session needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    /**
+     * Get the UE IP address of this downlink UE session.
+     *
+     * @return UE IP address
+     */
+    public Ip4Address ueAddress() {
+        return ueAddress;
+    }
+
+    /**
+     * Get the GTP tunnel peer ID that is set by this UE Session rule.
+     *
+     * @return GTP tunnel peer ID
+     */
+    public Byte tunPeerId() {
+        return tunPeerId;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.SESSION_DOWNLINK;
+    }
+
+    public static class Builder {
+        private Ip4Address ueAddress = null;
+        private Byte tunPeerId = null;
+        private boolean buffer = false;
+        private boolean drop = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the UE IP address that this downlink UE session rule matches on.
+         *
+         * @param ueAddress UE IP address
+         * @return This builder object
+         */
+        public Builder withUeAddress(Ip4Address ueAddress) {
+            this.ueAddress = ueAddress;
+            return this;
+        }
+
+        /**
+         * Set the GTP tunnel peer ID that is set by this UE Session rule.
+         *
+         * @param tunnelPeerId GTP tunnel peer ID
+         * @return This builder object
+         */
+        public Builder withGtpTunnelPeerId(Byte tunnelPeerId) {
+            this.tunPeerId = tunnelPeerId;
+            return this;
+        }
+
+        /**
+         * Sets whether to buffer downlink UE session traffic or not.
+         *
+         * @param buffer True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsBuffering(boolean buffer) {
+            this.buffer = buffer;
+            return this;
+        }
+
+        /**
+         * Sets whether to drop downlink UE session traffic or not.
+         *
+         * @param drop True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean drop) {
+            this.drop = drop;
+            return this;
+        }
+
+        public SessionDownlink build() {
+            // Match fields are required
+            checkNotNull(ueAddress, "UE address must be provided");
+            return new SessionDownlink(ueAddress, tunPeerId, buffer, drop);
+        }
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionUplink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionUplink.java
new file mode 100644
index 0000000..70db84c
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/SessionUplink.java
@@ -0,0 +1,175 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Session on the UPF-programmable device.
+ * Provides means to set up the UE Session in the uplink direction.
+ */
+@Beta
+public final class SessionUplink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address tunDestAddr; // The tunnel destination address (N3/S1U IPv4 address)
+    private final Integer teid;  // The Tunnel Endpoint ID that this UeSession matches on
+
+    // Action parameters
+    private final boolean dropping; // Used to convey dropping information
+
+    private SessionUplink(Ip4Address tunDestAddr,
+                          Integer teid,
+                          boolean drop) {
+        this.tunDestAddr = tunDestAddr;
+        this.teid = teid;
+        this.dropping = drop;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object == null) {
+            return false;
+        }
+        if (getClass() != object.getClass()) {
+            return false;
+        }
+
+        SessionUplink that = (SessionUplink) object;
+
+        return this.dropping == that.dropping &&
+                Objects.equals(tunDestAddr, that.tunDestAddr) &&
+                Objects.equals(teid, that.teid);
+    }
+
+    public int hashCode() {
+        return Objects.hash(tunDestAddr, teid, dropping);
+    }
+
+    @Override
+    public String toString() {
+        return "UESessionUL{" + matchString() + " -> " + actionString();
+    }
+
+    private String matchString() {
+        return "Match(tun_dst_addr=" + this.tunDstAddr() + ", TEID=" + this.teid() + ")";
+    }
+
+    private String actionString() {
+        StringBuilder actionStrBuilder = new StringBuilder("(");
+        if (this.needsDropping()) {
+            actionStrBuilder.append("DROP");
+
+        } else {
+            actionStrBuilder.append("FWD");
+        }
+        return actionStrBuilder.append(")").toString();
+    }
+
+    /**
+     * True if this UE Session needs dropping of the uplink traffic.
+     *
+     * @return true if the UE Session needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    /**
+     * Get the tunnel destination IP address in the uplink UE session (N3/S1U IP address).
+     *
+     * @return UE IP address
+     */
+    public Ip4Address tunDstAddr() {
+        return tunDestAddr;
+    }
+
+    /**
+     * Get the identifier of the GTP tunnel that this UE Session rule matches on.
+     *
+     * @return GTP tunnel ID
+     */
+    public Integer teid() {
+        return teid;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.SESSION_UPLINK;
+    }
+
+    public static class Builder {
+        private Ip4Address tunDstAddr = null;
+        private Integer teid = null;
+        private boolean drop = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the tunnel destination IP address (N3/S1U address) that this UE Session rule matches on.
+         *
+         * @param tunDstAddr The tunnel destination IP address
+         * @return This builder object
+         */
+        public Builder withTunDstAddr(Ip4Address tunDstAddr) {
+            this.tunDstAddr = tunDstAddr;
+            return this;
+        }
+
+        /**
+         * Set the identifier of the GTP tunnel that this UE Session rule matches on.
+         *
+         * @param teid GTP tunnel ID
+         * @return This builder object
+         */
+        public Builder withTeid(Integer teid) {
+            this.teid = teid;
+            return this;
+        }
+
+
+        /**
+         * Sets whether to drop uplink UE session traffic or not.
+         *
+         * @param drop True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean drop) {
+            this.drop = drop;
+            return this;
+        }
+
+        public SessionUplink build() {
+            // Match keys are required.
+            checkNotNull(tunDstAddr, "Tunnel destination must be provided");
+            checkNotNull(teid, "TEID must be provided");
+            return new SessionUplink(tunDstAddr, teid, drop);
+        }
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PdrStats.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
similarity index 76%
rename from core/api/src/main/java/org/onosproject/net/behaviour/upf/PdrStats.java
rename to core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
index eb4ea18..4eee3fe 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/PdrStats.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfCounter.java
@@ -17,21 +17,26 @@
 package org.onosproject.net.behaviour.upf;
 
 
+import com.google.common.annotations.Beta;
+
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
- * A structure for compactly passing PDR counter values for a given counter ID.
- * Contains four counts: Ingress Packets, Ingress Bytes, Egress Packets, Egress Bytes
+ * 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.
  */
-public final class PdrStats {
+@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 PdrStats(int cellId, long ingressPkts, long ingressBytes,
-                     long egressPkts, long egressBytes) {
+    private UpfCounter(int cellId, long ingressPkts, long ingressBytes,
+                       long egressPkts, long egressBytes) {
         this.cellId = cellId;
         this.ingressPkts = ingressPkts;
         this.ingressBytes = ingressBytes;
@@ -45,12 +50,12 @@
 
     @Override
     public String toString() {
-        return String.format("PDR-Stats:{ CellID: %d, Ingress:(%dpkts,%dbytes), Egress:(%dpkts,%dbytes) }",
+        return String.format("Stats:{ CellID: %d, Ingress:(%dpkts,%dbytes), Egress:(%dpkts,%dbytes) }",
                 cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
     }
 
     /**
-     * Get the cell ID (index) of the dataplane PDR counter that produced this set of stats.
+     * Get the cell ID (index) of the dataplane counter that produced this set of stats.
      *
      * @return counter cell ID
      */
@@ -94,6 +99,11 @@
         return egressBytes;
     }
 
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.COUNTER;
+    }
+
     public static class Builder {
         private Integer cellId;
         private long ingressPkts;
@@ -109,7 +119,7 @@
         }
 
         /**
-         * Set the Cell ID (index) of the datalane PDR counter that produced this set of stats.
+         * Set the Cell ID (index) of the datalane counter that produced this set of stats.
          *
          * @param cellId the counter cell ID
          * @return This builder
@@ -120,7 +130,7 @@
         }
 
         /**
-         * Set the number of packets and bytes that hit the PDR counter in the dataplane ingress pipeline.
+         * Set the number of packets and bytes that hit the counter in the dataplane ingress pipeline.
          *
          * @param ingressPkts  ingress packet count
          * @param ingressBytes egress packet count
@@ -133,7 +143,7 @@
         }
 
         /**
-         * Set the number of packets and bytes that hit the PDR counter in the dataplane egress pipeline.
+         * Set the number of packets and bytes that hit the counter in the dataplane egress pipeline.
          *
          * @param egressPkts  egress packet count
          * @param egressBytes egress byte count
@@ -145,9 +155,9 @@
             return this;
         }
 
-        public PdrStats build() {
+        public UpfCounter build() {
             checkNotNull(cellId, "CellID must be provided");
-            return new PdrStats(cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
+            return new UpfCounter(cellId, ingressPkts, ingressBytes, egressPkts, egressBytes);
         }
     }
 }
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 e0d9fb4..2b17421 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
@@ -28,157 +28,94 @@
 public interface UpfDevice {
 
     /**
-     * Remove any state previously created by this API.
+     * Removes any state previously created by this API.
      */
     void cleanUp();
 
     /**
-     * Remove all interfaces currently installed on the UPF-programmable device.
-     */
-    void clearInterfaces();
-
-    /**
-     * Remove all UE flows (PDRs, FARs) currently installed on the UPF-programmable device.
-     */
-    void clearFlows();
-
-    /**
-     * Get all ForwardingActionRules currently installed on the UPF-programmable device.
+     * Applies the given UPF entity to the UPF-programmable device.
      *
-     * @return a collection of installed FARs
-     * @throws UpfProgrammableException if FARs are unable to be read
+     * @param entity The UPF entity to be applied.
+     * @throws UpfProgrammableException if the given UPF entity can not be applied or
+     *                                  the operation is not supported on the given UPF entity.
      */
-    Collection<ForwardingActionRule> getFars() throws UpfProgrammableException;
+    void apply(UpfEntity entity) throws UpfProgrammableException;
 
     /**
-     * Get all PacketDetectionRules currently installed on the UPF-programmable device.
+     * Reads all the UPF entities of the given type from the UPF-programmable device.
      *
-     * @return a collection of installed PDRs
-     * @throws UpfProgrammableException if PDRs are unable to be read
+     * @param entityType The type of entities to read.
+     * @return A collection of installed UPF entities.
+     * @throws UpfProgrammableException if UPF entity type is not available to be read or
+     *                                  the operation is not supported on the given UPF entity type.
      */
-    Collection<PacketDetectionRule> getPdrs() throws UpfProgrammableException;
+    Collection<? extends UpfEntity> readAll(UpfEntityType entityType) throws UpfProgrammableException;
 
     /**
-     * Get all UPF interface lookup entries currently installed on the UPF-programmable device.
+     * Reads the given UPF counter ID from the UPF-programmable device.
      *
-     * @return a collection of installed interfaces
-     * @throws UpfProgrammableException if interfaces are unable to be read
+     * @param counterId The counter ID from which to read.
+     * @return The content of the UPF counter.
+     * @throws UpfProgrammableException if the counter ID is out of bounds.
      */
-    Collection<UpfInterface> getInterfaces() throws UpfProgrammableException;
+    UpfCounter readCounter(int counterId) throws UpfProgrammableException;
 
     /**
-     * Add a Packet Detection Rule (PDR) to the given device.
-     *
-     * @param pdr The PDR to be added
-     * @throws UpfProgrammableException if the PDR cannot be installed, or the counter index is out
-     *                                  of bounds
-     */
-    void addPdr(PacketDetectionRule pdr) throws UpfProgrammableException;
-
-    /**
-     * Remove a previously installed Packet Detection Rule (PDR) from the target device.
-     *
-     * @param pdr The PDR to be removed
-     * @throws UpfProgrammableException if the PDR cannot be found
-     */
-    void removePdr(PacketDetectionRule pdr) throws UpfProgrammableException;
-
-    /**
-     * Add a Forwarding Action Rule (FAR) to the given device.
-     *
-     * @param far The FAR to be added
-     * @throws UpfProgrammableException if the FAR cannot be installed
-     */
-    void addFar(ForwardingActionRule far) throws UpfProgrammableException;
-
-    /**
-     * Remove a previously installed Forwarding Action Rule (FAR) from the target device.
-     *
-     * @param far The FAR to be removed
-     * @throws UpfProgrammableException if the FAR cannot be found
-     */
-    void removeFar(ForwardingActionRule far) throws UpfProgrammableException;
-
-    /**
-     * Install a new interface on the UPF device's interface lookup tables.
-     *
-     * @param upfInterface the interface to install
-     * @throws UpfProgrammableException if the interface cannot be installed
-     */
-    void addInterface(UpfInterface upfInterface) throws UpfProgrammableException;
-
-    /**
-     * Remove a previously installed UPF interface from the target device.
-     *
-     * @param upfInterface the interface to be removed
-     * @throws UpfProgrammableException if the interface cannot be found
-     */
-    void removeInterface(UpfInterface upfInterface) throws UpfProgrammableException;
-
-    /**
-     * Read the the given cell (Counter index) of the PDR counters from the given device.
-     *
-     * @param counterIdx The counter cell index from which to read
-     * @return A structure containing ingress and egress packet and byte counts for the given
-     * cellId.
-     * @throws UpfProgrammableException if the cell ID is out of bounds
-     */
-    PdrStats readCounter(int counterIdx) throws UpfProgrammableException;
-
-    /**
-     * Return the number of PDR counter cells available. The number of cells in the ingress and
-     * egress PDR counters are equivalent.
-     *
-     * @return PDR counter size
-     */
-    long pdrCounterSize();
-
-    /**
-     * Return the number of maximum number of table entries the FAR table supports.
-     *
-     * @return the number of FARs that can be installed
-     */
-    long farTableSize();
-
-    /**
-     * Return the total number of table entries the downlink and uplink PDR tables support. Both
-     * tables support an equal number of entries, so the total is twice the size of either.
-     *
-     * @return the total number of PDRs that can be installed
-     */
-    long pdrTableSize();
-
-    /**
-     * Read the counter contents for all cell indices that are valid on the hardware switch.
-     * {@code maxCounterId} parameter is used to limit the number of counters
-     * retrieved from the UPF device. If the limit given is larger than the
-     * physical limit, the physical limit will be used. A limit of -1 removes
-     * limitations.
+     * 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
+     * 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}.
      *
      * @param maxCounterId Maximum counter ID to retrieve from the UPF device.
-     * @return A collection of counter values for all valid hardware counter cells
-     * @throws UpfProgrammableException if the counters are unable to be read
+     * @return A collection of UPF counters for all valid hardware counter cells.
+     * @throws UpfProgrammableException if the counters are unable to be read.
      */
-    Collection<PdrStats> readAllCounters(long maxCounterId) throws UpfProgrammableException;
+    Collection<UpfCounter> readCounters(long maxCounterId) throws UpfProgrammableException;
+
+    /**
+     * Deletes the given UPF entity from the UPF-programmable device.
+     *
+     * @param entity The UPF entity to be removed.
+     * @throws UpfProgrammableException if the given UPF entity is not found or
+     *                                  the operation is not supported on the given UPF entity.
+     */
+    void delete(UpfEntity entity) throws UpfProgrammableException;
+
+    /**
+     * Deletes the given UPF entity from the UPF-programmable device.
+     *
+     * @param entityType The UPF entity type to be removed.
+     * @throws UpfProgrammableException if the given UPF entity is not found or
+     *                                  the operation is not supported on the given UPF entity.
+     */
+    void deleteAll(UpfEntityType entityType) throws UpfProgrammableException;
+
+    /**
+     * Returns the total number of UPF entities of the given type supported by
+     * 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.
+     */
+    long tableSize(UpfEntityType entityType) throws UpfProgrammableException;
 
     /**
      * Instructs the UPF-programmable device to use GTP-U extension PDU Session Container (PSC) when
      * doing encap of downlink packets, with the given QoS Flow Identifier (QFI).
      *
-     * @param defaultQfi QFI to be used by default for all encapped packets.
      * @throws UpfProgrammableException if operation is not available
      */
-    // FIXME: remove once we expose QFI in logical pipeline
-    //  QFI should be set by the SMF using PFCP
-    void enablePscEncap(int defaultQfi) throws UpfProgrammableException;
+    void enablePscEncap() throws UpfProgrammableException;
 
     /**
-     * Disable PSC encap previously enabled with {@link #enablePscEncap(int)}.
+     * Disable PSC encap previously enabled with {@link #enablePscEncap()}.
      *
      * @throws UpfProgrammableException if operation is not available
      */
-    // FIXME: remove once we expose QFI in logical pipeline
-    //  QFI should be set by the SMF using PFCP
     void disablePscEncap() throws UpfProgrammableException;
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntity.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntity.java
new file mode 100644
index 0000000..ad1fbaa
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntity.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of a UPF entity used to interact with the UPF-programmable device.
+ */
+@Beta
+public interface UpfEntity {
+    /**
+     * Returns the type of this entity.
+     *
+     * @return entity type
+     */
+    UpfEntityType type();
+}
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
new file mode 100644
index 0000000..7ec3b19
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfEntityType.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+
+/**
+ * Type of UPF entity.
+ */
+@Beta
+public enum UpfEntityType {
+    INTERFACE("interface"),
+    TERMINATION_DOWNLINK("termination_downlink"),
+    TERMINATION_UPLINK("termination_uplink"),
+    SESSION_DOWNLINK("session_downlink"),
+    SESSION_UPLINK("session_downlink"),
+    TUNNEL_PEER("tunnel_peer"),
+    COUNTER("counter");
+
+    private final String humanReadableName;
+
+    UpfEntityType(String humanReadableName) {
+        this.humanReadableName = humanReadableName;
+    }
+
+    /**
+     * Returns a human readable representation of this UPF entity type (useful
+     * for logging).
+     *
+     * @return string
+     */
+    public String humanReadableName() {
+        return humanReadableName;
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java
index 3fee11a..f697d15 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfInterface.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.net.behaviour.upf;
 
+import com.google.common.annotations.Beta;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.Ip4Prefix;
 
@@ -26,7 +27,8 @@
 /**
  * A UPF device interface, such as a S1U or UE IP address pool.
  */
-public final class UpfInterface {
+@Beta
+public final class UpfInterface implements UpfEntity {
     private final Ip4Prefix prefix;
     private final Type type;
 
@@ -134,7 +136,6 @@
         return type == Type.CORE;
     }
 
-
     /**
      * Check if this UPF interface is for receiving buffered packets as they are released from the dbuf
      * buffering device.
@@ -154,6 +155,11 @@
         return this.prefix;
     }
 
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.INTERFACE;
+    }
+
     public enum Type {
         /**
          * Unknown UPF interface type.
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java
index 159adb0..052084e 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammable.java
@@ -30,13 +30,12 @@
 public interface UpfProgrammable extends HandlerBehaviour, UpfDevice {
     /**
      * Apps are expected to call this method as the first one when they are ready
-     * to install PDRs and FARs.
+     * to install any UPF entity.
      *
      * @return True if initialized, false otherwise.
      */
     boolean init();
 
-
     /**
      * Checks if the given flow rule has been generated by this UPF behaviour.
      *
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java
index 7daa806..96f8c85 100644
--- a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfProgrammableException.java
@@ -16,14 +16,20 @@
 
 package org.onosproject.net.behaviour.upf;
 
+import com.google.common.annotations.Beta;
+
+import java.util.Optional;
+
 /**
  * An exception indicating a an error happened in the UPF programmable behaviour.
  * Possible errors include the attempted insertion of a malformed flow rule, the
  * reading or writing of an out-of-bounds counter cell, the deletion of a non-existent
  * flow rule, and the attempted insertion of a flow rule into a full table.
  */
+@Beta
 public class UpfProgrammableException extends Exception {
     private final Type type;
+    private final UpfEntityType entityType;
 
     public enum Type {
         /**
@@ -31,13 +37,13 @@
          */
         UNKNOWN,
         /**
-         * The target table is at capacity.
+         * The target entity is at capacity.
          */
-        TABLE_EXHAUSTED,
+        ENTITY_EXHAUSTED,
         /**
-         * A provided counter cell index was out of range.
+         * A provided index was out of range.
          */
-        COUNTER_INDEX_OUT_OF_RANGE,
+        ENTITY_OUT_OF_RANGE,
         /**
          * The UpfProgrammable implementation doesn't support the operation.
          */
@@ -52,6 +58,7 @@
     public UpfProgrammableException(String message) {
         super(message);
         this.type = Type.UNKNOWN;
+        this.entityType = null;
     }
 
     /**
@@ -63,6 +70,20 @@
     public UpfProgrammableException(String message, Type type) {
         super(message);
         this.type = type;
+        this.entityType = null;
+    }
+
+    /**
+     * Creates a new exception for the given message, type and entity type.
+     *
+     * @param message    exception message
+     * @param type       exception type
+     * @param entityType entity type
+     */
+    public UpfProgrammableException(String message, Type type, UpfEntityType entityType) {
+        super(message);
+        this.type = type;
+        this.entityType = entityType;
     }
 
     /**
@@ -73,4 +94,13 @@
     public Type getType() {
         return type;
     }
+
+    /**
+     * Get the type of the entity that generated the exception.
+     *
+     * @return entity type
+     */
+    public Optional<UpfEntityType> getEntityType() {
+        return Optional.ofNullable(entityType);
+    }
 }
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationDownlink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationDownlink.java
new file mode 100644
index 0000000..3bc9a91
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationDownlink.java
@@ -0,0 +1,252 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Termination in the downlink direction on the
+ * UPF-programmable device.
+ * Provides means to configure the traffic behavior (e.g. set Traffic Class, GTP TEID, or QFI).
+ */
+@Beta
+public final class UpfTerminationDownlink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address ueSessionId; // UE Session ID, use UE IP address to uniquely identify a session.
+    // Action parameters
+    private final Integer ctrId;  // Counter ID unique to this UPF Termination Rule
+    private final Byte trafficClass;
+    private final Integer teid;  // Tunnel Endpoint Identifier
+    private final Byte qfi; // QoS Flow Identifier
+    private final boolean dropping;
+
+    private UpfTerminationDownlink(Ip4Address ueSessionId, Integer ctrId, Byte trafficClass,
+                                   Integer teid, Byte qfi, boolean dropping) {
+        this.ueSessionId = ueSessionId;
+        this.ctrId = ctrId;
+        this.trafficClass = trafficClass;
+        this.teid = teid;
+        this.qfi = qfi;
+        this.dropping = dropping;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        UpfTerminationDownlink that = (UpfTerminationDownlink) obj;
+
+        // Safe comparisons between potentially null objects
+        return this.dropping == that.dropping &&
+                Objects.equals(this.ueSessionId, that.ueSessionId) &&
+                Objects.equals(this.ctrId, that.ctrId) &&
+                Objects.equals(this.trafficClass, that.trafficClass) &&
+                Objects.equals(this.teid, that.teid) &&
+                Objects.equals(this.qfi, that.qfi);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ueSessionId, ctrId, trafficClass, teid, qfi, dropping);
+    }
+
+    /**
+     * Get UE Session ID associated with UPF Termination rule.
+     *
+     * @return UE Session ID
+     */
+    public Ip4Address ueSessionId() {
+        return ueSessionId;
+    }
+
+    /**
+     * Get PDR Counter ID associated with UPF Termination rule.
+     *
+     * @return PDR counter cell ID
+     */
+    public Integer counterId() {
+        return ctrId;
+    }
+
+    /**
+     * Get Traffic Class set by this UPF Termination rule.
+     *
+     * @return Traffic Class
+     */
+    public Byte trafficClass() {
+        return trafficClass;
+    }
+
+    /**
+     * Get GTP TEID set by this UPF Termination rule.
+     *
+     * @return GTP tunnel ID
+     */
+    public Integer teid() {
+        return teid;
+    }
+
+    /**
+     * Get QoS Flow Identifier set by this UPF Termination rule.
+     *
+     * @return QoS Flow Identifier
+     */
+    public Byte qfi() {
+        return qfi;
+    }
+
+    /**
+     * True if this UPF Termination needs to drop traffic.
+     *
+     * @return true if the UPF Termination needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.TERMINATION_DOWNLINK;
+    }
+
+    @Override
+    public String toString() {
+        return "TerminationUL{" + matchString() + "->" + actionString() + "}";
+    }
+
+    private String matchString() {
+        return "Match(ue_addr=" + this.ueSessionId() + ")";
+    }
+
+    private String actionString() {
+        return "(TEID=" + this.teid() +
+                ", CTR_ID=" + this.counterId() +
+                ", QFI=" + this.qfi() +
+                ", TC=" + this.trafficClass() +
+                ")";
+    }
+
+    public static class Builder {
+        private Ip4Address ueSessionId = null;
+        private Integer ctrId = null;
+        private Byte trafficClass = null;
+        private Integer teid = null;
+        private Byte qfi = null;
+        private boolean drop = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the ID of the UE session.
+         *
+         * @param ueSessionId UE session ID
+         * @return This builder object
+         */
+        public Builder withUeSessionId(Ip4Address ueSessionId) {
+            this.ueSessionId = ueSessionId;
+            return this;
+        }
+
+        /**
+         * Set the dataplane counter cell ID.
+         *
+         * @param ctrId PDR counter cell ID
+         * @return This builder object
+         */
+        public Builder withCounterId(int ctrId) {
+            this.ctrId = ctrId;
+            return this;
+        }
+
+        /**
+         * Set the Traffic Class.
+         *
+         * @param trafficClass Traffic Class
+         * @return This builder object
+         */
+        public Builder withTrafficClass(byte trafficClass) {
+            this.trafficClass = trafficClass;
+            return this;
+        }
+
+        /**
+         * Set the identifier of the unidirectional GTP tunnel that should be used for the UE Session.
+         *
+         * @param teid tunnel ID
+         * @return This builder object
+         */
+        public Builder withTeid(Integer teid) {
+            this.teid = teid;
+            return this;
+        }
+
+        /**
+         * Set the QoS Flow Identifier.
+         *
+         * @param qfi GTP Tunnel QFI
+         * @return This builder object
+         */
+        public Builder withQfi(byte qfi) {
+            this.qfi = qfi;
+            return this;
+        }
+
+        /**
+         * Sets whether to drop downlink UPF termination traffic or not.
+         *
+         * @param drop True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean drop) {
+            this.drop = drop;
+            return this;
+        }
+
+
+        public UpfTerminationDownlink build() {
+            // Match fields must be provided
+            checkNotNull(ueSessionId, "UE session ID must be provided");
+
+            checkNotNull(ctrId, "Counter ID must be provided");
+            // TODO: should we verify that when dropping no other fields are provided
+            return new UpfTerminationDownlink(
+                    this.ueSessionId, this.ctrId, this.trafficClass, this.teid,
+                    this.qfi, this.drop
+            );
+        }
+
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationUplink.java b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationUplink.java
new file mode 100644
index 0000000..74dbc01
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/behaviour/upf/UpfTerminationUplink.java
@@ -0,0 +1,197 @@
+/*
+ * 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 com.google.common.annotations.Beta;
+import org.onlab.packet.Ip4Address;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A structure representing the UE Termination in the uplink direction on the
+ * UPF-programmable device.
+ * Provides means to configure the traffic behavior (e.g. set Traffic Class).
+ */
+@Beta
+public final class UpfTerminationUplink implements UpfEntity {
+    // Match Keys
+    private final Ip4Address ueSessionId; // UE Session ID, use UE IP address to uniquely identify a session.
+    // Action parameters
+    private final Integer ctrId;  // Counter ID unique to this UPF Termination Rule
+    private final Byte trafficClass;
+    private final boolean dropping;
+
+    private UpfTerminationUplink(Ip4Address ueSessionId, Integer ctrId, Byte trafficClass,
+                                 boolean dropping) {
+        this.ueSessionId = ueSessionId;
+        this.ctrId = ctrId;
+        this.trafficClass = trafficClass;
+        this.dropping = dropping;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        UpfTerminationUplink that = (UpfTerminationUplink) obj;
+
+        // Safe comparisons between potentially null objects
+        return this.dropping == that.dropping &&
+                Objects.equals(this.ueSessionId, that.ueSessionId) &&
+                Objects.equals(this.ctrId, that.ctrId) &&
+                Objects.equals(this.trafficClass, that.trafficClass);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(ueSessionId, ctrId, trafficClass, dropping);
+    }
+
+    /**
+     * Get UE Session ID associated with UPF Termination rule.
+     *
+     * @return UE Session ID
+     */
+    public Ip4Address ueSessionId() {
+        return ueSessionId;
+    }
+
+    /**
+     * Get PDR Counter ID associated with UPF Termination rule.
+     *
+     * @return PDR counter cell ID
+     */
+    public Integer counterId() {
+        return ctrId;
+    }
+
+    /**
+     * Get Traffic Class set by this UPF Termination rule.
+     *
+     * @return Traffic Class
+     */
+    public Byte trafficClass() {
+        return trafficClass;
+    }
+
+    /**
+     * True if this UPF Termination needs to drop traffic.
+     *
+     * @return true if the UPF Termination needs dropping.
+     */
+    public boolean needsDropping() {
+        return dropping;
+    }
+
+    @Override
+    public UpfEntityType type() {
+        return UpfEntityType.TERMINATION_UPLINK;
+    }
+
+    @Override
+    public String toString() {
+        return "TerminationDL{" + matchString() + "->" + actionString() + "}";
+    }
+
+    private String matchString() {
+        return "Match(ue_addr=" + this.ueSessionId() + ")";
+    }
+
+    private String actionString() {
+        return "(CTR_ID=" + this.counterId() + ", TC=" + this.trafficClass() + ")";
+    }
+
+    public static class Builder {
+        private Ip4Address ueSessionId = null;
+        private Integer ctrId = null;
+        private Byte trafficClass = null;
+        private boolean dropping = false;
+
+        public Builder() {
+
+        }
+
+        /**
+         * Set the ID of the UE session.
+         *
+         * @param ueSessionId UE session ID
+         * @return This builder object
+         */
+        public Builder withUeSessionId(Ip4Address ueSessionId) {
+            this.ueSessionId = ueSessionId;
+            return this;
+        }
+
+        /**
+         * Set the dataplane counter cell ID.
+         *
+         * @param ctrId PDR counter cell ID
+         * @return This builder object
+         */
+        public Builder withCounterId(int ctrId) {
+            this.ctrId = ctrId;
+            return this;
+        }
+
+        /**
+         * Set the Traffic Class.
+         *
+         * @param trafficClass Traffic Class
+         * @return This builder object
+         */
+        public Builder withTrafficClass(byte trafficClass) {
+            this.trafficClass = trafficClass;
+            return this;
+        }
+
+        /**
+         * Sets whether to drop uplink UPF termination traffic or not.
+         *
+         * @param dropping True if request to buffer, false otherwise
+         * @return This builder object
+         */
+        public Builder needsDropping(boolean dropping) {
+            this.dropping = dropping;
+            return this;
+        }
+
+        public UpfTerminationUplink build() {
+            // Match fields must be provided
+            checkNotNull(ueSessionId, "UE session ID must be provided");
+
+            checkNotNull(ctrId, "Counter ID must be provided");
+            // TODO: should we verify that when dropping no other fields are provided
+            return new UpfTerminationUplink(
+                    this.ueSessionId, this.ctrId, this.trafficClass, this.dropping);
+        }
+
+    }
+
+}