[VOL-3660] Add support for PPPoED message decoding and flow installation on OltPipeline.

Signed-off-by: Gustavo Silva <gsilva@furukawalatam.com>
Change-Id: I1a6f318d71b8c1f59fdf582edacd44e9c46cd061
(cherry picked from commit 2bcc805d09f59aa5389a921c8b442c93d805a2d0)
diff --git a/utils/misc/src/main/java/org/onlab/packet/EthType.java b/utils/misc/src/main/java/org/onlab/packet/EthType.java
index c69ed145e..3affb9a 100644
--- a/utils/misc/src/main/java/org/onlab/packet/EthType.java
+++ b/utils/misc/src/main/java/org/onlab/packet/EthType.java
@@ -38,6 +38,7 @@
         MPLS_UNICAST(0x8847, "mpls_unicast", org.onlab.packet.MPLS.deserializer()),
         MPLS_MULTICAST(0x8848, "mpls_multicast", org.onlab.packet.MPLS.deserializer()),
         EAPOL(0x888e, "eapol", org.onlab.packet.EAPOL.deserializer()),
+        PPPoED(0x8863, "pppoed", org.onlab.packet.PPPoED.deserializer()),
         SLOW(0x8809, "slow", org.onlab.packet.Slow.deserializer()),
         UNKNOWN(0, "unknown", null);
 
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
index fb249a9..e3af960 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -49,6 +49,7 @@
     public static final short TYPE_VLAN = EthType.EtherType.VLAN.ethType().toShort();
     public static final short TYPE_QINQ = EthType.EtherType.QINQ.ethType().toShort();
     public static final short TYPE_BSN = EthType.EtherType.BDDP.ethType().toShort();
+    public static final short TYPE_PPPOED = EthType.EtherType.PPPoED.ethType().toShort();
 
     public static final short MPLS_UNICAST = EthType.EtherType.MPLS_UNICAST.ethType().toShort();
     public static final short MPLS_MULTICAST = EthType.EtherType.MPLS_MULTICAST.ethType().toShort();
@@ -703,6 +704,8 @@
             sb.append("\nllc packet");
         } else if (pkt instanceof EAPOL) {
             sb.append("\neapol");
+        } else if (pkt instanceof PPPoED) {
+            sb.append("\npppoed packet");
         } else {
             sb.append("\nunknown packet");
         }
