Send PIM Join/Prune messages based on events from the McastService.

Also change Interface to return a list of addresses rather than a set
to allow applications to rely on the order of configuration

Change-Id: Ie7f62fee507639325ee0a77b8db4088dae34597e
diff --git a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
index ce28908..1d09e35 100644
--- a/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
+++ b/apps/pim/src/main/java/org/onosproject/pim/impl/PIMInterface.java
@@ -22,6 +22,7 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.PIM;
+import org.onlab.packet.pim.PIMAddrUnicast;
 import org.onlab.packet.pim.PIMHello;
 import org.onlab.packet.pim.PIMHelloOption;
 import org.onlab.packet.pim.PIMJoinPrune;
@@ -30,6 +31,7 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.mcast.McastRoute;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.PacketService;
 import org.slf4j.Logger;
@@ -37,9 +39,11 @@
 import java.nio.ByteBuffer;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -55,6 +59,9 @@
 
     private final Logger log = getLogger(getClass());
 
+    private static final int JOIN_PERIOD = 60;
+    private static final double HOLD_TIME_MULTIPLIER = 3.5;
+
     private final PacketService packetService;
 
     private Interface onosInterface;
@@ -82,6 +89,8 @@
     // A map of all our PIM neighbors keyed on our neighbors IP address
     private Map<IpAddress, PIMNeighbor> pimNeighbors = new HashMap<>();
 
+    private Map<McastRoute, RouteData> routes = new ConcurrentHashMap<>();
+
     /**
      * Create a PIMInterface from an ONOS Interface.
      *
@@ -151,8 +160,8 @@
      *
      * @return a set of Ip Addresses on this interface
      */
-    public Set<InterfaceIpAddress> getIpAddresses() {
-        return onosInterface.ipAddresses();
+    public List<InterfaceIpAddress> getIpAddresses() {
+        return onosInterface.ipAddressesList();
     }
 
     /**
@@ -161,12 +170,12 @@
      * @return the choosen IP address or null if none
      */
     public IpAddress getIpAddress() {
-        if (onosInterface.ipAddresses().isEmpty()) {
+        if (onosInterface.ipAddressesList().isEmpty()) {
             return null;
         }
 
         IpAddress ipaddr = null;
-        for (InterfaceIpAddress ifipaddr : onosInterface.ipAddresses()) {
+        for (InterfaceIpAddress ifipaddr : onosInterface.ipAddressesList()) {
             ipaddr = ifipaddr.ipAddress();
             break;
         }
@@ -218,6 +227,10 @@
         return pimNeighbors.values();
     }
 
+    public Collection<McastRoute> getRoutes() {
+        return routes.keySet();
+    }
+
     /**
      * Checks whether any of our neighbors have expired, and cleans up their
      * state if they have.
@@ -402,6 +415,100 @@
 
     }
 
+    public void addRoute(McastRoute route, IpAddress nextHop, MacAddress nextHopMac) {
+        RouteData data = new RouteData(nextHop, nextHopMac);
+        routes.put(route, data);
+
+        sendJoinPrune(route, data, true);
+    }
+
+    public void removeRoute(McastRoute route) {
+        RouteData data = routes.remove(route);
+
+        if (data != null) {
+            sendJoinPrune(route, data, false);
+        }
+    }
+
+    public void sendJoins() {
+        routes.entrySet().forEach(entry -> {
+            if (entry.getValue().timestamp + TimeUnit.SECONDS.toMillis(JOIN_PERIOD) >
+                    System.currentTimeMillis()) {
+                return;
+            }
+
+            sendJoinPrune(entry.getKey(), entry.getValue(), true);
+        });
+    }
+
+    private void sendJoinPrune(McastRoute route, RouteData data, boolean join) {
+        PIMJoinPrune jp = new PIMJoinPrune();
+
+        jp.addJoinPrune(route.source().toIpPrefix(), route.group().toIpPrefix(), join);
+        jp.setHoldTime(join ? (short) Math.floor(JOIN_PERIOD * HOLD_TIME_MULTIPLIER) : 0);
+        jp.setUpstreamAddr(new PIMAddrUnicast(data.ipAddress.toString()));
+
+        PIM pim = new PIM();
+        pim.setPIMType(PIM.TYPE_JOIN_PRUNE_REQUEST);
+        pim.setPayload(jp);
+
+        IPv4 ipv4 = new IPv4();
+        ipv4.setDestinationAddress(PIM.PIM_ADDRESS.getIp4Address().toInt());
+        ipv4.setSourceAddress(getIpAddress().getIp4Address().toInt());
+        ipv4.setProtocol(IPv4.PROTOCOL_PIM);
+        ipv4.setTtl((byte) 1);
+        ipv4.setDiffServ((byte) 0xc0);
+        ipv4.setPayload(pim);
+
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(onosInterface.mac());
+        eth.setDestinationMACAddress(MacAddress.valueOf("01:00:5E:00:00:0d"));
+        eth.setEtherType(Ethernet.TYPE_IPV4);
+        eth.setPayload(ipv4);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(onosInterface.connectPoint().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(onosInterface.connectPoint().deviceId(),
+                treatment, ByteBuffer.wrap(eth.serialize())));
+
+        data.timestamp = System.currentTimeMillis();
+    }
+
+    /*private void sendPrune(McastRoute route, RouteData data) {
+        PIMJoinPrune jp = new PIMJoinPrune();
+
+        jp.addJoinPrune(route.source().toIpPrefix(), route.group().toIpPrefix(), false);
+        jp.setHoldTime((short) 0);
+        jp.setUpstreamAddr(new PIMAddrUnicast(data.ipAddress.toString()));
+
+        PIM pim = new PIM();
+        pim.setPIMType(PIM.TYPE_JOIN_PRUNE_REQUEST);
+        pim.setPayload(jp);
+
+        IPv4 ipv4 = new IPv4();
+        ipv4.setDestinationAddress(PIM.PIM_ADDRESS.getIp4Address().toInt());
+        ipv4.setSourceAddress(getIpAddress().getIp4Address().toInt());
+        ipv4.setProtocol(IPv4.PROTOCOL_PIM);
+        ipv4.setTtl((byte) 1);
+        ipv4.setDiffServ((byte) 0xc0);
+        ipv4.setPayload(pim);
+
+        Ethernet eth = new Ethernet();
+        eth.setSourceMACAddress(onosInterface.mac());
+        eth.setDestinationMACAddress(MacAddress.valueOf("01:00:5E:00:00:0d"));
+        eth.setEtherType(Ethernet.TYPE_IPV4);
+        eth.setPayload(ipv4);
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(onosInterface.connectPoint().port())
+                .build();
+
+        packetService.emit(new DefaultOutboundPacket(onosInterface.connectPoint().deviceId(),
+                treatment, ByteBuffer.wrap(eth.serialize())));
+    }*/
+
     /**
      * Returns a builder for a PIM interface.
      *
@@ -514,4 +621,16 @@
         }
 
     }
+
+    private static class RouteData {
+        public final IpAddress ipAddress;
+        public final MacAddress macAddress;
+        public long timestamp;
+
+        public RouteData(IpAddress ip, MacAddress mac) {
+            this.ipAddress = ip;
+            this.macAddress = mac;
+            timestamp = System.currentTimeMillis();
+        }
+    }
 }