ONOS-3830: Adding support for serialization/de-serialization of QinQ packets, support for TPID other than 0x88A8

Change-Id: I6f56c5afe0fcd439ca2be848e7da8a68b577cc16
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 e317818..09a79f5 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ethernet.java
@@ -46,6 +46,7 @@
     public static final short TYPE_IPV6 = EthType.EtherType.IPV6.ethType().toShort();
     public static final short TYPE_LLDP = EthType.EtherType.LLDP.ethType().toShort();
     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 MPLS_UNICAST = EthType.EtherType.MPLS_UNICAST.ethType().toShort();
@@ -73,7 +74,10 @@
     protected MacAddress destinationMACAddress;
     protected MacAddress sourceMACAddress;
     protected byte priorityCode;
+    protected byte qInQPriorityCode;
     protected short vlanID;
+    protected short qinqVID;
+    protected short qinqTPID;
     protected short etherType;
     protected boolean pad = false;
 
@@ -83,6 +87,8 @@
     public Ethernet() {
         super();
         this.vlanID = Ethernet.VLAN_UNTAGGED;
+        this.qinqVID = Ethernet.VLAN_UNTAGGED;
+        this.qinqTPID = TYPE_QINQ;
     }
 
     /**
@@ -208,6 +214,26 @@
     }
 
     /**
+     * Gets the QinQ priority code.
+     *
+     * @return the qInQPriorityCode
+     */
+    public byte getQinQPriorityCode() {
+        return this.qInQPriorityCode;
+    }
+
+    /**
+     * Sets the QinQ priority code.
+     *
+     * @param priority the priorityCode to set
+     * @return the Ethernet frame
+     */
+    public Ethernet setQinQPriorityCode(final byte priority) {
+        this.qInQPriorityCode = priority;
+        return this;
+    }
+
+    /**
      * Gets the VLAN ID.
      *
      * @return the vlanID
@@ -228,6 +254,47 @@
     }
 
     /**
+     * Gets the QinQ VLAN ID.
+     *
+     * @return the QinQ vlanID
+     */
+    public short getQinQVID() {
+        return this.qinqVID;
+    }
+
+    /**
+     * Sets the QinQ VLAN ID.
+     *
+     * @param vlan the vlanID to set
+     * @return the Ethernet frame
+     */
+    public Ethernet setQinQVID(final short vlan) {
+        this.qinqVID = vlan;
+        return this;
+    }
+    /**
+     * Gets the QinQ TPID.
+     *
+     * @return the QinQ TPID
+     */
+    public short getQinQTPID() {
+        return this.qinqTPID;
+    }
+
+    /**
+     * Sets the QinQ TPID.
+     *
+     * @param tpId the TPID to set
+     * @return the Ethernet frame
+     */
+    public Ethernet setQinQTPID(final short tpId) {
+        if (tpId != TYPE_VLAN && tpId != TYPE_QINQ) {
+           return null;
+        }
+        this.qinqTPID = tpId;
+        return this;
+    }
+    /**
      * Gets the Ethernet type.
      *
      * @return the etherType
@@ -291,6 +358,7 @@
             payloadData = this.payload.serialize();
         }
         int length = 14 + (this.vlanID == Ethernet.VLAN_UNTAGGED ? 0 : 4)
+                + (this.qinqVID == Ethernet.VLAN_UNTAGGED ? 0 : 4)
                 + (payloadData == null ? 0 : payloadData.length);
         if (this.pad && length < 60) {
             length = 60;
@@ -299,6 +367,10 @@
         final ByteBuffer bb = ByteBuffer.wrap(data);
         bb.put(this.destinationMACAddress.toBytes());
         bb.put(this.sourceMACAddress.toBytes());
+        if (this.qinqVID != Ethernet.VLAN_UNTAGGED) {
+            bb.putShort(this.qinqTPID);
+            bb.putShort((short) (this.qInQPriorityCode << 13 | this.qinqVID & 0x0fff));
+        }
         if (this.vlanID != Ethernet.VLAN_UNTAGGED) {
             bb.putShort(TYPE_VLAN);
             bb.putShort((short) (this.priorityCode << 13 | this.vlanID & 0x0fff));
@@ -335,11 +407,36 @@
         this.sourceMACAddress = MacAddress.valueOf(srcAddr);
 
         short ethType = bb.getShort();
+        if (ethType == TYPE_QINQ) {
+            final short tci = bb.getShort();
+            this.qInQPriorityCode = (byte) (tci >> 13 & 0x07);
+            this.qinqVID = (short) (tci & 0x0fff);
+            this.qinqTPID = TYPE_QINQ;
+            ethType = bb.getShort();
+        }
+
         if (ethType == TYPE_VLAN) {
             final short tci = bb.getShort();
             this.priorityCode = (byte) (tci >> 13 & 0x07);
             this.vlanID = (short) (tci & 0x0fff);
             ethType = bb.getShort();
+
+            // there might be one more tag with 1q TPID
+            if (ethType == TYPE_VLAN) {
+                // packet is double tagged with 1q TPIDs
+                // We handle only double tagged packets here and assume that in this case
+                // TYPE_QINQ above was not hit
+                // We put the values retrieved above with TYPE_VLAN in
+                // qInQ fields
+                this.qInQPriorityCode = this.priorityCode;
+                this.qinqVID = this.vlanID;
+                this.qinqTPID = TYPE_VLAN;
+
+                final short innerTci = bb.getShort();
+                this.priorityCode = (byte) (innerTci >> 13 & 0x07);
+                this.vlanID = (short) (innerTci & 0x0fff);
+                ethType = bb.getShort();
+            }
         } else {
             this.vlanID = Ethernet.VLAN_UNTAGGED;
         }
@@ -427,6 +524,8 @@
         int result = super.hashCode();
         result = prime * result + this.destinationMACAddress.hashCode();
         result = prime * result + this.etherType;
+        result = prime * result + this.qinqVID;
+        result = prime * result + this.qInQPriorityCode;
         result = prime * result + this.vlanID;
         result = prime * result + this.priorityCode;
         result = prime * result + (this.pad ? 1231 : 1237);
@@ -454,6 +553,12 @@
         if (!this.destinationMACAddress.equals(other.destinationMACAddress)) {
             return false;
         }
+        if (this.qInQPriorityCode != other.qInQPriorityCode) {
+            return false;
+        }
+        if (this.qinqVID != other.qinqVID) {
+            return false;
+        }
         if (this.priorityCode != other.priorityCode) {
             return false;
         }
@@ -504,6 +609,13 @@
                                     Integer.toHexString(this.getEtherType() & 0xffff)));
         }
 
+        if (this.getQinQVID() != Ethernet.VLAN_UNTAGGED) {
+            sb.append("\ndl_qinqVlan: ");
+            sb.append(this.getQinQVID());
+            sb.append("\ndl_qinqVlan_pcp: ");
+            sb.append(this.getQinQPriorityCode());
+        }
+
         sb.append("\ndl_vlan: ");
         if (this.getVlanID() == Ethernet.VLAN_UNTAGGED) {
             sb.append("untagged");
@@ -695,12 +807,37 @@
             eth.setSourceMACAddress(addressBuffer);
 
             short ethType = bb.getShort();
+            if (ethType == TYPE_QINQ) {
+                // in this case we excpect 2 VLAN headers
+                checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+                final short tci = bb.getShort();
+                eth.setQinQPriorityCode((byte) (tci >> 13 & 0x07));
+                eth.setQinQVID((short) (tci & 0x0fff));
+                eth.setQinQTPID(TYPE_QINQ);
+                ethType = bb.getShort();
+            }
             if (ethType == TYPE_VLAN) {
                 checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
                 final short tci = bb.getShort();
                 eth.setPriorityCode((byte) (tci >> 13 & 0x07));
                 eth.setVlanID((short) (tci & 0x0fff));
                 ethType = bb.getShort();
+
+                if (ethType == TYPE_VLAN) {
+                    // We handle only double tagged packets here and assume that in this case
+                    // TYPE_QINQ above was not hit
+                    // We put the values retrieved above with TYPE_VLAN in
+                    // qInQ fields
+                    checkHeaderLength(length, ETHERNET_HEADER_LENGTH + VLAN_HEADER_LENGTH);
+                    eth.setQinQPriorityCode(eth.getPriorityCode());
+                    eth.setQinQVID(eth.getVlanID());
+                    eth.setQinQTPID(TYPE_VLAN);
+
+                    final short innerTci = bb.getShort();
+                    eth.setPriorityCode((byte) (innerTci >> 13 & 0x07));
+                    eth.setVlanID((short) (innerTci & 0x0fff));
+                    ethType = bb.getShort();
+                }
             } else {
                 eth.setVlanID(Ethernet.VLAN_UNTAGGED);
             }
diff --git a/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java b/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
index 7edcc33..508e569 100644
--- a/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/EthernetTest.java
@@ -21,6 +21,8 @@
 
 import java.nio.ByteBuffer;
 
+import java.util.Arrays;
+
 import static org.junit.Assert.assertEquals;
 
 /**
@@ -32,11 +34,22 @@
     private MacAddress srcMac;
     private short ethertype = 6;
     private short vlan = 5;
+    private short qinqVlan = 55;
 
     private Deserializer<Ethernet> deserializer;
 
     private byte[] byteHeader;
     private byte[] vlanByteHeader;
+    private byte[] qinq8100ByteHeader;
+    private byte[] qinq88a8ByteHeader;
+
+    private static byte[] qinqHeaderExpected = {
+                (byte) 0x88, (byte) 0x88, (byte) 0x88, (byte) 0x88,
+                (byte) 0x88, (byte) 0x88, (byte) 0xaa, (byte) 0xaa,
+                (byte) 0xaa, (byte) 0xaa, (byte) 0xaa, (byte) 0xaa,
+                (byte) 0x88, (byte) 0xa8, (byte) 0x00, (byte) 0x37,
+                (byte) 0x81, (byte) 0x00, (byte) 0x00, (byte) 0x05,
+                (byte) 0x00, (byte) 0x06 };
 
     @Before
     public void setUp() {
@@ -68,6 +81,33 @@
         bb.putShort(ethertype);
 
         vlanByteHeader = bb.array();
+
+        // Create Ethernet byte array with a QinQ header with TPID 0x8100
+        bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH + Ethernet.VLAN_HEADER_LENGTH
+                                 + Ethernet.VLAN_HEADER_LENGTH);
+        bb.put(dstMacBytes);
+        bb.put(srcMacBytes);
+        bb.putShort(Ethernet.TYPE_VLAN);
+        bb.putShort(vlan);
+        bb.putShort(Ethernet.TYPE_VLAN);
+        bb.putShort(vlan);
+        bb.putShort(ethertype);
+
+        qinq8100ByteHeader = bb.array();
+
+        // Create Ethernet byte array with a QinQ header with TPID 0x88a8
+        bb = ByteBuffer.allocate(Ethernet.ETHERNET_HEADER_LENGTH + Ethernet.VLAN_HEADER_LENGTH
+                                 + Ethernet.VLAN_HEADER_LENGTH);
+        bb.put(dstMacBytes);
+        bb.put(srcMacBytes);
+        bb.putShort(Ethernet.TYPE_QINQ);
+        bb.putShort(qinqVlan);
+        bb.putShort(Ethernet.TYPE_VLAN);
+        bb.putShort(vlan);
+        bb.putShort(ethertype);
+
+        qinq88a8ByteHeader = bb.array();
+
     }
 
     @Test
@@ -100,4 +140,37 @@
         assertEquals(ethertype, eth.getEtherType());
     }
 
+    @Test
+    public void testDeserializeWithQinQ() throws Exception {
+        Ethernet eth = deserializer.deserialize(qinq8100ByteHeader, 0, qinq8100ByteHeader.length);
+
+        assertEquals(dstMac, eth.getDestinationMAC());
+        assertEquals(srcMac, eth.getSourceMAC());
+        assertEquals(vlan, eth.getVlanID());
+        assertEquals(vlan, eth.getQinQVID());
+        assertEquals(ethertype, eth.getEtherType());
+
+        eth = deserializer.deserialize(qinq88a8ByteHeader, 0, qinq88a8ByteHeader.length);
+
+        assertEquals(dstMac, eth.getDestinationMAC());
+        assertEquals(srcMac, eth.getSourceMAC());
+        assertEquals(vlan, eth.getVlanID());
+        assertEquals(qinqVlan, eth.getQinQVID());
+        assertEquals(ethertype, eth.getEtherType());
+    }
+
+    @Test
+    public void testSerializeWithQinQ() throws Exception {
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(dstMac);
+        eth.setSourceMACAddress(srcMac);
+        eth.setVlanID(vlan);
+        eth.setQinQVID(qinqVlan);
+        eth.setEtherType(ethertype);
+
+        byte[] encoded = eth.serialize();
+
+        assertEquals(Arrays.toString(encoded), Arrays.toString(qinqHeaderExpected));
+    }
+
 }