[ONOS-3857] BGP send update flow specification message to peer.

Change-Id: Ieb727bfa89a6e7891e69c3070937aa9c98b28050
diff --git a/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java b/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java
index 9f4d47d..2fd74fa 100644
--- a/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java
+++ b/protocols/bgp/api/src/main/java/org/onosproject/bgp/controller/BgpPeer.java
@@ -19,6 +19,8 @@
 import org.onosproject.bgpio.exceptions.BgpParseException;
 import org.onosproject.bgpio.protocol.BgpFactory;
 import org.onosproject.bgpio.protocol.BgpMessage;
+import org.onosproject.bgpio.protocol.flowspec.BgpFlowSpecDetails;
+import org.onosproject.bgpio.protocol.flowspec.BgpFlowSpecPrefix;
 import org.onosproject.bgpio.types.BgpValueType;
 
 /**
@@ -27,6 +29,23 @@
  */
 public interface BgpPeer {
 
+    enum FlowSpecOperation {
+
+        /**
+         * Signifies addition of flow specification rule.
+         */
+        ADD,
+
+        /**
+         *  Signifies updation of flow specification rule.
+         */
+        UPDATE,
+
+        /**
+         * Signifies deletion of flow specification rule.
+         */
+        DELETE
+    }
     /**
      * Sets the associated Netty channel for this bgp peer.
      *
@@ -110,4 +129,14 @@
      * @return sessionInfo bgp session info
      */
     BgpSessionInfo sessionInfo();
+
+    /**
+     * Updates flow specification rule.
+     *
+     * @param operType operation type add or delete or update
+     * @param prefix prefix for the flow rule
+     * @param flowSpec BGP flow specification components
+     */
+    void updateFlowSpec(FlowSpecOperation operType, BgpFlowSpecPrefix prefix,
+                               BgpFlowSpecDetails flowSpec);
 }
