Oplink EDFA driver

Change-Id: I7fafa66bf7a5290f2d7ea2e07046fa8dabdb20de
diff --git a/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkHandshakerUtil.java b/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkHandshakerUtil.java
new file mode 100644
index 0000000..298f65f
--- /dev/null
+++ b/drivers/optical/src/main/java/org/onosproject/driver/optical/handshaker/OplinkHandshakerUtil.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2016-present 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 org.onosproject.driver.optical.handshaker;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.onosproject.drivers.optical.OpticalAdjacencyLinkService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Annotations;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DefaultPortDescription;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.link.DefaultLinkDescription;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.net.optical.OpticalAnnotations;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest;
+import org.projectfloodlight.openflow.protocol.OFExpExtAdId;
+import org.projectfloodlight.openflow.protocol.OFExpPortAdidOtn;
+import org.projectfloodlight.openflow.protocol.OFExpPortAdjacency;
+import org.projectfloodlight.openflow.protocol.OFExpPortAdjacencyId;
+import org.projectfloodlight.openflow.protocol.OFExpPortAdjacencyRequest;
+import org.projectfloodlight.openflow.protocol.OFOplinkPortPower;
+import org.projectfloodlight.openflow.protocol.OFOplinkPortPowerRequest;
+
+/**
+ * Oplink handshaker utility.
+ */
+public class OplinkHandshakerUtil {
+
+    // Parent driver instance
+    private OpenFlowSwitchDriver driver;
+    // Total count of opspec in OFExpPortAdidOtn
+    private static final int OPSPEC_BYTES = 32;
+    // Bit count of id in opspec
+    private static final int OPSPEC_ID_BITS = 4;
+    // Start byte position of mac info
+    private static final int OPSPEC_MAC_POS = 18;
+    // Bit offset for mac
+    private static final int OPSPEC_MAC_BIT_OFF = 16;
+    // Start byte position of port info
+    private static final int OPSPEC_PORT_POS = 24;
+    // Right bit offset for mac
+    private static final int OPSPEC_PORT_BIT_OFF = 32;
+
+    /**
+     * Create a new OplinkHandshakerUtil.
+     * @param driver parent driver instance
+     */
+    public OplinkHandshakerUtil(OpenFlowSwitchDriver driver) {
+        this.driver = driver;
+    }
+
+    /**
+     * Creates an oplink port power request OF message.
+     *
+     * @return OF message of oplink port power request
+     */
+    public OFOplinkPortPowerRequest buildPortPowerRequest() {
+        OFOplinkPortPowerRequest request = driver.factory().buildOplinkPortPowerRequest()
+                .setXid(driver.getNextTransactionId())
+                .build();
+        return request;
+    }
+
+    /**
+     * Creates port adjacency request OF message.
+     *
+     * @return OF message of oplink port adjacency request
+     */
+    public OFExpPortAdjacencyRequest buildPortAdjacencyRequest() {
+        OFExpPortAdjacencyRequest request = driver.factory().buildExpPortAdjacencyRequest()
+                .setXid(driver.getNextTransactionId())
+                .build();
+        return request;
+    }
+
+    /**
+     * Creates an oplink port description request OF message.
+     *
+     * @return OF message of oplink port description request
+     */
+    public OFCircuitPortsRequest buildCircuitPortsRequest() {
+        OFCircuitPortsRequest request = driver.factory().buildCircuitPortsRequest()
+                .setXid(driver.getNextTransactionId())
+                .build();
+        return request;
+    }
+
+    /**
+     * Creates port descriptions with current power.
+     *
+     * @param portPowers current power
+     * @return port descriptions
+     */
+    public List<PortDescription> buildPortPowerDescriptions(List<OFOplinkPortPower> portPowers) {
+        DeviceService deviceService = driver.handler().get(DeviceService.class);
+        List<Port> ports = deviceService.getPorts(driver.data().deviceId());
+        HashMap<Long, OFOplinkPortPower> powerMap = new HashMap<>(portPowers.size());
+        // Get each port power value
+        portPowers.forEach(power -> powerMap.put((long) power.getPort(), power));
+        final List<PortDescription> portDescs = new ArrayList<>();
+        for (Port port : ports) {
+            DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+            builder.putAll(port.annotations());
+            OFOplinkPortPower power = powerMap.get(port.number().toLong());
+            if (power != null) {
+                // power value is actually signed-short value, down casting to recover sign bit.
+                builder.set(OpticalAnnotations.CURRENT_POWER, Short.toString((short) power.getPowerValue()));
+            }
+            portDescs.add(new DefaultPortDescription(port.number(), port.isEnabled(),
+                    port.type(), port.portSpeed(), builder.build()));
+        }
+        return portDescs;
+    }
+
+    /**
+     * Creates port descriptions with adjacency.
+     *
+     * @param portAds adjacency information
+     * @return port descriptions
+     */
+    public List<PortDescription> buildPortAdjacencyDescriptions(List<OFExpPortAdjacency> portAds) {
+        DeviceService deviceService = driver.handler().get(DeviceService.class);
+        List<Port> ports = deviceService.getPorts(driver.data().deviceId());
+        // Map port's number with port's adjacency
+        HashMap<Long, OFExpPortAdjacency> adMap = new HashMap<>(portAds.size());
+        portAds.forEach(ad -> adMap.put((long) ad.getPortNo().getPortNumber(), ad));
+        List<PortDescription> portDescs = new ArrayList<>();
+        for (Port port : ports) {
+            DefaultAnnotations.Builder builder = DefaultAnnotations.builder();
+            Annotations oldAnnotations = port.annotations();
+            builder.putAll(oldAnnotations);
+            OFExpPortAdjacency ad = adMap.get(port.number().toLong());
+            OplinkPortAdjacency neighbor = getNeighbor(ad);
+            if (neighbor == null) {
+                // no neighbors found
+                builder.remove(OpticalAnnotations.NEIGHBOR_ID);
+                builder.remove(OpticalAnnotations.NEIGHBOR_PORT);
+                removeLink(port.number());
+            } else {
+                // neighbor discovered, add to port descriptions
+                String newId = neighbor.getDeviceId().toString();
+                String newPort = neighbor.getPort().toString();
+                // Check if annotation already exists
+                if (!newId.equals(oldAnnotations.value(OpticalAnnotations.NEIGHBOR_ID)) ||
+                    !newPort.equals(oldAnnotations.value(OpticalAnnotations.NEIGHBOR_PORT))) {
+                    builder.set(OpticalAnnotations.NEIGHBOR_ID, newId);
+                    builder.set(OpticalAnnotations.NEIGHBOR_PORT, newPort);
+                }
+                addLink(port.number(), neighbor);
+            }
+            portDescs.add(new DefaultPortDescription(port.number(), port.isEnabled(),
+                    port.type(), port.portSpeed(), builder.build()));
+        }
+        return portDescs;
+    }
+
+    private OplinkPortAdjacency getNeighbor(OFExpPortAdjacency ad) {
+        // Check input parameter
+        if (ad == null) {
+            return null;
+        }
+        // Get adjacency properties
+        for (OFExpPortAdjacencyId adid : ad.getProperties()) {
+            List<OFExpExtAdId> otns = adid.getAdId();
+            if (otns != null && otns.size() > 0) {
+                OFExpPortAdidOtn otn = (OFExpPortAdidOtn) otns.get(0);
+                // ITU-T G.7714 ETH MAC Format (in second 16 bytes of the following)
+                // |---------------------------------------------------------------------------|
+                // | Other format (16 bytes)                                                   |
+                // |---------------------------------------------------------------------------|
+                // | Header (2 bytes) | ID (4 BITS) | MAC (6 bytes) | Port (4 bytes) | Unused  |
+                // |---------------------------------------------------------------------------|
+                ChannelBuffer buffer = ChannelBuffers.buffer(OPSPEC_BYTES);
+                otn.getOpspec().write32Bytes(buffer);
+                long mac = buffer.getLong(OPSPEC_MAC_POS) << OPSPEC_ID_BITS >>> OPSPEC_MAC_BIT_OFF;
+                int port = (int) (buffer.getLong(OPSPEC_PORT_POS) << OPSPEC_ID_BITS >>> OPSPEC_PORT_BIT_OFF);
+                // Oplink does not use the 4 most significant bytes of Dpid so Dpid can be
+                // constructed from MAC address
+                return new OplinkPortAdjacency(DeviceId.deviceId(Dpid.uri(new Dpid(mac))),
+                        PortNumber.portNumber(port));
+            }
+        }
+        // Returns null if no properties found
+        return null;
+    }
+
+    // Add incoming link with port
+    private void addLink(PortNumber portNumber, OplinkPortAdjacency neighbor) {
+        ConnectPoint dst = new ConnectPoint(driver.handler().data().deviceId(), portNumber);
+        ConnectPoint src = new ConnectPoint(neighbor.getDeviceId(), neighbor.getPort());
+        OpticalAdjacencyLinkService adService = driver.handler().get(OpticalAdjacencyLinkService.class);
+        adService.linkDetected(new DefaultLinkDescription(src, dst, Link.Type.OPTICAL));
+    }
+
+    // Remove incoming link with port if there are any.
+    private void removeLink(PortNumber portNumber) {
+        ConnectPoint dst = new ConnectPoint(driver.handler().data().deviceId(), portNumber);
+        // Check so only incoming links are removed
+        Set<Link> links = driver.handler().get(LinkService.class).getIngressLinks(dst);
+        if (links.isEmpty()) {
+            return;
+        }
+        // If link exists, remove it
+        OpticalAdjacencyLinkService adService = driver.handler().get(OpticalAdjacencyLinkService.class);
+        adService.linksVanished(dst);
+    }
+
+    private class OplinkPortAdjacency {
+        private DeviceId deviceId;
+        private PortNumber portNumber;
+
+        public OplinkPortAdjacency(DeviceId deviceId, PortNumber portNumber) {
+            this.deviceId = deviceId;
+            this.portNumber = portNumber;
+        }
+
+        public DeviceId getDeviceId() {
+            return deviceId;
+        }
+
+        public PortNumber getPort() {
+            return portNumber;
+        }
+    }
+}