Use LLDP for inter-instance link discovery.

Brought in OVXLLDP.java (renamed to OnosLldp) from OVX to encapsulate
LLDP building and verifying logic. There's also some small changes to
LLDP.java and LLDPTLV.java from OVX.

LinkDiscoveryManager.java has been changed to use the new OnosLldp class.
All links are now discovered using LLDPs, the BDDP code has been disabled.

Note: LinkDiscoveryManager still quite messy and contains unneeded
functionality which will be cleaned up in later patches.

OK to merge now (passed nightly tests).

Change-Id: Ifd8773f7531dc0f462c31aff1854a0dcd384c153
diff --git a/src/main/java/net/onrc/onos/core/packet/OnosLldp.java b/src/main/java/net/onrc/onos/core/packet/OnosLldp.java
new file mode 100644
index 0000000..7a53f0b
--- /dev/null
+++ b/src/main/java/net/onrc/onos/core/packet/OnosLldp.java
@@ -0,0 +1,454 @@
+/*******************************************************************************
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 net.onrc.onos.core.packet;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.apache.commons.lang.ArrayUtils;
+
+import com.google.common.base.Charsets;
+
+/**
+ * LLDP packets ONOS uses for discovery of physical network topology.
+ * Refer to IEEE Std 802.1ABTM-2009 for more information.
+ *
+ */
+public class OnosLldp extends LLDP {
+
+    // ON.Lab OUI and ONOS name for organizationally specific TLVs
+    static final byte[] ONLAB_OUI = {(byte) 0xa4, 0x23, 0x05};
+    public static final String ONOS_NAME = "ONOSVirteX";
+    static final byte[] LLDP_NICIRA = {0x01, 0x23, 0x20, 0x00, 0x00,
+            0x01};
+    static final byte[] LLDP_MULTICAST = {0x01, (byte) 0x80,
+            (byte) 0xc2, 0x00, 0x00, 0x0e};
+    static final byte[] BDDP_MULTICAST = {(byte) 0xff, (byte) 0xff,
+            (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff};
+    public static final short ETHERTYPE_VLAN = (short) 0x8100;
+
+    // TLV constants: type, size and subtype
+    // Organizationally specific TLV also have packet offset and contents of TLV
+    // header
+    private static final byte CHASSIS_TLV_TYPE = 1;
+    private static final byte CHASSIS_TLV_SIZE = 7;
+    private static final byte CHASSIS_TLV_SUBTYPE = 4;
+
+    private static final byte PORT_TLV_TYPE = 2;
+    private static final byte PORT_TLV_SIZE = 3;
+    private static final byte PORT_TLV_SUBTYPE = 2;
+
+    private static final byte TTL_TLV_TYPE = 3;
+    private static final byte TTL_TLV_SIZE = 2;
+
+    private static final byte NAME_TLV_TYPE = 127;
+    // 4 = OUI (3) + subtype (1)
+    private static final byte NAME_TLV_SIZE = (byte) (4 + OnosLldp.ONOS_NAME.length());
+    private static final byte NAME_TLV_SUBTYPE = 1;
+    private static final short NAME_TLV_OFFSET = 32;
+    private static final short NAME_TLV_HEADER = (short) ((NAME_TLV_TYPE << 9) | (NAME_TLV_SIZE & 0xff));
+    // Contents of full name TLV
+    private static final byte[] NAME_TLV = ByteBuffer.allocate(NAME_TLV_SIZE + 2)
+            .putShort(NAME_TLV_HEADER).put(ONLAB_OUI).put(NAME_TLV_SUBTYPE)
+            .put(ONOS_NAME.getBytes(Charsets.UTF_8)).array();
+
+    private static final byte DPID_TLV_TYPE = 127;
+    private static final byte DPID_TLV_SIZE = (byte) (12); // 12 = OUI (3) + subtype
+                                                     // (1) + dpid (8)
+    private static final byte DPID_TLV_SUBTYPE = 2;
+    private static final short DPID_TLV_HEADER = (short) ((DPID_TLV_TYPE << 9) | DPID_TLV_SIZE);
+    // Contents of dpid TLV
+    // Note that this does *not* contain the actual dpid since we cannot match
+    // on it
+    private static final byte[] DPID_TLV = ByteBuffer.allocate(DPID_TLV_SIZE + 2 - 8)
+            .putShort(DPID_TLV_HEADER).put(ONLAB_OUI).put(DPID_TLV_SUBTYPE)
+            .array();
+
+    // Pre-built contents of both organizationally specific TLVs
+    private static final byte[] OUI_TLV = ArrayUtils.addAll(NAME_TLV, DPID_TLV);
+
+    // Default switch, port number and TTL
+    private static final byte[] DEFAULT_DPID = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x00 };
+    private static final short DEFAULT_PORT = 0;
+    private static final short DEFAULT_TTL = 120; // in seconds
+
+    // Minimum and ONOS-generated LLDP packet sizes
+    private static final short MINIMUM_LLDP_SIZE = 61;
+    // Add 12 for 2-byte header of each TLV and a single EndOfLLDPTLV
+    private static final short ONOS_LLDP_SIZE = (short) (CHASSIS_TLV_SIZE
+            + PORT_TLV_SIZE + TTL_TLV_SIZE + NAME_TLV_SIZE + DPID_TLV_SIZE + 12);
+
+    // Direction TLVs are used to indicate if the LLDPs were sent
+    // periodically or in response to a received LLDP
+    private static final byte TLV_DIRECTION_TYPE = 0x73;
+    private static final short TLV_DIRECTION_LENGTH = 1;  // 1 byte
+    private static final byte[] TLV_DIRECTION_VALUE_FORWARD = {0x01};
+    private static final byte[] TLV_DIRECTION_VALUE_REVERSE = {0x02};
+    private static final LLDPTLV FORWARD_TLV
+            = new LLDPTLV().
+            setType(TLV_DIRECTION_TYPE).
+            setLength(TLV_DIRECTION_LENGTH).
+            setValue(TLV_DIRECTION_VALUE_FORWARD);
+
+    private static final LLDPTLV REVERSE_TLV
+            = new LLDPTLV().
+            setType(TLV_DIRECTION_TYPE).
+            setLength(TLV_DIRECTION_LENGTH).
+            setValue(TLV_DIRECTION_VALUE_REVERSE);
+
+    // Field offsets in ONOS-generated LLDP
+    private static final short ETHERTYPE_OFFSET = 12;
+    private static final short PORT_OFFSET = 26;
+    private static final short DPID_OFFSET = 54;
+
+    // Private member fields
+    // Byte arrays for TLV information string
+    private byte[] chassisId = new byte[CHASSIS_TLV_SIZE];
+    private byte[] portId = new byte[PORT_TLV_SIZE];
+    private byte[] ttl = new byte[TTL_TLV_SIZE];
+    private byte[] ouiName = new byte[NAME_TLV_SIZE];
+    private byte[] ouiDpid = new byte[DPID_TLV_SIZE];
+
+    // TLVs
+    private LLDPTLV chassisTLV;
+    private LLDPTLV portTLV;
+    private LLDPTLV ttlTLV;
+    private LLDPTLV ouiNameTLV;
+    private LLDPTLV ouiDpidTLV;
+    private List<LLDPTLV> optionalTLVList;
+
+    /**
+     * Instantiates a new ONOS LDDP message.
+     */
+    public OnosLldp() {
+        // Create TLVs
+        this.chassisTLV = new LLDPTLV();
+        this.portTLV = new LLDPTLV();
+        this.ttlTLV = new LLDPTLV();
+        this.ouiNameTLV = new LLDPTLV();
+        this.ouiDpidTLV = new LLDPTLV();
+        this.optionalTLVList = new LinkedList<LLDPTLV>();
+        this.optionalTLVList.add(this.ouiNameTLV);
+        this.optionalTLVList.add(this.ouiDpidTLV);
+
+        // Add TLVs to LLDP packet
+        this.setChassisId(this.chassisTLV);
+        this.setPortId(this.portTLV);
+        this.setTtl(this.ttlTLV);
+        this.setOptionalTLVList(this.optionalTLVList);
+
+        // Set TLVs to default values
+        this.setChassisTLV(DEFAULT_DPID);
+        this.setPortTLV(DEFAULT_PORT);
+        this.setTTLTLV(DEFAULT_TTL);
+        this.setOUIName(OnosLldp.ONOS_NAME);
+        this.setOUIDpid(DEFAULT_DPID);
+    }
+
+    /**
+     * Sets chassis TLV. Note that we can only put 6 bytes in the chassis ID, so
+     * we use another organizationally specific TLV to put the full dpid (see
+     * setOUIDpid()).
+     *
+     * @param dpid the switch DPID
+     */
+    private void setChassisTLV(final byte[] dpid) {
+        ByteBuffer bb = ByteBuffer.wrap(this.chassisId);
+        bb.put(CHASSIS_TLV_SUBTYPE);
+        for (int i = 2; i < 8; i++) {
+            bb.put(dpid[i]);
+        }
+
+        this.chassisTLV.setLength(CHASSIS_TLV_SIZE);
+        this.chassisTLV.setType(CHASSIS_TLV_TYPE);
+        this.chassisTLV.setValue(this.chassisId);
+    }
+
+    /**
+     * Sets port TLV.
+     *
+     * @param portNumber the port number
+     */
+    private void setPortTLV(final short portNumber) {
+        ByteBuffer bb = ByteBuffer.wrap(this.portId);
+        bb.put(PORT_TLV_SUBTYPE);
+        bb.putShort(portNumber);
+
+        this.portTLV.setLength(PORT_TLV_SIZE);
+        this.portTLV.setType(PORT_TLV_TYPE);
+        this.portTLV.setValue(this.portId);
+    }
+
+    /**
+     * Sets Time To Live TLV.
+     *
+     * @param time the time to live
+     */
+    private void setTTLTLV(final short time) {
+        ByteBuffer bb = ByteBuffer.wrap(this.ttl);
+        bb.putShort(time);
+
+        this.ttlTLV.setLength(TTL_TLV_SIZE);
+        this.ttlTLV.setType(TTL_TLV_TYPE);
+        this.ttlTLV.setValue(this.ttl);
+    }
+
+    /**
+     * Sets organizationally specific TLV for ONOS name (subtype 1).
+     *
+     * @param name the name
+     */
+    private void setOUIName(final String name) {
+        ByteBuffer bb = ByteBuffer.wrap(ouiName);
+        bb.put(OnosLldp.ONLAB_OUI);
+        bb.put(NAME_TLV_SUBTYPE);
+        bb.put(name.getBytes(Charsets.UTF_8));
+
+        this.ouiNameTLV.setLength(NAME_TLV_SIZE);
+        this.ouiNameTLV.setType(NAME_TLV_TYPE);
+        this.ouiNameTLV.setValue(ouiName);
+    }
+
+    /**
+     * Sets organizationally specific TLV for ONOS full dpid (subtype 2).
+     *
+     * @param dpid the switch DPID
+     */
+    private void setOUIDpid(final byte[] dpid) {
+        ByteBuffer bb = ByteBuffer.wrap(ouiDpid);
+        bb.put(OnosLldp.ONLAB_OUI);
+        bb.put(DPID_TLV_SUBTYPE);
+        bb.put(dpid);
+
+        this.ouiDpidTLV.setLength(DPID_TLV_SIZE);
+        this.ouiDpidTLV.setType(DPID_TLV_TYPE);
+        this.ouiDpidTLV.setValue(ouiDpid);
+    }
+
+    /**
+     * Sets switch DPID in LLDP packet.
+     *
+     * @param sw the switch dpid
+     */
+    public void setSwitch(Long dpid) {
+        final byte[] byteDpid = ByteBuffer.allocate(8).putLong(dpid)
+                .array();
+        this.setChassisTLV(byteDpid);
+        this.setOUIDpid(byteDpid);
+    }
+
+
+    /**
+     * Sets the port number in LLDP packet.
+     *
+     * @param port the port number
+     */
+    public void setPort(short portNumber) {
+        this.setPortTLV(portNumber);
+    }
+
+    /**
+     * Sets whether this is a forward or reverse LLDP.
+     *
+     * @param isReverse true if reverse, false if forward
+     */
+    public void setReverse(boolean isReverse) {
+        optionalTLVList.add((isReverse) ? REVERSE_TLV : FORWARD_TLV);
+    }
+
+    /**
+     * Serializes full LLDP packet to byte array.
+     *
+     * @return the serialized packet
+     */
+    public byte[] serialize() {
+        return super.serialize();
+    }
+
+    /**
+     * Checks if LLDP packet has correct size, LLDP multicast address, and
+     * ethertype. Packet assumed to have Ethernet header.
+     *
+     * @param packet full packet starting from the Ethernet header
+     * @return true if packet is LLDP, false otherwise
+     */
+    public static boolean isLLDP(final byte[] packet) {
+        // Does packet exist and does it have the mininum size?
+        if (packet == null || packet.length < MINIMUM_LLDP_SIZE) {
+            return false;
+        }
+
+        // Packet has LLDP multicast destination address?
+        final ByteBuffer bb = ByteBuffer.wrap(packet);
+        final byte[] dst = new byte[6];
+        bb.get(dst);
+
+        if (!(Arrays.equals(dst, OnosLldp.LLDP_NICIRA)
+                || Arrays.equals(dst, OnosLldp.LLDP_MULTICAST) || Arrays.equals(
+                dst, OnosLldp.BDDP_MULTICAST))) {
+
+            return false;
+        }
+
+        // Fetch ethertype, skip VLAN tag if it's there
+        short etherType = bb.getShort(ETHERTYPE_OFFSET);
+        if (etherType == ETHERTYPE_VLAN) {
+            etherType = bb.getShort(ETHERTYPE_OFFSET + 4);
+        }
+
+        // Check ethertype
+        if (etherType == Ethernet.TYPE_LLDP) {
+            return true;
+        }
+        if (etherType == Ethernet.TYPE_BSN) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Checks if packet has size of ONOS-generated LLDP, and correctness of two
+     * organizationally specific TLVs that use ON.Lab's OUI. Assumes packet is
+     * valid LLDP packet
+     *
+     * @param packet full packet starting from the Ethernet header
+     * @return true if this is an ONOS-generated LLDP, otherwise false
+     */
+    public static boolean isOnosLldp(byte[] packet) {
+        if (packet.length < ONOS_LLDP_SIZE) {
+            return false;
+        }
+
+        // Extra offset due to VLAN tag
+        final ByteBuffer bb = ByteBuffer.wrap(packet);
+        int offset = 0;
+        if (bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_LLDP
+                && bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_BSN) {
+            offset = 4;
+        }
+
+        // Compare packet's organizationally specific TLVs to the expected
+        // values
+        for (int i = 0; i < OUI_TLV.length; i++) {
+            if (packet[NAME_TLV_OFFSET + offset + i] != OUI_TLV[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Extracts dpid and port from ONOS-generated LLDP packet.
+     *
+     * @param packet full packet started at the Ethernet header
+     * @return switchport switch and port info from the DPID and Port TLVs
+     */
+    public static SwitchPort extractSwitchPort(final byte[] packet) {
+        final ByteBuffer bb = ByteBuffer.wrap(packet);
+
+        // Extra offset due to VLAN tag
+        int offset = 0;
+        if (bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_LLDP
+                && bb.getShort(ETHERTYPE_OFFSET) != Ethernet.TYPE_BSN) {
+            offset = 4;
+        }
+
+        final short port = bb.getShort(PORT_OFFSET + offset);
+        final long dpid = bb.getLong(DPID_OFFSET + offset);
+
+        return new SwitchPort(dpid, port);
+    }
+
+    /**
+     * Checks if the LLDP is a reverse LLDP (i.e. sent in response to receiving
+     * an LLDP on the link). This information is stored in the Direction TLV.
+     *
+     * @param lldp parsed LLDP packet
+     * @return true if the LLDP is a reverse LLDP, otherwise false
+     */
+    public static boolean isReverse(final LLDP lldp) {
+        for (LLDPTLV lldpTlv : lldp.getOptionalTLVList()) {
+            if ((lldpTlv.getType() == TLV_DIRECTION_TYPE) &&
+                    Arrays.equals(lldpTlv.getValue(), TLV_DIRECTION_VALUE_REVERSE)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (!super.equals(other)) {
+            return false;
+        }
+        //
+        // NOTE: Subclasses are are considered as change of identity, hence
+        // equals() will return false if the class type doesn't match.
+        //
+        if (getClass() != other.getClass()) {
+            return false;
+        }
+
+        OnosLldp otherLldp = (OnosLldp) other;
+
+        if (!this.chassisTLV.equals(otherLldp.chassisTLV)) {
+            return false;
+        }
+
+        if (!this.portTLV.equals(otherLldp.portTLV)) {
+            return false;
+        }
+
+        if (!this.ttlTLV.equals(otherLldp.ttlTLV)) {
+            return false;
+        }
+
+        if (!this.ouiNameTLV.equals(otherLldp.ouiNameTLV)) {
+            return false;
+        }
+
+        if (!this.ouiDpidTLV.equals(otherLldp.ouiDpidTLV)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + chassisTLV.hashCode();
+        result = prime * result + portTLV.hashCode();
+        result = prime * result + ttlTLV.hashCode();
+        result = prime * result + ouiNameTLV.hashCode();
+        result = prime * result + ouiDpidTLV.hashCode();
+
+        return result;
+    }
+}