diff --git a/utils/misc/src/main/java/org/onlab/packet/PPPoED.java b/utils/misc/src/main/java/org/onlab/packet/PPPoED.java
new file mode 100644
index 0000000..181db7d
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/PPPoED.java
@@ -0,0 +1,220 @@
+/*
+ * 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.onlab.packet;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+public class PPPoED extends BasePacket {
+    protected byte version;
+    protected byte type;
+    protected byte code;
+    protected short sessionId;
+    protected short payloadLength;
+    protected List<PPPoEDTag> tags = new ArrayList<>();
+
+    // PPPoED packet types
+    public static final byte PPPOED_CODE_PADI = (byte) 0x09;
+    public static final byte PPPOED_CODE_PADO = (byte) 0x07;
+    public static final byte PPPOED_CODE_PADR = (byte) 0x19;
+    public static final byte PPPOED_CODE_PADS = (byte) 0x65;
+    public static final byte PPPOED_CODE_PADT = (byte) 0xa7;
+
+    private static final int HEADER_LENGTH = 6;
+    private static final int TAG_HEADER_LENGTH = 4;
+
+    public PPPoED() {
+    }
+
+    public byte getVersion() {
+        return version;
+    }
+
+    public void setVersion(byte version) {
+        this.version = version;
+    }
+
+    public byte getType() {
+        return type;
+    }
+
+    public void setType(byte type) {
+        this.type = type;
+    }
+
+    public byte getCode() {
+        return code;
+    }
+
+    public void setCode(byte code) {
+        this.code = code;
+    }
+
+    public short getSessionId() {
+        return sessionId;
+    }
+
+    public void setSessionId(short sessionId) {
+        this.sessionId = sessionId;
+    }
+
+    public short getPayloadLength() {
+        return payloadLength;
+    }
+
+    public void setPayloadLength(short payloadLength) {
+        this.payloadLength = payloadLength;
+    }
+
+    public List<PPPoEDTag> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<PPPoEDTag> tags) {
+        this.tags = tags;
+    }
+
+    /**
+     * Gets a list of tags from the packet.
+     *
+     * @param tagType the type field of the required tags
+     * @return List of the tags that match the type or an empty list if there is none
+     */
+    public ArrayList<PPPoEDTag> getTagList(short tagType) {
+        ArrayList<PPPoEDTag> tagList = new ArrayList<>();
+        for (int i = 0; i < this.tags.size(); i++) {
+            if (this.tags.get(i).getType() == tagType) {
+                tagList.add(this.tags.get(i));
+            }
+        }
+        return tagList;
+    }
+
+    /**
+     * Gets a tag from the packet.
+     *
+     * @param tagType the type field of the required tag
+     * @return the first tag that matches the type or null if does not exist
+     */
+    public PPPoEDTag getTag(short tagType) {
+        for (int i = 0; i < this.tags.size(); i++) {
+            if (this.tags.get(i).getType() == tagType) {
+                return this.tags.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Sets a tag in the packet.
+     *
+     * @param tagType the type field of the tag to set
+     * @param value    value to be set
+     * @return reference to the tag object
+     */
+    public PPPoEDTag setTag(short tagType, byte[] value) {
+        short tagLength = (short) (value.length);
+        PPPoEDTag newTag = new PPPoEDTag(tagType, tagLength, value);
+        this.tags.add(newTag);
+        this.payloadLength += TAG_HEADER_LENGTH + tagLength;
+        return newTag;
+    }
+
+    /**
+     * Deserializer for PPPoED packets.
+     *
+     * @return deserializer
+     */
+    public static Deserializer<PPPoED> deserializer() {
+        return (data, offset, length) -> {
+            checkInput(data, offset, length, HEADER_LENGTH);
+
+            final ByteBuffer bb = ByteBuffer.wrap(data, offset, length);
+            PPPoED pppoed = new PPPoED();
+            byte versionByte = bb.get();
+            pppoed.setVersion((byte) (versionByte >> 4 & 0xf));
+            pppoed.setType((byte) (versionByte & 0xf));
+            pppoed.setCode(bb.get());
+            pppoed.setSessionId(bb.getShort());
+            pppoed.setPayloadLength(bb.getShort());
+            int remainingLength = pppoed.payloadLength;
+            while (remainingLength > 0 && bb.hasRemaining()) {
+                PPPoEDTag tag = new PPPoEDTag();
+                tag.setType(bb.getShort());
+                tag.setLength(bb.getShort());
+                tag.value = new byte[tag.length];
+                bb.get(tag.value, 0, tag.length);
+                pppoed.tags.add(tag);
+                remainingLength -= tag.length + TAG_HEADER_LENGTH;
+            }
+            return pppoed;
+        };
+    }
+
+    @Override
+    public byte[] serialize() {
+        final byte[] data = new byte[this.payloadLength + HEADER_LENGTH];
+        final ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.put((byte) ((this.version & 0xf) << 4 | this.type & 0xf));
+        bb.put(this.code);
+        bb.putShort(this.sessionId);
+        bb.putShort(this.payloadLength);
+        for (int i = 0; i < this.tags.size(); i++) {
+            PPPoEDTag tag = this.tags.get(i);
+            bb.putShort(tag.getType());
+            bb.putShort(tag.getLength());
+            bb.put(tag.getValue());
+        }
+        return data;
+    }
+
+    @Override
+    public String toString() {
+        return "PPPoED{" +
+                "version=" + version +
+                ", type=" + type +
+                ", code=" + code +
+                ", session_id=" + sessionId +
+                ", payload_length=" + payloadLength +
+                ", tags=" + tags +
+                '}';
+    }
+
+    public enum Type {
+        PADI(PPPOED_CODE_PADI),
+        PADO(PPPOED_CODE_PADO),
+        PADR(PPPOED_CODE_PADR),
+        PADS(PPPOED_CODE_PADS),
+        PADT(PPPOED_CODE_PADT);
+
+        public int value;
+
+        Type(int value) {
+            this.value = value;
+        }
+
+        public static Type getTypeByValue(int value) {
+            return Stream.of(values())
+                    .filter(el -> el.value == value)
+                    .findFirst()
+                    .orElse(null);
+        }
+    }
+}
diff --git a/utils/misc/src/main/java/org/onlab/packet/PPPoEDTag.java b/utils/misc/src/main/java/org/onlab/packet/PPPoEDTag.java
new file mode 100644
index 0000000..15f6f26
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/packet/PPPoEDTag.java
@@ -0,0 +1,89 @@
+/*
+ * 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.onlab.packet;
+
+import java.util.Arrays;
+
+public class PPPoEDTag {
+    protected short type;
+    protected short length;
+    protected byte[] value;
+
+    // PPPoED tag types
+    public static final short PPPOED_TAG_END_OF_LIST = 0x0000;
+    public static final short PPPOED_TAG_SERVICE_NAME = 0x0101;
+    public static final short PPPOED_TAG_AC_NAME = 0x0102;
+    public static final short PPPOED_TAG_HOST_UNIQ = 0x0103;
+    public static final short PPPOED_TAG_AC_COOKIE = 0x0104;
+    public static final short PPPOED_TAG_VENDOR_SPECIFIC = 0x0105;
+    public static final short PPPOED_TAG_RELAY_SESSION_ID = 0x0110;
+    public static final short PPPOED_TAG_SERVICE_NAME_ERROR = 0x0201;
+    public static final short PPPOED_TAG_AC_SYSTEM_ERROR = 0x0202;
+    public static final short PPPOED_TAG_GENERIC_ERROR = 0x0203;
+
+    /**
+     * Default constructor.
+     */
+    public PPPoEDTag() {
+    }
+
+    /**
+     * Constructs a PPPoED tag with type, length and value.
+     *
+     * @param type type
+     * @param length length
+     * @param value value
+     */
+    public PPPoEDTag(final short type, final short length, final byte[] value) {
+        this.type = type;
+        this.length = length;
+        this.value = value;
+    }
+
+    public short getType() {
+        return type;
+    }
+
+    public void setType(short type) {
+        this.type = type;
+    }
+
+    public short getLength() {
+        return length;
+    }
+
+    public void setLength(short length) {
+        this.length = length;
+    }
+
+    public byte[] getValue() {
+        return value;
+    }
+
+    public void setValue(byte[] value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "PPPoEDTag{" +
+                "type=" + type +
+                ", length=" + length +
+                ", value=" + Arrays.toString(value) +
+                '}';
+    }
+}
diff --git a/utils/misc/src/test/java/org/onlab/packet/PPPoEDTest.java b/utils/misc/src/test/java/org/onlab/packet/PPPoEDTest.java
new file mode 100644
index 0000000..c2ca05b
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/packet/PPPoEDTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.onlab.packet;
+
+import com.google.common.io.Resources;
+
+import java.nio.ByteBuffer;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for PPPoED class.
+ */
+public class PPPoEDTest {
+
+    private static final byte VERSION = (byte) 1;
+    private static final byte TYPE = (byte) 1;
+    private static final short NO_SESSION = (short) 0;
+    private static final short SESSION = (short) 0x02e2;
+
+    private static final String AC_NAME = "pfSense.localdomain";
+    private static final long AC_COOKIE = 32098554083868671L;
+    private static final String SERVICE_NAME = "*";
+
+    private static final String PADI = "pppoed/padi.bin";
+    private static final String PADO = "pppoed/pado.bin";
+    private static final String PADR = "pppoed/padr.bin";
+    private static final String PADS = "pppoed/pads.bin";
+    private static final String PADT = "pppoed/padt.bin";
+
+    @Test
+    public void testDeserializeBadInput() throws Exception {
+        PacketTestUtils.testDeserializeBadInput(PPPoED.deserializer());
+    }
+
+    @Test
+    public void testTruncatedPacket() throws Exception {
+        byte[] byteHeader = ByteBuffer.allocate(6)
+                .put((byte) 0x11) // version/type
+                .put((byte) 0x09) // code
+                .putShort((short) 0x0000) // transaction id
+                .putShort((short) 0x0000) // payload length
+                .array();
+
+        PacketTestUtils.testDeserializeTruncated(PPPoED.deserializer(), byteHeader);
+    }
+
+    @Test
+    public void testDeserializePadi() throws Exception {
+        byte[] data = Resources.toByteArray(PPPoEDTest.class.getResource(PADI));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+        PPPoED pppoed = (PPPoED) eth.getPayload();
+        assertPPPoEDHeader(pppoed, PPPoED.PPPOED_CODE_PADI);
+        assertEquals(NO_SESSION, pppoed.getSessionId());
+        assertEquals(0, pppoed.getTags().size());
+    }
+
+    @Test
+    public void testDeserializePado() throws Exception {
+        byte[] data = Resources.toByteArray(PPPoEDTest.class.getResource(PADO));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+
+        PPPoED pppoed = (PPPoED) eth.getPayload();
+        assertPPPoEDHeader(pppoed, PPPoED.PPPOED_CODE_PADO);
+        assertEquals(NO_SESSION, pppoed.getSessionId());
+
+        assertEquals(3, pppoed.getTags().size());
+
+        PPPoEDTag acName = pppoed.getTag(PPPoEDTag.PPPOED_TAG_AC_NAME);
+        assertArrayEquals(AC_NAME.getBytes(), acName.value);
+
+        PPPoEDTag serviceName = pppoed.getTag(PPPoEDTag.PPPOED_TAG_SERVICE_NAME);
+        assertArrayEquals(SERVICE_NAME.getBytes(), serviceName.value);
+
+        PPPoEDTag acCookie = pppoed.getTag(PPPoEDTag.PPPOED_TAG_AC_COOKIE);
+        assertEquals(AC_COOKIE, ByteBuffer.wrap(acCookie.value).getLong());
+    }
+
+    @Test
+    public void testDeserializePadr() throws Exception {
+        byte[] data = Resources.toByteArray(PPPoEDTest.class.getResource(PADR));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+
+        PPPoED pppoed = (PPPoED) eth.getPayload();
+        assertPPPoEDHeader(pppoed, PPPoED.PPPOED_CODE_PADR);
+        assertEquals(NO_SESSION, pppoed.getSessionId());
+
+        assertEquals(1, pppoed.getTags().size());
+
+        PPPoEDTag acCookie = pppoed.getTag(PPPoEDTag.PPPOED_TAG_AC_COOKIE);
+        assertEquals(AC_COOKIE, ByteBuffer.wrap(acCookie.value).getLong());
+    }
+
+    @Test
+    public void testDeserializePads() throws Exception {
+        byte[] data = Resources.toByteArray(PPPoEDTest.class.getResource(PADS));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+
+        PPPoED pppoed = (PPPoED) eth.getPayload();
+        assertPPPoEDHeader(pppoed, PPPoED.PPPOED_CODE_PADS);
+        assertEquals(SESSION, pppoed.getSessionId());
+
+        assertEquals(2, pppoed.getTags().size());
+        PPPoEDTag acName = pppoed.getTag(PPPoEDTag.PPPOED_TAG_AC_NAME);
+        assertArrayEquals(AC_NAME.getBytes(), acName.value);
+
+        PPPoEDTag acCookie = pppoed.getTag(PPPoEDTag.PPPOED_TAG_AC_COOKIE);
+        assertEquals(AC_COOKIE, ByteBuffer.wrap(acCookie.value).getLong());
+    }
+
+    @Test
+    public void testDeserializePadt() throws Exception {
+        byte[] data = Resources.toByteArray(PPPoEDTest.class.getResource(PADT));
+        Ethernet eth = Ethernet.deserializer().deserialize(data, 0, data.length);
+
+        PPPoED pppoed = (PPPoED) eth.getPayload();
+        assertPPPoEDHeader(pppoed, PPPoED.PPPOED_CODE_PADT);
+        assertEquals(SESSION, pppoed.getSessionId());
+
+        assertEquals(1, pppoed.getTags().size());
+        PPPoEDTag acCookie = pppoed.getTag(PPPoEDTag.PPPOED_TAG_AC_COOKIE);
+        assertEquals(AC_COOKIE, ByteBuffer.wrap(acCookie.value).getLong());
+    }
+
+    private void assertPPPoEDHeader(PPPoED pppoed, byte code) {
+        assertNotNull(pppoed);
+        assertEquals(VERSION, pppoed.getVersion());
+        assertEquals(TYPE, pppoed.getType());
+        assertEquals(code, pppoed.getCode());
+    }
+
+}
diff --git a/utils/misc/src/test/resources/org/onlab/packet/pppoed/padi.bin b/utils/misc/src/test/resources/org/onlab/packet/pppoed/padi.bin
new file mode 100644
index 0000000..46daf26
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/pppoed/padi.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/pppoed/pado.bin b/utils/misc/src/test/resources/org/onlab/packet/pppoed/pado.bin
new file mode 100644
index 0000000..c40ec46
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/pppoed/pado.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/pppoed/padr.bin b/utils/misc/src/test/resources/org/onlab/packet/pppoed/padr.bin
new file mode 100644
index 0000000..7b742ce
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/pppoed/padr.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/pppoed/pads.bin b/utils/misc/src/test/resources/org/onlab/packet/pppoed/pads.bin
new file mode 100644
index 0000000..0709d80
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/pppoed/pads.bin
Binary files differ
diff --git a/utils/misc/src/test/resources/org/onlab/packet/pppoed/padt.bin b/utils/misc/src/test/resources/org/onlab/packet/pppoed/padt.bin
new file mode 100644
index 0000000..9ecf018
--- /dev/null
+++ b/utils/misc/src/test/resources/org/onlab/packet/pppoed/padt.bin
Binary files differ