[VOL-3661] PPPoE IA application - initial commit

Change-Id: Idaf23f8736cba955fe8a3049b8fc9c85b3cd3ab9
Signed-off-by: Gustavo Silva <gsilva@furukawalatam.com>
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 0000000..96631bb
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>pppoeagent</artifactId>
+        <groupId>org.opencord</groupId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>pppoeagent-api</artifactId>
+    <packaging>bundle</packaging>
+    <description>PPPoE Intermediate Agent API</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <version>${onos.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-misc</artifactId>
+            <version>${onos.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.opencord</groupId>
+            <artifactId>sadis-api</artifactId>
+            <version>${sadis.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${onos.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/api/src/main/java/org/opencord/pppoeagent/PPPoEDVendorSpecificTag.java b/api/src/main/java/org/opencord/pppoeagent/PPPoEDVendorSpecificTag.java
new file mode 100644
index 0000000..8605637
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/PPPoEDVendorSpecificTag.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.opencord.pppoeagent;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Represents the "Vendor-Specific" PPPoED tag.
+ * This class is used by the PPPoE agent in order to read/build the tag that carries the
+ * vendor-id, circuit-id and remote-id information, which are attached to the upstrem packets.
+ */
+public class PPPoEDVendorSpecificTag {
+    public static final int BBF_IANA_VENDOR_ID = 3561;
+    private static final byte CIRCUIT_ID_OPTION = 1;
+    private static final byte REMOTE_ID_OPTION = 2;
+
+    private int vendorId = BBF_IANA_VENDOR_ID;
+    private String circuitId = null;
+    private String remoteId = null;
+
+    /**
+     * Creates an empty Vendor-Specific tag object.
+     *
+     */
+    public PPPoEDVendorSpecificTag() {
+    }
+
+    /**
+     * Creates a new Vendor-Specific tag object.
+     *
+     * @param circuitId circuit-id information
+     * @param remoteId remote-id information
+     */
+    public PPPoEDVendorSpecificTag(String circuitId, String remoteId) {
+        this.circuitId = circuitId;
+        this.remoteId = remoteId;
+    }
+
+    /**
+     * Sets the vendor-id.
+     *
+     * @param value vendor-id to be set.
+     */
+    public void setVendorId(int value) {
+        this.vendorId = value;
+    }
+
+    /**
+     * Gets the vendor-id.
+     *
+     * @return the vendor-id.
+     */
+    public Integer getVendorId() {
+        return this.vendorId;
+    }
+
+    /**
+     * Sets the circuit-id.
+     *
+     * @param value circuit-id to be set.
+     */
+    public void setCircuitId(String value) {
+        this.circuitId = value;
+    }
+
+    /**
+     * Gets the circuit-id.
+     *
+     * @return the circuit-id.
+     */
+    public String getCircuitId() {
+        return this.circuitId;
+    }
+
+    /**
+     * Sets the remote-id.
+     *
+     * @param value remote-id to be set.
+     */
+    public void setRemoteId(String value) {
+        this.remoteId = value;
+    }
+
+    /**
+     * Gets the remote-id.
+     *
+     * @return the remote-id.
+     */
+    public String getRemoteId() {
+        return this.remoteId;
+    }
+
+    /**
+     * Returns the representation of the PPPoED vendor-specific tag as byte array.
+     * @return returns byte array
+     */
+    public byte[] toByteArray() {
+        ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+        byte[] bytes = ByteBuffer.allocate(4).putInt(this.vendorId).array();
+        buf.write(bytes, 0, bytes.length);
+
+        // Add sub option if set
+        if (circuitId != null) {
+            buf.write(CIRCUIT_ID_OPTION);
+            buf.write((byte) circuitId.length());
+            bytes = circuitId.getBytes(StandardCharsets.UTF_8);
+            buf.write(bytes, 0, bytes.length);
+        }
+
+        // Add sub option if set
+        if (remoteId != null) {
+            buf.write(REMOTE_ID_OPTION);
+            buf.write((byte) remoteId.length());
+            bytes = remoteId.getBytes(StandardCharsets.UTF_8);
+            buf.write(bytes, 0, bytes.length);
+        }
+
+        return buf.toByteArray();
+    }
+
+    /**
+     * Returns a PPPoEDVendorSpecificTag object from a byte array.
+     * @param data byte array data to convert to PPPoEDVendorSpecificTag object.
+     * @return vendor specific tag from given byte array.
+     * */
+    public static PPPoEDVendorSpecificTag fromByteArray(byte[] data) {
+        int position = 0;
+        final int vendorIdLength = 4;
+
+        PPPoEDVendorSpecificTag vendorSpecificTag = new PPPoEDVendorSpecificTag();
+
+        if (data.length < vendorIdLength) {
+            return vendorSpecificTag;
+        }
+
+        int vId = ByteBuffer.wrap(data, position, vendorIdLength).getInt();
+        vendorSpecificTag.setVendorId(vId);
+
+        position += vendorIdLength;
+
+        while (data.length > position) {
+            byte code = data[position];
+            position++;
+
+            if (data.length < position) {
+                break;
+            }
+
+            int length = (int) data[position];
+            position++;
+
+            if (data.length < (position + length)) {
+                break;
+            }
+
+            String clvString = new String(Arrays.copyOfRange(data, position, length + position),
+                                          StandardCharsets.UTF_8);
+
+            position += length;
+
+            if (code == CIRCUIT_ID_OPTION) {
+                vendorSpecificTag.setCircuitId(clvString);
+            } else if (code == REMOTE_ID_OPTION) {
+                vendorSpecificTag.setRemoteId(clvString);
+            }
+        }
+
+        return vendorSpecificTag;
+    }
+}
diff --git a/api/src/main/java/org/opencord/pppoeagent/PppoeAgentEvent.java b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentEvent.java
new file mode 100644
index 0000000..ec491b1
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentEvent.java
@@ -0,0 +1,204 @@
+/*
+ * 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.opencord.pppoeagent;
+
+import org.onlab.packet.MacAddress;
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.ConnectPoint;
+/**
+ * PppoeAgent event.
+ */
+public class PppoeAgentEvent extends AbstractEvent<PppoeAgentEvent.Type, PppoeSessionInfo> {
+    private final ConnectPoint connectPoint;
+    private final MacAddress subscriberMacAddress;
+    private final String counterName;
+    private final Long counterValue;
+    private final String subscriberId;
+
+    // Session terminates may have an error tag with a 'reason' message, this field is meant to track that info.
+    private final String reason;
+
+    public static final String GLOBAL_COUNTER = "global";
+
+    /**
+     * Type of the event.
+     */
+    public enum Type {
+        /**
+         * PPPoE discovery negotiation started.
+         */
+        START,
+
+        /**
+         * PPPoE server is responding to client packets - session is under negotiation.
+         */
+        NEGOTIATION,
+
+        /**
+         * PPPoE discovery negotiation is complete, session is established.
+         */
+        SESSION_ESTABLISHED,
+
+        /**
+         * Client or server event to indicate end of a session.
+         */
+        TERMINATE,
+
+        /**
+         * PPPoE stats update.
+         */
+        STATS_UPDATE,
+
+        /**
+         * Circuit-id mismatch.
+         */
+        INVALID_CID,
+
+        /**
+         * Default value for unknown event.
+         */
+        UNKNOWN
+    }
+
+
+    /**
+     * Creates a new PPPoE counters event.
+     *
+     * @param type type of the event
+     * @param sessionInfo session info
+     * @param counterName name of specific counter
+     * @param counterValue value of specific counter
+     * @param subscriberMacAddress the subscriber MAC address information
+     * @param subscriberId id of subscriber
+     */
+    public PppoeAgentEvent(Type type, PppoeSessionInfo sessionInfo, String counterName, Long counterValue,
+                           MacAddress subscriberMacAddress, String subscriberId) {
+        super(type, sessionInfo);
+        this.counterName = counterName;
+        this.counterValue = counterValue;
+        this.subscriberMacAddress = subscriberMacAddress;
+        this.subscriberId = subscriberId;
+        this.connectPoint = null;
+        this.reason = null;
+    }
+
+    /**
+     * Creates a new generic PPPoE event.
+     *
+     * @param type type of the event
+     * @param sessionInfo session info
+     * @param connectPoint connect point the client is on
+     * @param subscriberMacAddress the subscriber MAC address information
+     */
+    public PppoeAgentEvent(Type type, PppoeSessionInfo sessionInfo, ConnectPoint connectPoint,
+                           MacAddress subscriberMacAddress) {
+        super(type, sessionInfo);
+        this.connectPoint = connectPoint;
+        this.subscriberMacAddress = subscriberMacAddress;
+        this.counterName = null;
+        this.counterValue = null;
+        this.subscriberId = null;
+        this.reason = null;
+    }
+
+    /**
+     * Creates a new PPPOE event with a reason (PADT packets may have a 'reason' field).
+     *
+     * @param type type of the event
+     * @param sessionInfo session info
+     * @param connectPoint connect point the client is on
+     * @param subscriberMacAddress the subscriber MAC address information
+     * @param reason events such TERMINATE may have reason field
+     */
+    public PppoeAgentEvent(Type type, PppoeSessionInfo sessionInfo, ConnectPoint connectPoint,
+                           MacAddress subscriberMacAddress, String reason) {
+        super(type, sessionInfo);
+        this.connectPoint = connectPoint;
+        this.subscriberMacAddress = subscriberMacAddress;
+        this.reason = reason;
+        this.counterName = null;
+        this.counterValue = null;
+        this.subscriberId = null;
+    }
+
+
+    /**
+     * Gets the PPPoE client connect point.
+     *
+     * @return connect point
+     */
+    public ConnectPoint getConnectPoint() {
+        return connectPoint;
+    }
+
+    /**
+     * Gets the subscriber MAC address.
+     *
+     * @return the MAC address from subscriber
+     */
+    public MacAddress getSubscriberMacAddress() {
+        return subscriberMacAddress;
+    }
+
+    /**
+     * Gets the event reason.
+     *
+     * @return event reason.
+     */
+    public String getReason() {
+        return reason;
+    }
+
+    /**
+     * Gets the counter name.
+     *
+     * @return counter name.
+     */
+    public String getCounterName() {
+        return counterName;
+    }
+
+    /**
+     * Gets the counter value.
+     *
+     * @return counter value.
+     */
+    public Long getCounterValue() {
+        return counterValue;
+    }
+
+    /**
+     * Gets the subscriber identifier information.
+     *
+     * @return the Id from subscriber
+     */
+    public String getSubscriberId() {
+        return subscriberId;
+    }
+
+    @Override
+    public String toString() {
+        return "PppoeAgentEvent{" +
+                "connectPoint=" + connectPoint +
+                ", subscriberMacAddress=" + subscriberMacAddress +
+                ", reason='" + reason + '\'' +
+                ", counterName=" + counterName +
+                ", counterValue=" + counterValue +
+                ", subscriberId='" + subscriberId + '\'' +
+                '}';
+    }
+}
diff --git a/api/src/main/java/org/opencord/pppoeagent/PppoeAgentListener.java b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentListener.java
new file mode 100644
index 0000000..03d3501
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentListener.java
@@ -0,0 +1,25 @@
+/*
+ * 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.opencord.pppoeagent;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Listener for PPPoE agents events.
+ */
+public interface PppoeAgentListener extends EventListener<PppoeAgentEvent> {
+}
diff --git a/api/src/main/java/org/opencord/pppoeagent/PppoeAgentService.java b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentService.java
new file mode 100644
index 0000000..616607a
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentService.java
@@ -0,0 +1,34 @@
+/*
+ * 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.opencord.pppoeagent;
+
+import java.util.Map;
+import org.onlab.packet.MacAddress;
+import org.onosproject.event.ListenerService;
+
+/**
+ * PPPoE Agent service.
+ */
+public interface PppoeAgentService extends
+        ListenerService<PppoeAgentEvent, PppoeAgentListener> {
+    Map<MacAddress, PppoeSessionInfo> getSessionsMap();
+
+    /**
+    * Removes all PPPoE agent session entries.
+    */
+    void clearSessionsMap();
+}
diff --git a/api/src/main/java/org/opencord/pppoeagent/PppoeAgentStoreDelegate.java b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentStoreDelegate.java
new file mode 100644
index 0000000..3d2f72e
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/PppoeAgentStoreDelegate.java
@@ -0,0 +1,24 @@
+/*
+ * 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.opencord.pppoeagent;
+
+import org.onosproject.store.StoreDelegate;
+/**
+ * Store delegate for PPPoE Agent store.
+ */
+public interface PppoeAgentStoreDelegate extends StoreDelegate<PppoeAgentEvent> {
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/PppoeSessionInfo.java b/api/src/main/java/org/opencord/pppoeagent/PppoeSessionInfo.java
new file mode 100644
index 0000000..81d7d09
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/PppoeSessionInfo.java
@@ -0,0 +1,204 @@
+/*
+ * 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.opencord.pppoeagent;
+
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.PPPoED;
+import org.onosproject.net.ConnectPoint;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+/**
+ * PppoeAgent session information.
+ */
+public class PppoeSessionInfo {
+    private ConnectPoint clientCp;
+    private ConnectPoint serverCp;
+    private Byte packetCode;
+    private short sessionId;
+    SubscriberAndDeviceInformation subscriber;
+    MacAddress clientMac;
+
+    /**
+     * Creates a new PPPoE session information object.
+     *
+     * @param clientCp   PPPoE client connect-point.
+     * @param serverCp   PPPoE server connect-point.
+     * @param packetCode The packet code of last PPPOED received message.
+     * @param sessionId  session-id value.
+     * @param subscriber Sadis object of PPPoE client.
+     * @param clientMac  MAC address of PPPoE client.
+     */
+    public PppoeSessionInfo(ConnectPoint clientCp, ConnectPoint serverCp, Byte packetCode, short sessionId,
+                            SubscriberAndDeviceInformation subscriber, MacAddress clientMac) {
+        this.clientCp = clientCp;
+        this.serverCp = serverCp;
+        this.packetCode = packetCode;
+        this.sessionId = sessionId;
+        this.subscriber = subscriber;
+        this.clientMac = clientMac;
+    }
+
+    /**
+     * Creates an empty PPPoE session information object.
+     */
+    public PppoeSessionInfo() {
+    }
+
+    /**
+     * Gets the PPPoE client connect-point.
+     *
+     * @return client connect-point.
+     */
+    public ConnectPoint getClientCp() {
+        return clientCp;
+    }
+
+    /**
+     * Sets the PPPoE client connect-point.
+     *
+     * @param clientCp client connect-point.
+     */
+    public void setClientCp(ConnectPoint clientCp) {
+        this.clientCp = clientCp;
+    }
+
+    /**
+     * Gets the PPPoE server connect-point.
+     *
+     * @return server connect-point.
+     */
+    public ConnectPoint getServerCp() {
+        return serverCp;
+    }
+
+    /**
+     * Sets the PPPoE server connect-point.
+     *
+     * @param serverCp server connect-point.
+     */
+    public void setServerCp(ConnectPoint serverCp) {
+        this.serverCp = serverCp;
+    }
+
+    /**
+     * Gets the PPPoE client SADIS object.
+     *
+     * @return client SADIS object.
+     */
+    public SubscriberAndDeviceInformation getSubscriber() {
+        return subscriber;
+    }
+
+    /**
+     * Sets the PPPoE client SADIS object.
+     *
+     * @param subscriber client SADIS object.
+     */
+    public void setSubscriber(SubscriberAndDeviceInformation subscriber) {
+        this.subscriber = subscriber;
+    }
+
+    /**
+     * Gets the PPPoE client MAC address.
+     *
+     * @return client MAC address.
+     */
+    public MacAddress getClientMac() {
+        return clientMac;
+    }
+
+    /**
+     * Sets the PPPoE client MAC address.
+     *
+     * @param clientMac MAC address.
+     */
+    public void setClientMac(MacAddress clientMac) {
+        this.clientMac = clientMac;
+    }
+
+    /**
+     * Gets the PPPoE session-id.
+     *
+     * @return PPPoE session-id.
+     */
+    public short getSessionId() {
+        return sessionId;
+    }
+
+    /**
+     * Sets the PPPoE session-id.
+     *
+     * @param sessionId PPPoE session-id.
+     */
+    public void setSessionId(short sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    /**
+     * Gets the PPPoED packet code of the last received message.
+     *
+     * @return last received PPPoED code.
+     */
+    public Byte getPacketCode() {
+        return packetCode;
+    }
+
+    /**
+     * Sets the PPPoED packet code from the last received message.
+     *
+     * @param packetCode PPPoED packet code.
+     */
+    public void setPacketCode(byte packetCode) {
+        this.packetCode = packetCode;
+    }
+
+    /**
+     * Gets a string to represent the current session state based on the last received packet code.
+     * This function uses conveniently the name of some PPPoEAgentEvent types to represent the state.
+     *
+     * @return PPPOE session state.
+     */
+    public String getCurrentState() {
+        PPPoED.Type lastReceivedPkt = PPPoED.Type.getTypeByValue(this.packetCode);
+        switch (lastReceivedPkt) {
+            case PADI:
+                return PppoeAgentEvent.Type.START.name();
+            case PADR:
+            case PADO:
+                return PppoeAgentEvent.Type.NEGOTIATION.name();
+            case PADS:
+                return PppoeAgentEvent.Type.SESSION_ESTABLISHED.name();
+            case PADT:
+                // This case might never happen (entry is being removed on PADT messages).
+                return PppoeAgentEvent.Type.TERMINATE.name();
+            default:
+                return PppoeAgentEvent.Type.UNKNOWN.name();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "PppoeSessionInfo{" +
+                "clientCp=" + clientCp +
+                ", serverCp=" + serverCp +
+                ", packetCode=" + packetCode +
+                ", sessionId=" + sessionId +
+                ", subscriber=" + subscriber +
+                ", clientMac=" + clientMac +
+                '}';
+    }
+}
+
diff --git a/api/src/main/java/org/opencord/pppoeagent/package-info.java b/api/src/main/java/org/opencord/pppoeagent/package-info.java
new file mode 100644
index 0000000..22b3633
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * API for PPPoE agent.
+ */
+package org.opencord.pppoeagent;
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdBuilder.java b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdBuilder.java
new file mode 100644
index 0000000..f955704
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdBuilder.java
@@ -0,0 +1,437 @@
+/*
+ * 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.opencord.pppoeagent.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang3.Range;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Port;
+import org.onosproject.net.device.DeviceService;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.onosproject.net.Device;
+import org.opencord.sadis.UniTagInformation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Circuit-id builder util.
+ */
+public class CircuitIdBuilder {
+    //#region Circuit-id Builder
+    private CircuitIdConfig circuitIdConfig;
+    private DeviceService deviceService;
+    private BaseInformationService<SubscriberAndDeviceInformation> subsService;
+    private ConnectPoint connectPoint;
+    private String[] exclusiveExpressions;
+    private Map<CircuitIdFieldName, String> customSeparators;
+    private UniTagInformation uniTagInformation;
+
+    private final ArrayList<CircuitIdField> availableFields = new ArrayList<>(
+        Arrays.asList(new CircuitIdAccessNodeIdentifier(),
+                new CircuitIdNetworkTechnology(),
+                new CircuitIdSlot(),
+                new CircuitIdPort(),
+                new CircuitIdOnuSerialNumber(),
+                new CircuitIdUniPortNumber(),
+                new CircuitIdSvId(),
+                new CircuitIdCvId())
+    );
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    /**
+     * Creates a new CircuitId builder with default configuration.
+     */
+    public CircuitIdBuilder() {
+        // Instantiate the circuit-id config Object.
+        circuitIdConfig = new CircuitIdConfig();
+        circuitIdConfig.setSeparator("/");
+        // This is a default list with exclusive expressions to validate the field value.
+        exclusiveExpressions = new String[] {circuitIdConfig.getSeparator()};
+
+        // At the end we set the default field list - in the future these fields will be defined by the operator.
+        circuitIdConfig.setFieldListWithDefault(availableFields);
+        customSeparators = new HashMap<>();
+    }
+
+    /**
+     * Sets the connect-point.
+     *
+     * @param value connect-point.
+     * @return circuit-id builder.
+     */
+    public CircuitIdBuilder setConnectPoint(ConnectPoint value) {
+        this.connectPoint = value;
+        return this;
+    }
+
+    /**
+     * Sets the device service.
+     *
+     * @param value device service.
+     * @return circuit-id builder.
+     */
+    public CircuitIdBuilder setDeviceService(DeviceService value) {
+        this.deviceService = value;
+        return this;
+    }
+
+    /**
+     * Sets the SADIS service.
+     *
+     * @param value SADIS service.
+     * @return circuit-id builder.
+     */
+    public CircuitIdBuilder setSubsService(BaseInformationService<SubscriberAndDeviceInformation> value) {
+        this.subsService = value;
+        return this;
+    }
+
+    /**
+     * Sets the UniTag information.
+     *
+     * @param value UniTag information.
+     * @return circuit-id builder.
+     */
+    public CircuitIdBuilder setUniTagInformation(UniTagInformation value) {
+        this.uniTagInformation = value;
+        return this;
+    }
+
+    /**
+     * Adds a custom separator.
+     *
+     * @param field the circuit-id field associated to the custom separator.
+     * @param separator the custom separator to be added.
+     * @return circuit-id builder.
+     */
+    public CircuitIdBuilder addCustomSeparator(CircuitIdFieldName field, String separator) {
+        if (!circuitIdConfig.getSeparator().equals(separator)) {
+            customSeparators.put(field, separator);
+        } else {
+            log.warn("Not defining custom separator '{}' for {} field since it's equals to the configured separator.",
+                    separator, field.name());
+        }
+
+        return this;
+    }
+
+    /**
+     * Gets the custom separators.
+     *
+     * @return a map with the circuit-id fields and its custom separators.
+     */
+    public Map<CircuitIdFieldName, String> getCustomSeparators() {
+        return customSeparators;
+    }
+
+    /**
+     * Gets the circuit-id configuration object.
+     *
+     * @return the configuration for circuit-id builder.
+     */
+    public CircuitIdConfig getCircuitIdConfig() {
+        return this.circuitIdConfig;
+    }
+
+    /**
+     * Sets the circuit-id configuration.
+     *
+     * @param fieldNameList the list of desired fields.
+     * @return circuit-id builder.
+     */
+    public CircuitIdBuilder setCircuitIdConfig(ArrayList<CircuitIdFieldName> fieldNameList) {
+        circuitIdConfig.setFieldList(fieldNameList, availableFields);
+        return this;
+    }
+
+    /**
+     * Generates the circuit-id based on the provided configuration and default/custom separators.
+     *
+     * @return circuit-id.
+     */
+    public String build() {
+        String circuitId = "";
+
+        // Get the field list.
+        ArrayList<CircuitIdField> fieldList = circuitIdConfig.getFieldList();
+
+        // Check if it's valid.
+        if (fieldList == null || fieldList.size() <= 0) {
+            log.error("Failed to build the circuit Id: there's no entries in the field list.");
+            return "";
+        }
+
+        // This list is filtered to ignore prefix fields.
+        ArrayList<CircuitIdField> filteredFieldList = fieldList;
+
+        // Go throughout the field list to build the string.
+        for (int i = 0; i <  filteredFieldList.size(); i++) {
+            CircuitIdField field = filteredFieldList.get(i);
+
+            String value = "";
+            try {
+                value = field.build();
+            } catch (MissingParameterException | FieldValidationException e) {
+                log.error(e.getMessage());
+                return "";
+            }
+
+            // Concat the obtained value with the rest of id.
+            circuitId = circuitId.concat(value);
+
+            // If this is not the last "for" iteration, isolate with the separator.
+            if (i != (filteredFieldList.size() - 1)) {
+                String separator = customSeparators.containsKey(field.getFieldName()) ?
+                        customSeparators.get(field.getFieldName()) : circuitIdConfig.getSeparator();
+
+                circuitId = circuitId.concat(separator);
+            }
+        }
+        // At the end, it returns the fully built string.
+        return circuitId;
+    }
+    //#endregion
+
+    //#region Field Classes
+    private SubscriberAndDeviceInformation getSadisEntry() {
+        Port p = deviceService.getPort(connectPoint);
+        String subscriber = p.annotations().value(AnnotationKeys.PORT_NAME);
+        return subsService.get(subscriber);
+    }
+
+    private class CircuitIdAccessNodeIdentifier extends CircuitIdField {
+        CircuitIdAccessNodeIdentifier() {
+            this.setFieldName(CircuitIdFieldName.AcessNodeIdentifier)
+                    .setIsNumber(false)
+                    .setExclusiveExpressions(exclusiveExpressions);
+        }
+
+        @Override
+        String build() throws MissingParameterException,
+                              FieldValidationException {
+            // This field must be the serial number of the OLT which the packet came from.
+            // So we try to recover this information using the device Id of the connect point.
+            Device device = deviceService.getDevice(connectPoint.deviceId());
+
+            if (device == null) {
+                String errorMsg = String.format("Device not found at device service: %s.", connectPoint.deviceId());
+                throw new MissingParameterException(errorMsg);
+            }
+
+            String accessNodeId = device.serialNumber();
+
+            if (isValid(accessNodeId)) {
+                return accessNodeId;
+            } else {
+                throw new FieldValidationException(this, accessNodeId);
+            }
+        }
+    }
+
+    private static class CircuitIdSlot extends CircuitIdField {
+        CircuitIdSlot() {
+            this.setFieldName(CircuitIdFieldName.Slot)
+                    .setIsNumber(true)
+                    .setMaxLength(2)
+                    .setRange(Range.between(0L, 99L));
+        }
+
+        @Override
+        String build() {
+            // At first, this will be hard-coded as "0". But this may change in future implementation.
+            return "0";
+        }
+    }
+
+    private class CircuitIdPort extends CircuitIdField {
+        CircuitIdPort() {
+            this.setFieldName(CircuitIdFieldName.Port)
+                    .setIsNumber(true)
+                    .setMaxLength(3)
+                    .setRange(Range.between(1L, 256L));
+        }
+
+        @Override
+        String build() throws MissingParameterException,
+                              FieldValidationException {
+            String port = "";
+
+            // If there's no connect-point we can't build this field.
+            if (connectPoint == null) {
+                String errorMsg = "ConnectPoint not passed to circuit-id builder - can't build PORT field.";
+                throw new MissingParameterException(errorMsg);
+            }
+
+            long connectPointPort = connectPoint.port().toLong();
+            int ponPort = ((int) connectPointPort >> 12) + 1;
+
+            port = String.valueOf(ponPort);
+            if (isValid(port)) {
+                return port;
+            } else {
+                throw new FieldValidationException(this, port);
+            }
+        }
+    }
+
+    private class CircuitIdOnuSerialNumber extends CircuitIdField {
+        CircuitIdOnuSerialNumber() {
+            this.setFieldName(CircuitIdFieldName.OnuSerialNumber)
+                    .setIsNumber(false)
+                    .setExclusiveExpressions(exclusiveExpressions);
+        }
+
+        @Override
+        String build() throws MissingParameterException,
+                              FieldValidationException {
+
+            if (deviceService == null) {
+                String errorMsg = "Device service not passed to circuit-id builder - can't build ONU SN field.";
+                throw new MissingParameterException(errorMsg);
+            }
+
+            Port p = deviceService.getPort(connectPoint);
+            String onuSn = p.annotations().value(AnnotationKeys.PORT_NAME);
+
+            // The reason of the following block is that in some cases, the UNI port number can be
+            // appended in the ONU serial number. So it's required to remove it.
+            CircuitIdUniPortNumber uniPortNumberField = new CircuitIdUniPortNumber();
+            String uniPortNumber = uniPortNumberField.build();
+
+            if (uniPortNumberField.isValid(uniPortNumber)) {
+                // Build the suffix it shouldn't have based on the UNI port number.
+                uniPortNumber = "-" + (Integer.parseInt(uniPortNumber));
+
+                // Checks if the serial number contains this suffix.
+                if (onuSn.substring(onuSn.length() - uniPortNumber.length()).equals(uniPortNumber)) {
+                    // Remove it if this is the case.
+                    onuSn = onuSn.substring(0, onuSn.indexOf(uniPortNumber));
+                }
+
+            } else {
+                log.debug("Failed to get the UNI port number, the ONU serial number could be wrong;");
+            }
+
+
+            if (isValid(onuSn)) {
+                return onuSn;
+            } else {
+                throw new FieldValidationException(this, onuSn);
+            }
+        }
+    }
+
+    private class CircuitIdUniPortNumber extends CircuitIdField {
+        CircuitIdUniPortNumber() {
+            this.setFieldName(CircuitIdFieldName.UniPortNumber)
+                    .setIsNumber(true)
+                    .setMaxLength(2)
+                    .setRange(Range.between(1L, 99L));
+        }
+
+        @Override
+        String build() throws MissingParameterException,
+                              FieldValidationException {
+            // If there's no connect-point we can't build this field.
+            if (connectPoint == null) {
+                String errorMsg = "ConnectPoint not passed to circuit-id builder - can't build UNI PORT NUMBER field.";
+                throw new MissingParameterException(errorMsg);
+            }
+
+            long connectPointPort = connectPoint.port().toLong();
+            int uniPortNumber = ((int) connectPointPort & 0xF) + 1;
+
+            String uniPortString = String.valueOf(uniPortNumber);
+
+            if (isValid(uniPortString)) {
+                return uniPortString;
+            } else {
+                throw new FieldValidationException(this, uniPortString);
+            }
+        }
+    }
+
+    private class CircuitIdSvId extends CircuitIdField {
+        CircuitIdSvId() {
+            this.setFieldName(CircuitIdFieldName.SvID)
+                    .setIsNumber(true)
+                    .setMaxLength(4)
+                    .setRange(Range.between(0L, 4095L));
+        }
+
+        @Override
+        String build() throws MissingParameterException,
+                              FieldValidationException {
+            if (uniTagInformation == null) {
+                String errorMsg = String.format("UNI TAG info not found for %s while looking for S-TAG.", connectPoint);
+                throw new MissingParameterException(errorMsg);
+            }
+
+            String sVid = uniTagInformation.getPonSTag().toString();
+            if (isValid(sVid)) {
+                return sVid;
+            } else {
+                throw new FieldValidationException(this, sVid);
+            }
+        }
+    }
+
+     private class CircuitIdCvId extends CircuitIdField {
+        CircuitIdCvId() {
+            this.setFieldName(CircuitIdFieldName.CvID)
+                    .setIsNumber(true)
+                    .setMaxLength(4)
+                    .setRange(Range.between(0L, 4095L));
+        }
+
+        @Override
+        String build() throws MissingParameterException,
+                              FieldValidationException {
+            if (uniTagInformation == null) {
+                String errorMsg = String.format("UNI TAG info not found for %s looking for C-TAG", connectPoint);
+                throw new MissingParameterException(errorMsg);
+            }
+
+            String cVid = uniTagInformation.getPonCTag().toString();
+            if (isValid(cVid)) {
+                return cVid;
+            } else {
+                throw new FieldValidationException(this, cVid);
+            }
+        }
+    }
+
+    private class CircuitIdNetworkTechnology extends CircuitIdField {
+        CircuitIdNetworkTechnology() {
+            this.setFieldName(CircuitIdFieldName.NetworkTechnology)
+                    .setIsNumber(false)
+                    .setExclusiveExpressions(exclusiveExpressions);
+        }
+
+        // For now this is fixed.
+        @Override
+        String build() {
+            return "xpon";
+        }
+    }
+    //#endregion
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdConfig.java b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdConfig.java
new file mode 100644
index 0000000..3efc369
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdConfig.java
@@ -0,0 +1,128 @@
+/*
+ * 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.opencord.pppoeagent.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Configuration options for circuit-id builder.
+ */
+public class CircuitIdConfig {
+    private String separator;
+    private ArrayList<CircuitIdField> fieldList;
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    // This list defines the default configuration for circuit-id.
+    // Circuit-id will follow the same order of this list.
+    private final ArrayList<CircuitIdFieldName> defaultFieldSelection = new ArrayList<>(
+            Arrays.asList(CircuitIdFieldName.AcessNodeIdentifier,
+                    CircuitIdFieldName.NetworkTechnology,
+                    CircuitIdFieldName.Slot,
+                    CircuitIdFieldName.Port,
+                    CircuitIdFieldName.OnuSerialNumber,
+                    CircuitIdFieldName.UniPortNumber,
+                    CircuitIdFieldName.SvID,
+                    CircuitIdFieldName.CvID)
+    );
+
+    /**
+     * Gets the default separator.
+     *
+     * @return separator.
+     */
+    public String getSeparator() {
+        return separator;
+    }
+
+    /**
+     * Sets the default separator.
+     *
+     * @param  value separator.
+     * @return circuit-id configuration object.
+     */
+    public CircuitIdConfig setSeparator(String value) {
+        this.separator = value;
+        return this;
+    }
+
+    /**
+     * Gets the circuit-id field list.
+     *
+     * @return circuit-id field list.
+     */
+    public ArrayList<CircuitIdField> getFieldList() {
+        return fieldList;
+    }
+
+    private CircuitIdConfig setFieldList(ArrayList<CircuitIdField> value) {
+        this.fieldList = value;
+        return this;
+    }
+
+    /**
+     * Sets the field list considering a list of supported fields.
+     *
+     * @param fieldSelection   desired field list.
+     * @param availableFields  available field list.
+     * @return circuit-id config object.
+     */
+    public CircuitIdConfig setFieldList(ArrayList<CircuitIdFieldName> fieldSelection,
+                                        ArrayList<CircuitIdField> availableFields) {
+        // Find out the fields based on the field selection for this config.
+        ArrayList<CircuitIdField> value = fieldSelection
+                .stream()
+                .map(fieldName -> availableFields
+                        .stream()
+                        .filter(field -> field.getFieldName()
+                                .equals(fieldName))
+                        .findFirst()
+                        .orElse(null))
+                .collect(Collectors.toCollection(ArrayList::new));
+
+        boolean foundAllFields = value.stream().noneMatch(Objects::isNull);
+
+        // Ignores if it found all fields.
+        if (!foundAllFields) {
+            // Otherwise, it log and remove null entries.
+            log.warn("Some desired fields are not available.");
+            value.removeAll(Collections.singleton(null));
+        }
+
+        setFieldList(value);
+        return this;
+    }
+
+    /**
+     * Sets the field list with the default values considering a list of available fields.
+     *
+     * @param availableFields available field list.
+     * @return circuit-id config object.
+     */
+    public CircuitIdConfig setFieldListWithDefault(ArrayList<CircuitIdField> availableFields) {
+        return setFieldList(this.getDefaultFieldSelection(), availableFields);
+    }
+
+    private ArrayList<CircuitIdFieldName> getDefaultFieldSelection() {
+        return defaultFieldSelection;
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdField.java b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdField.java
new file mode 100644
index 0000000..f44aaf3
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdField.java
@@ -0,0 +1,119 @@
+/*
+ * 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.opencord.pppoeagent.util;
+
+import org.apache.commons.lang3.Range;
+
+import java.util.Arrays;
+
+/**
+ *  Circuit-id field abstract model.
+ *  This is a model that shouldn't be instantiated, for new fields please create a class that extends this one.
+ */
+public abstract class CircuitIdField {
+    // Definitions to perform general validation.
+    private Integer maxLength;
+    private boolean isRangeLike;
+    private Range<Long> range;
+    private CircuitIdFieldName fieldName;
+    private boolean isNumber;
+
+    // This is used to avoid conflicts in string values,
+    // Example: when a field value contains the same char as the separator.
+    private String[] exclusiveExpressions;
+
+    CircuitIdFieldName getFieldName() {
+        return this.fieldName;
+    }
+
+    CircuitIdField setFieldName(CircuitIdFieldName value) {
+        this.fieldName = value;
+        return this;
+    }
+
+    Integer getMaxLength() {
+        return this.maxLength;
+    }
+
+    CircuitIdField setMaxLength(int value) {
+        this.maxLength = value;
+        return this;
+    }
+
+    Range<Long> getRange() {
+        return isRangeLike ? range : null;
+    }
+
+    CircuitIdField setRange(Range<Long> value) {
+        this.isRangeLike = value != null;
+        this.range = value;
+        return this;
+    }
+
+    CircuitIdField setIsNumber(boolean value) {
+        this.isNumber = value;
+        return this;
+    }
+
+    CircuitIdField setExclusiveExpressions(String[] value) {
+        this.exclusiveExpressions = value;
+        return this;
+    }
+
+    // It's recommended to call this method after building the field value, this will
+    // perform general validations based on the definitions of the field (range, maxLength, isNumber, (...))
+    boolean isValid(String value) {
+        // This is the flag is the output of the assertions.
+        boolean challenge = true;
+
+        if (value == null) {
+            return false;
+        }
+
+        // This code block is only in the case we're validating a number:
+        long longValue = 0L;
+        if (isNumber) {
+            try {
+                // We try to parse the value.
+                longValue = Long.parseLong(value);
+                // If there's a range defined.
+                if (isRangeLike) {
+                    // We verify if the value is at the defined range.
+                    challenge &= range.contains(longValue);
+                }
+            } catch (NumberFormatException e) {
+                // If something goes wrong in the conversion, it means the number is invalid.
+                return false;
+            }
+        } else if (exclusiveExpressions != null) {
+            // When exclusive expressions are defined, it verifies if the value contains one of the expressions.
+            // If this is the case, it's an invalid value.
+            challenge = Arrays.stream(exclusiveExpressions)
+                    .noneMatch(exp -> value.contains(exp));
+        }
+
+        // At the end, it verifies if the value respects the max length. But only if the max length is defined.
+        if (maxLength != null) {
+            challenge &= value.length() <= maxLength;
+        }
+        return challenge;
+    }
+
+    // Each field should implement its own build method.
+    String build() throws MissingParameterException, FieldValidationException {
+        return "";
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdFieldName.java b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdFieldName.java
new file mode 100644
index 0000000..14cbab9
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/CircuitIdFieldName.java
@@ -0,0 +1,34 @@
+/*
+ * 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.opencord.pppoeagent.util;
+
+/**
+ * Represents the field names for circuit-id.
+ */
+public enum CircuitIdFieldName {
+    AcessNodeIdentifier(1),
+    Slot(2),
+    Port(3),
+    OnuSerialNumber(4),
+    UniPortNumber(5),
+    SvID(6),
+    CvID(7),
+    NetworkTechnology(8);
+    private final int value;
+    CircuitIdFieldName(int value) {
+        this.value = value;
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/FieldValidationException.java b/api/src/main/java/org/opencord/pppoeagent/util/FieldValidationException.java
new file mode 100644
index 0000000..8754e74
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/FieldValidationException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.opencord.pppoeagent.util;
+
+/**
+ * Exception meant to be thrown when circuit-id builder builds a field which the result
+ * is not valid according to the validation criteria.
+ */
+public class FieldValidationException extends Exception {
+    public FieldValidationException(CircuitIdField field, String value) {
+        super(String.format("Failed to build the circuit ID. %s field is not valid: %s",
+                field.getFieldName().name(), value));
+    }
+
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/MissingParameterException.java b/api/src/main/java/org/opencord/pppoeagent/util/MissingParameterException.java
new file mode 100644
index 0000000..b6f8ec5
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/MissingParameterException.java
@@ -0,0 +1,26 @@
+/*
+ * 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.opencord.pppoeagent.util;
+
+/**
+ * Exception meant to be thrown when circuit-id builder tries to build a
+ * field but there are missing parameterization.
+ */
+public class MissingParameterException extends Exception {
+    public MissingParameterException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/api/src/main/java/org/opencord/pppoeagent/util/package-info.java b/api/src/main/java/org/opencord/pppoeagent/util/package-info.java
new file mode 100644
index 0000000..6e02984
--- /dev/null
+++ b/api/src/main/java/org/opencord/pppoeagent/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * PPPoE agent util api.
+ */
+package org.opencord.pppoeagent.util;