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/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;
+    }
+}