diff --git a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java
index f2ae36e..8db8f80 100755
--- a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java
+++ b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpControllerImpl.java
@@ -133,8 +133,16 @@
             boolean isLinkstate = false;
             while (listIterator.hasNext()) {
                 BgpValueType attr = listIterator.next();
-                if ((attr instanceof MpReachNlri) || (attr instanceof MpUnReachNlri)) {
-                    isLinkstate = true;
+                if (attr instanceof MpReachNlri) {
+                    MpReachNlri mpReach = (MpReachNlri) attr;
+                    if (mpReach.bgpFlowSpecInfo() == null) {
+                        isLinkstate = true;
+                    }
+                } else if (attr instanceof MpUnReachNlri) {
+                    MpUnReachNlri mpUnReach = (MpUnReachNlri) attr;
+                    if (mpUnReach.bgpFlowSpecInfo() == null) {
+                        isLinkstate = true;
+                    }
                 }
             }
             if (isLinkstate) {
diff --git a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java
index 8e42208..efd84fb 100644
--- a/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java
+++ b/protocols/bgp/ctl/src/main/java/org/onosproject/bgp/controller/impl/BgpPeerImpl.java
@@ -17,6 +17,13 @@
 package org.onosproject.bgp.controller.impl;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
 import org.jboss.netty.channel.Channel;
 import org.onlab.packet.IpAddress;
 import org.onosproject.bgp.controller.BgpController;
@@ -28,13 +35,23 @@
 import org.onosproject.bgpio.protocol.BgpFactory;
 import org.onosproject.bgpio.protocol.BgpLSNlri;
 import org.onosproject.bgpio.protocol.BgpMessage;
+import org.onosproject.bgpio.protocol.flowspec.BgpFlowSpecDetails;
+import org.onosproject.bgpio.protocol.flowspec.BgpFlowSpecPrefix;
 import org.onosproject.bgpio.protocol.linkstate.BgpLinkLsNlriVer4;
 import org.onosproject.bgpio.protocol.linkstate.BgpNodeLSNlriVer4;
 import org.onosproject.bgpio.protocol.linkstate.BgpPrefixIPv4LSNlriVer4;
 import org.onosproject.bgpio.protocol.linkstate.PathAttrNlriDetails;
+import org.onosproject.bgpio.types.AsPath;
+import org.onosproject.bgpio.types.As4Path;
 import org.onosproject.bgpio.types.BgpValueType;
+import org.onosproject.bgpio.types.LocalPref;
+import org.onosproject.bgpio.types.Med;
 import org.onosproject.bgpio.types.MpReachNlri;
 import org.onosproject.bgpio.types.MpUnReachNlri;
+import org.onosproject.bgpio.types.MultiProtocolExtnCapabilityTlv;
+import org.onosproject.bgpio.types.Origin;
+import org.onosproject.bgpio.types.RouteDistinguisher;
+import org.onosproject.bgpio.util.Constants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -65,6 +82,26 @@
     private BgpLocalRib bgplocalRibVpn;
     private AdjRibIn adjRib;
     private VpnAdjRibIn vpnAdjRib;
+    private BgpFlowSpecRibOut flowSpecRibOut;
+    private BgpFlowSpecRibOut vpnFlowSpecRibOut;
+
+    /**
+     * Returns the flowSpec RIB out.
+     *
+     * @return flow Specification RIB out
+     */
+    public BgpFlowSpecRibOut flowSpecRibOut() {
+        return flowSpecRibOut;
+    }
+
+    /**
+     * Returns the VPN flowSpec RIB out.
+     *
+     * @return VPN flow Specification RIB out
+     */
+    public BgpFlowSpecRibOut vpnFlowSpecRibOut() {
+        return vpnFlowSpecRibOut;
+    }
 
     /**
      * Return the adjacency RIB-IN.
@@ -104,8 +141,129 @@
         this.bgplocalRibVpn =  bgpController.bgpLocalRibVpn();
         this.adjRib = new AdjRibIn();
         this.vpnAdjRib = new VpnAdjRibIn();
+        this.flowSpecRibOut = new BgpFlowSpecRibOut();
+        this.vpnFlowSpecRibOut = new BgpFlowSpecRibOut();
     }
 
+    /**
+     * Check if peer support capability.
+     *
+     * @param type capability type
+     * @param afi address family identifier
+     * @param sAfi subsequent address family identifier
+     * @return true if capability is supported, otherwise false
+     */
+    public final boolean isCapabilitySupported(short type, short afi, byte sAfi) {
+
+        List<BgpValueType> capability = sessionInfo.remoteBgpCapability();
+        ListIterator<BgpValueType> listIterator = capability.listIterator();
+
+        while (listIterator.hasNext()) {
+            BgpValueType tlv = listIterator.next();
+
+            if (tlv.getType() == type) {
+                if (tlv.getType() == MultiProtocolExtnCapabilityTlv.TYPE) {
+                    MultiProtocolExtnCapabilityTlv temp = (MultiProtocolExtnCapabilityTlv) tlv;
+                    if ((temp.getAfi() == afi) && (temp.getSafi() == sAfi)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Send flow specification update message to peer.
+     *
+     * @param operType operation type
+     * @param flowSpec flow specification details
+     */
+    public final void sendFlowSpecUpdateMessageToPeer(FlowSpecOperation operType, BgpFlowSpecDetails flowSpec) {
+
+        List<BgpValueType> attributesList = new LinkedList<>();
+        byte sessionType = sessionInfo.isIbgpSession() ? (byte) 0 : (byte) 1;
+        byte sAfi = Constants.SAFI_FLOWSPEC_VALUE;
+
+        boolean isFsCapabilitySet = isCapabilitySupported(MultiProtocolExtnCapabilityTlv.TYPE,
+                                                        Constants.AFI_FLOWSPEC_VALUE,
+                                                        Constants.SAFI_FLOWSPEC_VALUE);
+
+        boolean isVpnFsCapabilitySet = isCapabilitySupported(MultiProtocolExtnCapabilityTlv.TYPE,
+                                                        Constants.AFI_FLOWSPEC_VALUE,
+                                                        Constants.VPN_SAFI_FLOWSPEC_VALUE);
+        if ((!isFsCapabilitySet) && (!isVpnFsCapabilitySet)) {
+            log.debug("Peer do not support BGP flow spec capability", channel.getRemoteAddress());
+            return;
+        }
+
+        if (isVpnFsCapabilitySet) {
+            sAfi = Constants.VPN_SAFI_FLOWSPEC_VALUE;
+        }
+
+        attributesList.add(new Origin(sessionType));
+
+        if (sessionType != 0) {
+            // EBGP
+            if (!bgpController.getConfig().getLargeASCapability()) {
+                List<Short> aspathSet = new ArrayList<>();
+                List<Short> aspathSeq = new ArrayList<>();
+                aspathSeq.add((short) bgpController.getConfig().getAsNumber());
+
+                AsPath asPath = new AsPath(aspathSet, aspathSeq);
+                attributesList.add(asPath);
+            } else {
+                List<Integer> aspathSet = new ArrayList<>();
+                List<Integer> aspathSeq = new ArrayList<>();
+                aspathSeq.add(bgpController.getConfig().getAsNumber());
+
+                As4Path as4Path = new As4Path(aspathSet, aspathSeq);
+                attributesList.add(as4Path);
+            }
+            attributesList.add(new Med(0));
+        } else {
+            attributesList.add(new AsPath());
+            attributesList.add(new Med(0));
+            attributesList.add(new LocalPref(100));
+        }
+
+        // TODO: Update flow spec action
+
+        if (operType == FlowSpecOperation.ADD) {
+            attributesList.add(new MpReachNlri(flowSpec, Constants.AFI_FLOWSPEC_VALUE, sAfi));
+        } else if (operType == FlowSpecOperation.DELETE) {
+            attributesList.add(new MpUnReachNlri(flowSpec, Constants.AFI_FLOWSPEC_VALUE, sAfi));
+        }
+
+        BgpMessage msg = Controller.getBgpMessageFactory4().updateMessageBuilder()
+                                                           .setBgpPathAttributes(attributesList).build();
+
+        log.debug("Sending Flow spec Update message to {}", channel.getRemoteAddress());
+        channel.write(Collections.singletonList(msg));
+    }
+
+    @Override
+    public void updateFlowSpec(FlowSpecOperation operType, BgpFlowSpecPrefix prefix, BgpFlowSpecDetails flowSpec) {
+        Preconditions.checkNotNull(operType, "flow specification operation type cannot be null");
+        Preconditions.checkNotNull(prefix, "flow specification prefix cannot be null");
+        Preconditions.checkNotNull(flowSpec, "flow specification details cannot be null");
+        Preconditions.checkNotNull(flowSpec.fsActionTlv(), "flow specification action cannot be null");
+
+        if (operType == FlowSpecOperation.ADD) {
+            if (flowSpec.routeDistinguisher() == null) {
+                flowSpecRibOut.add(prefix, flowSpec);
+            } else {
+                vpnFlowSpecRibOut.add(flowSpec.routeDistinguisher(), prefix, flowSpec);
+            }
+        } else if (operType == FlowSpecOperation.DELETE) {
+            if (flowSpec.routeDistinguisher() == null) {
+                flowSpecRibOut.delete(prefix);
+            } else {
+                vpnFlowSpecRibOut.delete(flowSpec.routeDistinguisher(), prefix);
+            }
+        }
+        sendFlowSpecUpdateMessageToPeer(operType, flowSpec);
+    }
 
     @Override
     public void buildAdjRibIn(List<BgpValueType> pathAttr) throws BgpParseException {
@@ -255,12 +413,47 @@
         localRibVpn.localRibUpdate(vpnAdjacencyRib());
     }
 
+    /**
+     * Update peer flow specification RIB on peer disconnect.
+     *
+     */
+    public void updateFlowSpecOnPeerDisconnect() {
+
+        boolean isCapabilitySet = isCapabilitySupported(MultiProtocolExtnCapabilityTlv.TYPE,
+                                                        Constants.AFI_FLOWSPEC_VALUE,
+                                                        Constants.SAFI_FLOWSPEC_VALUE);
+        if (isCapabilitySet) {
+            Set<BgpFlowSpecPrefix> flowSpecKeys = flowSpecRibOut.flowSpecTree().keySet();
+            for (BgpFlowSpecPrefix key : flowSpecKeys) {
+                BgpFlowSpecDetails flowSpecDetails = flowSpecRibOut.flowSpecTree().get(key);
+                sendFlowSpecUpdateMessageToPeer(FlowSpecOperation.DELETE, flowSpecDetails);
+            }
+        }
+
+        boolean isVpnCapabilitySet = isCapabilitySupported(MultiProtocolExtnCapabilityTlv.TYPE,
+                                                        Constants.AFI_FLOWSPEC_VALUE,
+                                                        Constants.VPN_SAFI_FLOWSPEC_VALUE);
+        if (isVpnCapabilitySet) {
+            Set<RouteDistinguisher> flowSpecKeys = vpnFlowSpecRibOut.vpnFlowSpecTree().keySet();
+            for (RouteDistinguisher key : flowSpecKeys) {
+                Map<BgpFlowSpecPrefix, BgpFlowSpecDetails> fsTree = vpnFlowSpecRibOut.vpnFlowSpecTree().get(key);
+
+                Set<BgpFlowSpecPrefix> fsKeys = fsTree.keySet();
+                for (BgpFlowSpecPrefix fsKey : fsKeys) {
+                    BgpFlowSpecDetails flowSpecDetails = vpnFlowSpecRibOut.flowSpecTree().get(fsKey);
+                    sendFlowSpecUpdateMessageToPeer(FlowSpecOperation.DELETE, flowSpecDetails);
+                }
+            }
+        }
+    }
+
     // ************************
     // Channel related
     // ************************
 
     @Override
     public final void disconnectPeer() {
+        this.updateFlowSpecOnPeerDisconnect();
         this.channel.close();
     }