Adding OnePing app.

Change-Id: I9de0d80041421d7a4004b7bf1db239cc8bc7685f
diff --git a/ifwd/app.xml b/ifwd/app.xml
index 9403d06..3f64ce7 100644
--- a/ifwd/app.xml
+++ b/ifwd/app.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<app name="org.onosproject.app.ifwd" origin="ON.Lab" version="1.1.0"
+<app name="org.onosproject.ifwd" origin="ON.Lab" version="1.2.0"
         features="onos-app-ifwd">
     <description>ONOS Reactive forwarding application using intent subsystem (experimental)</description>
 </app>
diff --git a/intent-perf/src/assembly/app.xml b/intent-perf/src/assembly/app.xml
index dcc6ec8..05bdc7e 100644
--- a/intent-perf/src/assembly/app.xml
+++ b/intent-perf/src/assembly/app.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<app name="org.onosproject.intentperf" origin="ON.Lab" version="1.1.0"
+<app name="org.onosproject.intentperf" origin="ON.Lab" version="1.2.0"
      features="onos-app-intent-perf">
     <description>Intent performance application</description>
 </app>
\ No newline at end of file
diff --git a/oneping/pom.xml b/oneping/pom.xml
new file mode 100644
index 0000000..ff03479
--- /dev/null
+++ b/oneping/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015 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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onosproject</groupId>
+        <artifactId>onos-app-samples</artifactId>
+        <version>1.2.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>onos-app-oneping</artifactId>
+    <packaging>bundle</packaging>
+
+    <description>ONOS One-Ping sample app</description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.5.3</version>
+                <configuration>
+                    <descriptor>src/assembly/bin.xml</descriptor>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/oneping/src/assembly/app.xml b/oneping/src/assembly/app.xml
new file mode 100644
index 0000000..632a535
--- /dev/null
+++ b/oneping/src/assembly/app.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015 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.
+  -->
+<app name="org.onosproject.oneping" origin="ON.Lab" version="1.2.0"
+     featuresRepo="mvn:org.onosproject/oneping-app-features/1.2.0-SNAPSHOT/xml/features"
+     features="oneping-app,onos-app-tvue">
+    <description>One-Ping-Only sample application!</description>
+</app>
diff --git a/oneping/src/assembly/bin.xml b/oneping/src/assembly/bin.xml
new file mode 100644
index 0000000..97d1782
--- /dev/null
+++ b/oneping/src/assembly/bin.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2015 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.
+  -->
+<assembly
+        xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+    <formats>
+        <format>zip</format>
+    </formats>
+    <id>onos</id>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <files>
+        <file>
+            <source>src/assembly/app.xml</source>
+            <destName>app.xml</destName>
+        </file>
+        <file>
+            <source>src/assembly/features.xml</source>
+            <destName>m2/org/onosproject/${project.artifactId}-features/${project.version}/${project.artifactId}-features-${project.version}-features.xml</destName>
+        </file>
+        <file>
+            <source>target/${project.artifactId}-${project.version}.jar</source>
+            <destName>m2/org/onosproject/${project.artifactId}/${project.version}/${project.artifactId}-${project.version}.jar</destName>
+        </file>
+    </files>
+</assembly>
diff --git a/oneping/src/assembly/features.xml b/oneping/src/assembly/features.xml
new file mode 100644
index 0000000..c87d6a4
--- /dev/null
+++ b/oneping/src/assembly/features.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  ~ 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.
+  -->
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0"
+          name="onos-oneping-1.2.0">
+    <repository>mvn:org.onosproject.samples/onos-app-oneping-features/1.0-SNAPSHOT/xml/features</repository>
+
+    <feature name="oneping-app" version="1.2.0"
+             description="ONOS OnePing sample app">
+        <feature>onos-api</feature>
+        <bundle>mvn:org.onosproject.samples/oneping-app/1.2.0-SNAPSHOT</bundle>
+    </feature>
+
+</features>
diff --git a/oneping/src/main/java/org/onos/oneping/OnePing.java b/oneping/src/main/java/org/onos/oneping/OnePing.java
new file mode 100644
index 0000000..f9989f5
--- /dev/null
+++ b/oneping/src/main/java/org/onos/oneping/OnePing.java
@@ -0,0 +1,240 @@
+package org.onos.oneping;
+
+import com.google.common.collect.HashMultimap;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleEvent;
+import org.onosproject.net.flow.FlowRuleListener;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria.EthCriterion;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Objects;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_DST;
+import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_SRC;
+
+/**
+ * Sample application that permits only one ICMP ping per minute for a unique
+ * src/dst MAC pair per switch.
+ */
+@Component(immediate = true)
+public class OnePing {
+
+    private static final String MSG_PINGED_ONCE =
+            "Thank you, Vasili. One ping from {} to {} received by {}";
+    private static final String MSG_PINGED_TWICE =
+            "What are you doing, Vasili?! I said one ping only!!! " +
+                    "Ping from {} to {} has already been received by {};" +
+                    " 60 second ban has been issued";
+    private static final String MSG_PING_REENABLED =
+            "Careful next time, Vasili! Re-enabled ping from {} to {} on {}";
+
+
+    private static Logger log = LoggerFactory.getLogger(OnePing.class);
+
+    private static final int PRIORITY = 128;
+    private static final int DROP_PRIORITY = 129;
+    private static final int TIMEOUT_SEC = 60; // seconds
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    private ApplicationId appId;
+    private final PacketProcessor packetProcessor = new PingPacketProcessor();
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+    private final FlowRuleListener flowListener = new InternalFlowListener();
+
+    private final TrafficSelector selector = DefaultTrafficSelector.builder()
+            .matchEthType(Ethernet.TYPE_IPV4).matchIPProtocol(IPv4.PROTOCOL_ICMP)
+            .build();
+
+//    private TrafficTreatment treatment = DefaultTrafficTreatment.builder().punt().build();  // requires ONOS 1.1.0+
+    private TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+            .setOutput(PortNumber.CONTROLLER).build();  // requires ONOS 1.0.1+
+
+    private final HashMultimap<DeviceId, PingRecord> pings = HashMultimap.create();
+    private final Timer timer = new Timer("oneping-sweeper");
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication("org.onos.oneping");
+        packetService.addProcessor(packetProcessor, PRIORITY);
+        deviceService.addListener(deviceListener);
+        flowRuleService.addListener(flowListener);
+        pushInterceptRules();
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        flowRuleService.removeFlowRulesById(appId);
+        packetService.removeProcessor(packetProcessor);
+        flowRuleService.removeListener(flowListener);
+        log.info("Stopped");
+    }
+
+    // Pushes ICMP intercept rules to all connected devices
+    private void pushInterceptRules() {
+        deviceService.getDevices().forEach(this::pushInterceptRule);
+    }
+
+    // Pushes ICMP intercept rule to the specified device.
+    private void pushInterceptRule(Device device) {
+        DefaultFlowRule rule = new DefaultFlowRule(device.id(), selector, treatment,
+                                                   PRIORITY, appId, 0, true);
+        flowRuleService.applyFlowRules(rule);
+    }
+
+    // Processes the specified ICMP ping packet.
+    private void processPing(PacketContext context, Ethernet eth) {
+        DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
+        MacAddress src = eth.getSourceMAC();
+        MacAddress dst = eth.getDestinationMAC();
+        PingRecord ping = new PingRecord(src, dst);
+        boolean pinged = pings.get(deviceId).contains(ping);
+
+        if (pinged) {
+            log.warn(MSG_PINGED_TWICE, src, dst, deviceId);
+            banPings(deviceId, src, dst);
+            context.block();
+        } else {
+            log.info(MSG_PINGED_ONCE, src, dst, deviceId);
+            pings.put(deviceId, ping);
+            timer.schedule(new PingPruner(deviceId, ping), TIMEOUT_SEC * 1000);
+        }
+    }
+
+    // Installs a temporary drop rule for the ICMP pings between given srd/dst.
+    private void banPings(DeviceId deviceId, MacAddress src, MacAddress dst) {
+        TrafficSelector sel = DefaultTrafficSelector.builder()
+                .matchEthSrc(src).matchEthDst(dst).build();
+        TrafficTreatment treat = DefaultTrafficTreatment.builder().build();
+        DefaultFlowRule drop = new DefaultFlowRule(deviceId, sel, treat,
+                                                   DROP_PRIORITY, appId,
+                                                   TIMEOUT_SEC, false);
+        flowRuleService.applyFlowRules(drop);
+    }
+
+
+    // Indicates whether the specified packet corresponds to ICMP ping.
+    private boolean isIcmpPing(Ethernet eth) {
+        if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
+            return ((IPv4) eth.getPayload()).getProtocol() == IPv4.PROTOCOL_ICMP;
+        }
+        return false;
+    }
+
+
+    // Intercepts packets
+    private class PingPacketProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+            Ethernet eth = context.inPacket().parsed();
+            if (isIcmpPing(eth)) {
+                processPing(context, eth);
+            }
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            if (event.type() == DeviceEvent.Type.DEVICE_ADDED) {
+                pushInterceptRule(event.subject());
+            }
+        }
+    }
+
+    private class PingRecord {
+        private final MacAddress src;
+        private final MacAddress dst;
+
+        PingRecord(MacAddress src, MacAddress dst) {
+            this.src = src;
+            this.dst = dst;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(src, dst);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final PingRecord other = (PingRecord) obj;
+            return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst);
+        }
+    }
+
+    // Prunes the given ping record from the specified device.
+    private class PingPruner extends TimerTask {
+        private final DeviceId deviceId;
+        private final PingRecord ping;
+
+        public PingPruner(DeviceId deviceId, PingRecord ping) {
+            this.deviceId = deviceId;
+            this.ping = ping;
+        }
+
+        @Override
+        public void run() {
+            pings.remove(deviceId, ping);
+        }
+    }
+
+    // Listens for our removed flows.
+    private class InternalFlowListener implements FlowRuleListener {
+        @Override
+        public void event(FlowRuleEvent event) {
+            FlowRule flowRule = event.subject();
+            if (event.type() == FlowRuleEvent.Type.RULE_REMOVED &&
+                    flowRule.appId() == appId.id()) {
+                MacAddress src = ((EthCriterion) flowRule.selector().getCriterion(ETH_SRC)).mac();
+                MacAddress dst = ((EthCriterion) flowRule.selector().getCriterion(ETH_DST)).mac();
+                log.warn(MSG_PING_REENABLED, src, dst, flowRule.deviceId());
+            }
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 570d5a1..3515f94 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,13 +32,14 @@
     <description>ONOS sample applications</description>
 
     <modules>
-        <module>tvue</module>
+        <module>intent-perf</module>
+        <module>oneping</module>
         <module>ifwd</module>
         <module>calendar</module>
         <module>demo</module>
         <module>election</module>
-        <module>intent-perf</module>
         <module>database-perf</module>
+        <module>tvue</module>
     </modules>
 
     <properties>
diff --git a/tvue/app.xml b/tvue/app.xml
index 1435903..98b675b 100644
--- a/tvue/app.xml
+++ b/tvue/app.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<app name="org.onosproject.app.tvue" origin="ON.Lab" version="1.1.0"
+<app name="org.onosproject.tvue" origin="ON.Lab" version="1.2.0"
         features="onos-app-tvue">
     <description>Early prototype GUI (deprecated)</description>
 </app>