First implementation of BNG application

The application offers network level APIs for the BNG data plane.

An attachment (BngAttachment) at network level is identified by an arbitrary string.
The exposed APIs are key-value like APIs.

Change-Id: If0e484f487ea16dd8c7dd99642f75686e1dbc29a
diff --git a/app/src/main/java/org/opencord/bng/packets/GenericPpp.java b/app/src/main/java/org/opencord/bng/packets/GenericPpp.java
new file mode 100644
index 0000000..464bf2d
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/GenericPpp.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+import com.google.common.base.Objects;
+import org.onlab.packet.Data;
+import org.onlab.packet.Deserializer;
+
+import java.nio.ByteBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Implements the Link Control Protocol (LCP) header.
+ */
+
+public class GenericPpp extends Ppp {
+
+    // TODO: Add support for TLV options.
+
+    public static final byte CHAP_CODE_CHALLENGE = 0x01;
+    public static final byte CHAP_CODE_RESPONSE = 0x02;
+    public static final byte CHAP_CODE_SUCCESS = 0x03;
+    public static final byte CHAP_CODE_FAILURE = 0x04;
+
+    public static final byte PAP_AUTH_REQ = 0x01;
+    public static final byte PAP_AUTH_ACK = 0x02;
+    public static final byte PAP_AUTH_NACK = 0x03;
+
+    public static final byte CODE_TERM_REQ = 0x05;
+    public static final byte CODE_TERM_ACK = 0x06;
+
+
+    /**
+     * Deserializer function for LCP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<GenericPpp> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, MIN_HEADER_LENGTH);
+            GenericPpp ppp = new GenericPpp();
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            ppp.code = bb.get();
+            ppp.identifier = bb.get();
+            ppp.length = bb.getShort();
+            if (ppp.length > MIN_HEADER_LENGTH) {
+                ppp.payload = Data.deserializer()
+                        .deserialize(data, bb.position(),
+                                     Math.min(bb.limit() - bb.position(),
+                                              ppp.length - MIN_HEADER_LENGTH));
+                ppp.payload.setParent(ppp);
+            }
+            return ppp;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        int payloadLength = 0;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+            payloadLength = payloadData.length + MIN_HEADER_LENGTH;
+        }
+        int realLength = this.length > payloadLength ? this.length : payloadLength;
+        final byte[] data = new byte[realLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(this.code);
+        bb.put(this.identifier);
+        bb.putShort(this.length);
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+        return data;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hashCode(code, identifier, length);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final GenericPpp other = (GenericPpp) obj;
+        return Objects.equal(this.code, other.code)
+                && Objects.equal(this.identifier, other.identifier)
+                && Objects.equal(this.length, other.length);
+    }
+}
+
diff --git a/app/src/main/java/org/opencord/bng/packets/Ipcp.java b/app/src/main/java/org/opencord/bng/packets/Ipcp.java
new file mode 100644
index 0000000..d550005
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/Ipcp.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.IpAddress;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onlab.packet.PacketUtils.checkHeaderLength;
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Implements the IP Control Protocol (IPCP) packet format.
+ */
+
+public class Ipcp extends Ppp {
+
+    public static final byte CONF_REQ = 0x01;
+    public static final byte ACK = 0x02;
+    public static final byte NAK = 0x03;
+
+    private PppTlv ipaddresstlv = null;
+
+    private List<PppTlv> pppTlvList;
+
+    Ipcp() {
+        super();
+        pppTlvList = Lists.newLinkedList();
+    }
+
+    /**
+     * Gets the IP Address TLV field.
+     *
+     * @return the IP Address TLV field if present, null otherwise
+     */
+    public PppTlv getIpAddressTlv() {
+        return this.ipaddresstlv;
+    }
+
+    /**
+     * Gets the IP address TLV field as an IpAddress object.
+     *
+     * @return The IP address TLV field as IpAddress object, it will be address
+     * 0 if no TLV address is present in the packet
+     */
+    public IpAddress getIpAddress() {
+        return IpAddress.valueOf(IpAddress.Version.INET,
+                                 this.getIpAddressTlv().getValue());
+    }
+
+    /**
+     * Sets the IP address TLV field.
+     *
+     * @param ipaddresstlv the IP address TLV to set
+     * @return this
+     */
+    public Ipcp setIpAddressTlv(PppTlv ipaddresstlv) {
+        this.ipaddresstlv = ipaddresstlv;
+        return this;
+    }
+
+    /**
+     * Gets the TLV field list.
+     *
+     * @return the IPCP TLV field list
+     */
+    public List<PppTlv> getIpcpTlvList() {
+        return this.pppTlvList;
+    }
+
+    @Override
+    public byte[] serialize() {
+        // TODO: Can it have any payload?
+        final byte[] data = new byte[this.length];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(this.code);
+        bb.put(this.identifier);
+        bb.putShort(this.length);
+        if (ipaddresstlv != null) {
+            bb.put(ipaddresstlv.serialize());
+        }
+        if (this.pppTlvList != null) {
+            for (final PppTlv tlv : this.pppTlvList) {
+                bb.put(tlv.serialize());
+            }
+        }
+        return data;
+    }
+
+    /**
+     * Deserializer function for IPCP packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Ipcp> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, MIN_HEADER_LENGTH);
+            Ipcp ipcp = new Ipcp();
+            ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            ipcp.code = bb.get();
+            ipcp.identifier = bb.get();
+            ipcp.length = bb.getShort();
+            short currentIndex = MIN_HEADER_LENGTH;
+            PppTlv tlv;
+            while (currentIndex < ipcp.length) {
+                // Each new TLV IPCP must be a minimum of 2 bytes
+                // (containing the type and length fields).
+                currentIndex += 2;
+                checkHeaderLength(length, currentIndex);
+
+                tlv = (new PppTlv()).deserialize(bb);
+                // if there was a failure to deserialize stop processing TLVs
+                if (tlv == null) {
+                    break;
+                }
+                if (tlv.getType() == PppTlv.IPCPTLV_IP_ADDRESS) {
+                    ipcp.ipaddresstlv = tlv;
+                } else {
+                    ipcp.pppTlvList.add(tlv);
+                }
+                currentIndex += tlv.getLength() - 2;
+            }
+            if (currentIndex != ipcp.length) {
+                throw new DeserializationException("Length of packet do not correspond to IPCP TLVs options");
+            }
+            return ipcp;
+        };
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hashCode(ipaddresstlv);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final Ipcp other = (Ipcp) obj;
+        return Objects.equal(this.ipaddresstlv, other.ipaddresstlv);
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("code", code)
+                .add("identifier", identifier)
+                .add("length", length)
+                .add("pppTlvList", pppTlvList)
+                .add("ipaddresstlv", ipaddresstlv)
+                .toString();
+    }
+}
diff --git a/app/src/main/java/org/opencord/bng/packets/Ppp.java b/app/src/main/java/org/opencord/bng/packets/Ppp.java
new file mode 100644
index 0000000..3785d51
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/Ppp.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+import com.google.common.base.Objects;
+import org.onlab.packet.BasePacket;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Implements a generic PPP header.
+ */
+
+public abstract class Ppp extends BasePacket {
+
+    static final int MIN_HEADER_LENGTH = 4;
+
+    byte code;
+    byte identifier;
+    short length; // Length includes the code, identifier, length and data fields
+
+    /**
+     * Gets the LCP code.
+     *
+     * @return the LCP code
+     */
+    public byte getCode() {
+        return code;
+    }
+
+    /**
+     * Sets the LCP code.
+     *
+     * @param code the LCP code to set
+     */
+    public void setCode(byte code) {
+        this.code = code;
+    }
+
+    /**
+     * Gets the LCP identifier.
+     *
+     * @return the LCP identifier
+     */
+    public byte getIdentifier() {
+        return identifier;
+    }
+
+    /**
+     * Sets the LCP identifier.
+     *
+     * @param identifier the LCP identifier to set
+     */
+    public void setIdentifier(byte identifier) {
+        this.identifier = identifier;
+    }
+
+    /**
+     * Gets the length.
+     *
+     * @return the length
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * Sets the length.
+     *
+     * @param length the length to set
+     */
+    public void setLength(short length) {
+        this.length = length;
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(getClass())
+//                .add("PPPType", pppProtocol)
+                .add("code", code)
+                .add("identifier", identifier)
+                .add("length", length)
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Objects.hashCode(code, identifier, length);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final Ppp other = (Ppp) obj;
+        return Objects.equal(this.code, other.code)
+                && Objects.equal(this.identifier, other.identifier)
+                && Objects.equal(this.length, other.length);
+    }
+}
diff --git a/app/src/main/java/org/opencord/bng/packets/PppProtocolType.java b/app/src/main/java/org/opencord/bng/packets/PppProtocolType.java
new file mode 100644
index 0000000..b3f102d
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/PppProtocolType.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+/**
+ * PPP Protocol type enumerator.
+ */
+public enum PppProtocolType {
+    LCP(0xc021, "lcp", true),
+    IPCP(0x8021, "ipcp", true),
+    PAP(0xc023, "pap", true),
+    CHAP(0xc223, "chap", true),
+    IPv4(0x0021, "ipv4", false),
+    IPv6(0x0057, "ipv6", false),
+    NO_PROTOCOL(0, "no_proto", true);
+
+    private final short code;
+    private final String type;
+    private final boolean control;
+
+    /**
+     * Constructs new PPP Protocol types.
+     *
+     * @param code         The PPP Protocol type.
+     * @param type         Textual representation of the PPP Protocol type.
+     * @param control      True if is control plane packet, false otherwise.
+     */
+    PppProtocolType(int code, String type, boolean control) {
+        this.code = (short) (code & 0xFFFF);
+        this.type = type;
+        this.control = control;
+    }
+
+    /**
+     * Lookups for a PPP Protocol type.
+     *
+     * @param code The code for PPP protocol.
+     * @return The PPPProtocol type
+     */
+    public static PppProtocolType lookup(short code) {
+        for (PppProtocolType type : PppProtocolType.values()) {
+            if (code == type.code()) {
+                return type;
+            }
+        }
+        return NO_PROTOCOL;
+    }
+
+    /**
+     * Returns code associated to the PPP protocol.
+     *
+     * @return The code for PPP protocol
+     */
+    public short code() {
+        return this.code;
+    }
+
+    /**
+     * Returns the string representation of the PPP protocol.
+     *
+     * @return The PPP protocol string representation
+     */
+    public String type() {
+        return this.type;
+    }
+
+    /**
+     * Checks if the PPP protocol is carrying control plane packets.
+     *
+     * @return True if the PPP protocol is for control plane packets, false
+     * otherwise
+     */
+    public boolean control() {
+        return this.control;
+    }
+}
diff --git a/app/src/main/java/org/opencord/bng/packets/PppTlv.java b/app/src/main/java/org/opencord/bng/packets/PppTlv.java
new file mode 100644
index 0000000..366ef7b
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/PppTlv.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+import com.google.common.base.Objects;
+import org.onlab.packet.DeserializationException;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Implements IPCP Type-Length-Value options.
+ */
+public class PppTlv {
+    public static final byte IPCPTLV_IP_ADDRESS = 0x03;
+    private byte type;
+    private byte length; // Including the 2 byte of Minimum header
+    private byte[] value;
+
+    /**
+     * Get the TLV type.
+     *
+     * @return the TLV type
+     */
+    public byte getType() {
+        return type;
+    }
+
+    /**
+     * Set the TLV type.
+     *
+     * @param type the TLV type to set
+     * @return this
+     */
+    public PppTlv setType(byte type) {
+        this.type = type;
+        return this;
+    }
+
+    /**
+     * Get the TLV length. The length include the 2 bytes of minimum header
+     * length.
+     *
+     * @return the TLV length
+     */
+    public byte getLength() {
+        return length;
+    }
+
+    /**
+     * Set the TLV length. Length must include the 2 bytes of minimum header
+     * length.
+     *
+     * @param length the TLV length to set.
+     * @return this
+     */
+    public PppTlv setLength(byte length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * Get the TLV value field as byte array.
+     *
+     * @return the TLV value field
+     */
+    public byte[] getValue() {
+        return value;
+    }
+
+    /**
+     * Set the TLV valued field.
+     *
+     * @param value the TLV value to set
+     * @return this
+     */
+    public PppTlv setValue(byte[] value) {
+        this.value = value;
+        return this;
+    }
+
+    public byte[] serialize() {
+        final byte[] data = new byte[this.length];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put(type);
+        bb.put(length);
+        if (this.value != null) {
+            bb.put(this.value);
+        }
+        return data;
+    }
+
+    public PppTlv deserialize(final ByteBuffer bb) throws DeserializationException {
+        if (bb.remaining() < 2) {
+            throw new DeserializationException(
+                    "Not enough bytes to deserialize PPP TLV options");
+        }
+        this.type = bb.get();
+        this.length = bb.get();
+        if (this.length > 2) {
+            this.value = new byte[this.length - 2];
+
+            // if there is an underrun just toss the TLV
+            // Length include the length of the TLV header itself
+            if (bb.remaining() < this.length - 2) {
+                throw new DeserializationException(
+                        "Remaining bytes are less then the length of the PPP TLV tag");
+            }
+            bb.get(this.value);
+            return this;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        sb.append("type= ");
+        sb.append(this.type);
+        sb.append("length= ");
+        sb.append(this.length);
+        sb.append("value= ");
+        sb.append(Arrays.toString(this.value));
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof PppTlv)) {
+            return false;
+        }
+        final PppTlv other = (PppTlv) obj;
+        if (this.length != other.length) {
+            return false;
+        }
+        if (this.type != other.type) {
+            return false;
+        }
+        if (!Arrays.equals(this.value, other.value)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(type, length, Arrays.hashCode(value));
+    }
+}
diff --git a/app/src/main/java/org/opencord/bng/packets/Pppoe.java b/app/src/main/java/org/opencord/bng/packets/Pppoe.java
new file mode 100644
index 0000000..01945c8
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/Pppoe.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableMap;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.Data;
+import org.onlab.packet.Deserializer;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPacket;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.IPv6;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+import static org.onlab.packet.PacketUtils.checkHeaderLength;
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Implements PPPoE packet format.
+ */
+public class Pppoe extends BasePacket {
+
+    public static final short TYPE_PPPOED = (short) 0x8863;
+    public static final short TYPE_PPPOES = (short) 0x8864;
+
+    static final ImmutableMap<Short, Deserializer<? extends IPacket>> PROTOCOL_DESERIALIZER_MAP =
+            ImmutableMap.<Short, Deserializer<? extends IPacket>>builder()
+                    //FIXME: write the correct parser for LCP, PAP, and CHAP
+                    .put(PppProtocolType.LCP.code(), GenericPpp.deserializer())
+                    .put(PppProtocolType.PAP.code(), GenericPpp.deserializer())
+                    .put(PppProtocolType.CHAP.code(), GenericPpp.deserializer())
+                    .put(PppProtocolType.IPCP.code(), Ipcp.deserializer())
+                    .put(PppProtocolType.IPv4.code(), IPv4.deserializer())
+                    .put(PppProtocolType.IPv6.code(), IPv6.deserializer())
+                    .build();
+    // Account of PPPoE standard header
+    static final int HEADER_LENGTH = 6;
+    // Fields part of PPPoE
+    private byte version; // 4bit
+    private byte typeId;  // 4bit
+    private PppoeType packetType; // 8bit => code in PPPoE header
+    private short sessionId; // 16bit
+    private short payloadLength; // 16bit
+    // FIXME: use PPPProtocol enum type
+    private short pppProtocol = 0;
+    //TODO: TLV TAGs (ref. to RFC2516)
+    private PppoeTlvTag acName = null;
+    private PppoeTlvTag serviceName = null;
+    private List<PppoeTlvTag> optionalTagTlvList;
+
+    public Pppoe() {
+        super();
+        optionalTagTlvList = new LinkedList<>();
+    }
+
+    /**
+     * Get the packet type.
+     *
+     * @return the packet type
+     */
+    public PppoeType getPacketType() {
+        return packetType;
+    }
+
+    /**
+     * Set the Packet Type.
+     *
+     * @param type The packet type to set
+     * @return this
+     */
+    public Pppoe setPacketType(PppoeType type) {
+        this.packetType = type;
+        return this;
+    }
+
+    /**
+     * Get the session ID.
+     *
+     * @return the session ID
+     */
+    public short getSessionId() {
+        return this.sessionId;
+    }
+
+    /**
+     * Set the session ID.
+     *
+     * @param sessionId the session ID to set
+     * @return this
+     */
+    public Pppoe setSessionId(short sessionId) {
+        this.sessionId = sessionId;
+        return this;
+    }
+
+    /**
+     * Get the Point-to-Point Protocol.
+     *
+     * @return the Point-to-Point Protocol
+     */
+    public short getPppProtocol() {
+        return this.pppProtocol;
+    }
+
+    /**
+     * Get the AC-Name.
+     *
+     * @return the AC-Name
+     */
+    public PppoeTlvTag getAcName() {
+        return this.acName;
+    }
+
+    /**
+     * Set the AC-Name.
+     *
+     * @param acname AC-Name to set
+     * @return this
+     */
+    public Pppoe setAcName(final PppoeTlvTag acname) {
+        this.acName = acname;
+        return this;
+    }
+
+    /**
+     * Get the PPPoE version field.
+     *
+     * @return The version field
+     */
+    public byte getVersion() {
+        return this.version;
+    }
+
+    /**
+     * Set the version field.
+     *
+     * @param version version to set
+     * @return this
+     */
+    public Pppoe setVersion(byte version) {
+        this.version = (byte) (version & 0xF);
+        return this;
+    }
+
+    /**
+     * Get the PPPoE type ID.
+     *
+     * @return The type ID
+     */
+    public short getTypeId() {
+        return this.typeId;
+    }
+
+    /**
+     * Set the type ID.
+     *
+     * @param typeId Type ID to set
+     * @return this
+     */
+    public Pppoe setTypeId(byte typeId) {
+        this.typeId = (byte) (typeId & 0xF);
+        return this;
+    }
+
+    /**
+     * Get the PPPoE payload length header field.
+     *
+     * @return The payload length
+     */
+    public short getPayloadLength() {
+        return this.payloadLength;
+    }
+
+    /**
+     * Set the payload length.
+     *
+     * @param payloadLength the payload length to set.
+     * @return this
+     */
+    public Pppoe setPayloadLength(short payloadLength) {
+        this.payloadLength = payloadLength;
+        return this;
+    }
+
+    @Override
+    public byte[] serialize() {
+        byte[] payloadData = null;
+        int payloadLength = 0;
+        if (this.payload != null) {
+            this.payload.setParent(this);
+            payloadData = this.payload.serialize();
+            payloadLength = payloadData.length + HEADER_LENGTH +
+                    (this.packetType == PppoeType.SESSION ? 2 : 0);
+        }
+        // PayloadLength account for PPP header field
+        int realLength = Math.max(this.payloadLength + HEADER_LENGTH, payloadLength);
+        final byte[] data = new byte[realLength];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put((byte) (this.version << 4 | this.typeId & 0xf));
+        bb.put(this.packetType.code);
+        bb.putShort(this.sessionId);
+        bb.putShort(this.payloadLength);
+        if (this.packetType != PppoeType.SESSION) {
+            // Only session packet have PPP header
+            bb.putShort(this.pppProtocol);
+        } else {
+            // Only NON session packet have options
+            // TLV Tags
+            if (acName != null) {
+                bb.put(acName.serialize());
+            }
+            if (serviceName != null) {
+                bb.put(serviceName.serialize());
+            }
+            if (this.optionalTagTlvList != null) {
+                for (final PppoeTlvTag tlv : this.optionalTagTlvList) {
+                    bb.put(tlv.serialize());
+                }
+            }
+        }
+        if (payloadData != null) {
+            bb.put(payloadData);
+        }
+        return data;
+    }
+
+    /**
+     * Deserializer function for PPPoE packets.
+     *
+     * @return deserializer function
+     */
+    public static Deserializer<Pppoe> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            Pppoe pppoe = new Pppoe();
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            byte versionAndType = bb.get();
+            pppoe.version = (byte) (versionAndType >> 4 & 0xF);
+            pppoe.typeId = (byte) (versionAndType & 0xF);
+            byte code = bb.get();
+            pppoe.sessionId = bb.getShort();
+            pppoe.payloadLength = bb.getShort();
+            pppoe.packetType = PppoeType.lookup(code);
+            // Check if the PPPoE packet is a SESSION packet
+            if (pppoe.packetType == PppoeType.SESSION) {
+                // Parse inner protocols
+                pppoe.pppProtocol = bb.getShort();
+                Deserializer<? extends IPacket> deserializer;
+                if (Pppoe.PROTOCOL_DESERIALIZER_MAP.containsKey(pppoe.pppProtocol)) {
+                    deserializer = PROTOCOL_DESERIALIZER_MAP.get(pppoe.pppProtocol);
+                } else {
+                    deserializer = Data.deserializer();
+                }
+                int remainingLength = bb.limit() - bb.position();
+                int bytesToRead = Math.min(pppoe.payloadLength - 2, remainingLength);
+                if (bytesToRead > 0) {
+                    pppoe.payload = deserializer.deserialize(data, bb.position(), bytesToRead);
+                    pppoe.payload.setParent(pppoe);
+                }
+            } else {
+                // PPPoE Packet is of Discovery type
+                // Parse TLV PPPoED Tags
+                int currentIndex = HEADER_LENGTH;
+                PppoeTlvTag tlv;
+                do {
+                    // Each new TLV PPPoE TAG must be a minimum of 4 bytes
+                    // (containing the type and length fields).
+                    if (length - currentIndex < 4) {
+                        // Probably the packet was zero-padded to reach the Ethernet minimum length.
+                        // Let's skip and accept the packet
+                        // FIXME: is there a "smarter" way to identify a padded packet?
+                        break;
+                    }
+                    currentIndex += 4;
+                    checkHeaderLength(length, currentIndex);
+                    tlv = new PppoeTlvTag().deserialize(bb);
+                    // if there was a failure to deserialize stop processing TLVs
+                    if (tlv == null) {
+                        break;
+                    }
+                    switch (tlv.getTagType()) {
+                        case PppoeTlvTag.PPPOED_TAGTYPE_EOL:
+                            // end delimiter
+                            break;
+                        case PppoeTlvTag.PPPOED_TAGTYPE_SERVICENAME:
+                            // Service Name
+                            pppoe.serviceName = tlv;
+                            break;
+                        case PppoeTlvTag.PPPOED_TAGTYPE_ACNAME:
+                            // AC-Name
+                            pppoe.acName = tlv;
+                            break;
+                        default:
+                            pppoe.optionalTagTlvList.add(tlv);
+                            break;
+                    }
+                    currentIndex += tlv.getLength();
+                } while (tlv.getTagType() != 0 && currentIndex < length);
+            }
+            return pppoe;
+        };
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(getClass())
+                .add("version", Byte.toString(version))
+                .add("typeId", Byte.toString(typeId))
+                .add("code", Byte.toString(packetType.code))
+                .add("sessionId", Short.toString(sessionId))
+                .add("payloadLength", Short.toString(payloadLength))
+                .toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() +
+                Objects.hashCode(version, typeId, packetType,
+                                 sessionId, payloadLength, pppProtocol,
+                                 acName, serviceName, optionalTagTlvList);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        if (!super.equals(obj)) {
+            return false;
+        }
+        final Pppoe other = (Pppoe) obj;
+        return Objects.equal(this.version, other.version)
+                && Objects.equal(this.typeId, other.typeId)
+                && Objects.equal(this.packetType, other.packetType)
+                && Objects.equal(this.sessionId, other.sessionId)
+                && Objects.equal(this.payloadLength, other.payloadLength)
+                && Objects.equal(this.pppProtocol, other.pppProtocol)
+                && Objects.equal(this.acName, other.acName)
+                && Objects.equal(this.serviceName, other.serviceName)
+                && Objects.equal(this.optionalTagTlvList, other.optionalTagTlvList);
+    }
+
+    /**
+     * PPPoE Discovery types.
+     */
+    public enum PppoeType {
+        PADI(0x9, "padi"),
+        PADO(0x7, "pado"),
+        PADR(0x19, "padr"),
+        PADS(0x65, "pads"),
+        PADT(0xa7, "padt"),
+        SESSION(0x0, "session"),
+        UNKNOWN(0xFF, "unknown");
+
+        private final byte code;
+        private final String type;
+
+        /**
+         * Constructs new PPPoE Discovery types.
+         *
+         * @param code The PPPoED type.
+         * @param type Textual representation of the PPPoED type.
+         */
+        PppoeType(int code, String type) {
+            this.code = (byte) (code & 0xFF);
+            this.type = type;
+        }
+
+        public static PppoeType lookup(byte code) {
+            for (PppoeType type : PppoeType.values()) {
+                if (code == type.code()) {
+                    return type;
+                }
+            }
+            return UNKNOWN;
+        }
+
+        public byte code() {
+            return code;
+        }
+
+        public String type() {
+            return type;
+        }
+    }
+
+    /**
+     * Checks if the passed Ethernet packet is PPPoE Service.
+     *
+     * @param eth Packet to check
+     * @return True if the packet contains PPPoE Service header
+     */
+    public static boolean isPPPoES(Ethernet eth) {
+        return eth.getEtherType() == TYPE_PPPOES;
+    }
+
+    /**
+     * Checks if the passed Ethernet packet is PPPoE Discovery.
+     *
+     * @param eth Packet to check
+     * @return True if the packet contains PPPoE Discovery header
+     */
+    public static boolean isPPPoED(Ethernet eth) {
+        return eth.getEtherType() == TYPE_PPPOED;
+    }
+}
diff --git a/app/src/main/java/org/opencord/bng/packets/PppoeTlvTag.java b/app/src/main/java/org/opencord/bng/packets/PppoeTlvTag.java
new file mode 100644
index 0000000..d315e7f
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/PppoeTlvTag.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2019-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.bng.packets;
+
+import com.google.common.base.Objects;
+import org.onlab.packet.DeserializationException;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * Implements the PPPoE Type-Length-Value TAGs.
+ */
+public class PppoeTlvTag {
+
+    public static final short PPPOED_TAGTYPE_EOL = 0;
+    public static final short PPPOED_TAGTYPE_SERVICENAME = 0x0101;
+    public static final short PPPOED_TAGTYPE_ACNAME = 0x0102;
+
+    private short tagType;
+    private short length; // Length excluded the header (4 bytes minimum header)
+    private byte[] value;
+
+    /**
+     * Gets the TLV TAG type.
+     *
+     * @return the TLV TAG type
+     */
+    public short getTagType() {
+        return tagType;
+    }
+
+    /**
+     * Sets the TLV TAG type.
+     *
+     * @param tagType the type to set
+     * @return this
+     */
+    public PppoeTlvTag setTagType(short tagType) {
+        this.tagType = tagType;
+        return this;
+    }
+
+    /**
+     * Gets the length, number of bytes of value field.
+     *
+     * @return the length
+     */
+    public short getLength() {
+        return length;
+    }
+
+    /**
+     * Sets the length.
+     *
+     * @param length the length to set excluded the header length
+     * @return this
+     */
+    public PppoeTlvTag setLength(final short length) {
+        this.length = length;
+        return this;
+    }
+
+    /**
+     * The TLV value.
+     *
+     * @return the value
+     */
+    public byte[] getValue() {
+        return this.value;
+    }
+
+    /**
+     * Set the TLV value.
+     *
+     * @param value the value to set
+     * @return this
+     */
+    public PppoeTlvTag setValue(final byte[] value) {
+        this.value = value;
+        return this;
+    }
+
+    public byte[] serialize() {
+        final byte[] data = new byte[4 + this.length];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.putShort(tagType);
+        bb.putShort(length);
+        if (this.value != null) {
+            bb.put(this.value);
+        }
+        return data;
+    }
+
+    public PppoeTlvTag deserialize(final ByteBuffer bb) throws DeserializationException {
+        if (bb.remaining() < 4) {
+            throw new DeserializationException(
+                    "Not enough bytes to deserialize PPPoE TLV tag type and length");
+        }
+        this.tagType = bb.getShort();
+        this.length = bb.getShort();
+
+        if (this.length > 0) {
+            this.value = new byte[this.length];
+
+            // if there is an underrun just toss the TLV
+            if (bb.remaining() < this.length) {
+                throw new DeserializationException(
+                        "Remaining bytes are less then the length of the PPPoE TLV tag");
+            }
+            bb.get(this.value);
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        sb.append("type= ");
+        sb.append(this.tagType);
+        sb.append("length= ");
+        sb.append(this.length);
+        sb.append("value= ");
+        sb.append(Arrays.toString(this.value));
+        sb.append("]");
+        return sb.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof PppoeTlvTag)) {
+            return false;
+        }
+        final PppoeTlvTag other = (PppoeTlvTag) obj;
+        if (this.length != other.length) {
+            return false;
+        }
+        if (this.tagType != other.tagType) {
+            return false;
+        }
+        if (!Arrays.equals(this.value, other.value)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(tagType, length, Arrays.hashCode(value));
+    }
+}
diff --git a/app/src/main/java/org/opencord/bng/packets/package-info.java b/app/src/main/java/org/opencord/bng/packets/package-info.java
new file mode 100644
index 0000000..accf4a2
--- /dev/null
+++ b/app/src/main/java/org/opencord/bng/packets/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2019-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.
+ */
+
+/**
+ * Packet parser for BNG application.
+ */
+package org.opencord.bng.packets;
\ No newline at end of file