Draft implementation of Tor App

Change-Id: I783c324e6d2308c0afd4ab5ede12d32d3ae138aa
diff --git a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
index 18f1e8c..e106823 100644
--- a/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
+++ b/apps/pi-demo/common/src/main/java/org/onosproject/pi/demo/app/common/AbstractUpgradableFabricApp.java
@@ -46,7 +46,6 @@
 import org.onosproject.net.host.HostService;
 import org.onosproject.net.pi.model.PiPipeconf;
 import org.onosproject.net.pi.model.PiPipeconfId;
-import org.onosproject.net.pi.model.PiPipelineInterpreter;
 import org.onosproject.net.pi.runtime.PiPipeconfService;
 import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.topology.Topology;
@@ -71,7 +70,6 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.String.format;
 import static java.util.stream.Collectors.toSet;
 import static java.util.stream.Stream.concat;
 import static org.onlab.util.Tools.groupedThreads;
@@ -151,7 +149,7 @@
     /**
      * Creates a new PI fabric app.
      *
-     * @param appName     app name
+     * @param appName      app name
      * @param appPipeconfs collection of compatible pipeconfs
      */
     protected AbstractUpgradableFabricApp(String appName, Collection<PiPipeconf> appPipeconfs) {
@@ -228,7 +226,7 @@
         one, it generates the necessary flow rules and starts the deploy process on each device.
          */
         scheduledExecutorService.scheduleAtFixedRate(this::checkTopologyAndGenerateFlowRules,
-                0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
+                                                     0, CHECK_TOPOLOGY_INTERVAL_SECONDS, TimeUnit.SECONDS);
     }
 
     private void setAppFreezed(boolean appFreezed) {
@@ -260,7 +258,7 @@
      * @return a list of flow rules
      * @throws FlowRuleGeneratorException if flow rules cannot be generated
      */
-    public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
+    public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Set<Host> dstHosts,
                                                      Collection<DeviceId> spines, Topology topology)
             throws FlowRuleGeneratorException;
 
@@ -273,7 +271,7 @@
      * @return a list of flow rules
      * @throws FlowRuleGeneratorException if flow rules cannot be generated
      */
-    public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topology)
+    public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Set<Host> dstHosts, Topology topology)
             throws FlowRuleGeneratorException;
 
     private void deployAllDevices() {
@@ -453,27 +451,14 @@
     /**
      * Returns a new, pre-configured flow rule builder.
      *
-     * @param did       a device id
-     * @param tableName a table name
+     * @param did     a device id
+     * @param tableId a PI table ID
      * @return a new flow rule builder
      */
-    protected FlowRule.Builder flowRuleBuilder(DeviceId did, String tableName) throws FlowRuleGeneratorException {
-
-        final Device device = deviceService.getDevice(did);
-        if (!device.is(PiPipelineInterpreter.class)) {
-            throw new FlowRuleGeneratorException(format("Device %s has no PiPipelineInterpreter", did));
-        }
-        final PiPipelineInterpreter interpreter = device.as(PiPipelineInterpreter.class);
-        final int flowRuleTableId;
-        if (interpreter.mapPiTableId(PiTableId.of(tableName)).isPresent()) {
-            flowRuleTableId = interpreter.mapPiTableId(PiTableId.of(tableName)).get();
-        } else {
-            throw new FlowRuleGeneratorException(format("Unknown table %s in interpreter", tableName));
-        }
-
+    protected FlowRule.Builder flowRuleBuilder(DeviceId did, PiTableId tableId) throws FlowRuleGeneratorException {
         return DefaultFlowRule.builder()
                 .forDevice(did)
-                .forTable(flowRuleTableId)
+                .forTable(tableId)
                 .fromApp(appId)
                 .withPriority(FLOW_PRIORITY)
                 .makePermanent();
diff --git a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
index fccbab5..26ff775 100644
--- a/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
+++ b/apps/pi-demo/ecmp/src/main/java/org/onosproject/pi/demo/app/ecmp/EcmpFabricApp.java
@@ -39,6 +39,7 @@
 import org.onosproject.net.pi.runtime.PiActionParamId;
 import org.onosproject.net.pi.runtime.PiHeaderFieldId;
 import org.onosproject.net.pi.runtime.PiTableAction;
+import org.onosproject.net.pi.runtime.PiTableId;
 import org.onosproject.net.topology.DefaultTopologyVertex;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyGraph;
@@ -64,6 +65,8 @@
 public class EcmpFabricApp extends AbstractUpgradableFabricApp {
 
     private static final String APP_NAME = "org.onosproject.pi-ecmp-fabric";
+    private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+    private static final PiTableId ECMP_GROUP_TABLE_ID = PiTableId.of(ECMP_GROUP_TABLE);
 
     private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
 
@@ -78,7 +81,7 @@
     }
 
     @Override
-    public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
+    public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Set<Host> dstHosts,
                                             Collection<DeviceId> availableSpines, Topology topo)
             throws FlowRuleGeneratorException {
 
@@ -120,7 +123,7 @@
 
         // From srHost to dstHosts.
         for (Host dstHost : dstHosts) {
-            FlowRule rule = flowRuleBuilder(leaf, EcmpInterpreter.TABLE0)
+            FlowRule rule = flowRuleBuilder(leaf, TABLE0_ID)
                     .withSelector(
                             DefaultTrafficSelector.builder()
                                     .matchInPort(hostPort)
@@ -135,7 +138,7 @@
 
         // From fabric ports to this leaf host.
         for (PortNumber port : fabricPorts) {
-            FlowRule rule = flowRuleBuilder(leaf, EcmpInterpreter.TABLE0)
+            FlowRule rule = flowRuleBuilder(leaf, TABLE0_ID)
                     .withSelector(
                             DefaultTrafficSelector.builder()
                                     .matchInPort(port)
@@ -154,7 +157,7 @@
     }
 
     @Override
-    public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
+    public List<FlowRule> generateSpineRules(DeviceId deviceId, Set<Host> dstHosts, Topology topo)
             throws FlowRuleGeneratorException {
 
         List<FlowRule> rules = Lists.newArrayList();
@@ -183,7 +186,7 @@
                 treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
             }
 
-            FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.TABLE0)
+            FlowRule rule = flowRuleBuilder(deviceId, TABLE0_ID)
                     .withSelector(
                             DefaultTrafficSelector.builder()
                                     .matchEthType(IPV4.ethType().toShort())
@@ -212,7 +215,7 @@
         Iterator<PortNumber> portIterator = fabricPorts.iterator();
         List<FlowRule> rules = Lists.newArrayList();
         for (short i = 0; i < HASHED_LINKS; i++) {
-            FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.ECMP_GROUP_TABLE)
+            FlowRule rule = flowRuleBuilder(deviceId, ECMP_GROUP_TABLE_ID)
                     .withSelector(
                             buildEcmpTrafficSelector(groupId, i))
                     .withTreatment(
@@ -248,7 +251,7 @@
                 .build();
     }
 
-    public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
+    private int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
         DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
         // Counts the number of unique portNumber sets for each deviceId.
         // Each distinct set of portNumbers will have a unique ID.
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/Combination.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/Combination.java
new file mode 100644
index 0000000..5275fa8
--- /dev/null
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/Combination.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * 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.pi.demo.app.tor;
+
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.math.IntMath;
+
+import java.util.AbstractSet;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Combination algorithm implementation, taken from Guava 23.
+ */
+final class Combination {
+
+    private Combination() {
+        // Hides constructor.
+    }
+
+    /**
+     * Returns a map from the ith element of list to i.
+     */
+    private static <E> ImmutableMap<E, Integer> indexMap(Collection<E> list) {
+        ImmutableMap.Builder<E, Integer> builder = ImmutableMap.builder();
+        int i = 0;
+        for (E e : list) {
+            builder.put(e, i++);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Returns the set of all subsets of {@code set} of size {@code size}. For example, {@code
+     * combinations(ImmutableSet.of(1, 2, 3), 2)} returns the set {@code {{1, 2}, {1, 3}, {2, 3}}}.
+     * <p>
+     * <p>Elements appear in these subsets in the same iteration order as they appeared in the input set. The order in
+     * which these subsets appear in the outer set is undefined.
+     * <p>
+     * <p>The returned set and its constituent sets use {@code equals} to decide whether two elements are identical,
+     * even if the input set uses a different concept of equivalence.
+     * <p>
+     * <p><i>Performance notes:</i> the memory usage of the returned set is only {@code O(n)}. When the result set is
+     * constructed, the input set is merely copied. Only as the result set is iterated are the individual subsets
+     * created. Each of these subsets occupies an additional O(n) memory but only for as long as the user retains a
+     * reference to it. That is, the set returned by {@code combinations} does not retain the individual subsets.
+     *
+     * @param set  the set of elements to take combinations of
+     * @param size the number of elements per combination
+     * @return the set of all combinations of {@code size} elements from {@code set}
+     * @throws IllegalArgumentException if {@code size} is not between 0 and {@code set.size()} inclusive
+     * @throws NullPointerException     if {@code set} is or contains {@code null}
+     * @since 23.0
+     */
+    static <E> Set<Set<E>> combinations(Set<E> set, final int size) {
+        final ImmutableMap<E, Integer> index = indexMap(set);
+        checkArgument(size > 0, "size");
+        checkArgument(size <= index.size(), "size (%s) must be <= set.size() (%s)", size, index.size());
+        if (size == 0) {
+            return ImmutableSet.of(ImmutableSet.<E>of());
+        } else if (size == index.size()) {
+            return ImmutableSet.of(index.keySet());
+        }
+        return new AbstractSet<Set<E>>() {
+            @Override
+            public boolean contains(Object o) {
+                if (o instanceof Set) {
+                    Set<?> s = (Set<?>) o;
+                    return s.size() == size && index.keySet().containsAll(s);
+                }
+                return false;
+            }
+
+            @Override
+            public Iterator<Set<E>> iterator() {
+                return new AbstractIterator<Set<E>>() {
+                    final BitSet bits = new BitSet(index.size());
+
+                    @Override
+                    protected Set<E> computeNext() {
+                        if (bits.isEmpty()) {
+                            bits.set(0, size);
+                        } else {
+                            int firstSetBit = bits.nextSetBit(0);
+                            int bitToFlip = bits.nextClearBit(firstSetBit);
+
+                            if (bitToFlip == index.size()) {
+                                return endOfData();
+                            }
+
+                          /*
+                           * The current set in sorted order looks like
+                           * {firstSetBit, firstSetBit + 1, ..., bitToFlip - 1, ...}
+                           * where it does *not* contain bitToFlip.
+                           *
+                           * The next combination is
+                           *
+                           * {0, 1, ..., bitToFlip - firstSetBit - 2, bitToFlip, ...}
+                           *
+                           * This is lexicographically next if you look at the combinations in descending order
+                           * e.g. {2, 1, 0}, {3, 1, 0}, {3, 2, 0}, {3, 2, 1}, {4, 1, 0}...
+                           */
+
+                            bits.set(0, bitToFlip - firstSetBit - 1);
+                            bits.clear(bitToFlip - firstSetBit - 1, bitToFlip);
+                            bits.set(bitToFlip);
+                        }
+                        final BitSet copy = (BitSet) bits.clone();
+                        return new AbstractSet<E>() {
+                            @Override
+                            public boolean contains(Object o) {
+                                Integer i = index.get(o);
+                                return i != null && copy.get(i);
+                            }
+
+                            @Override
+                            public Iterator<E> iterator() {
+                                return new AbstractIterator<E>() {
+                                    int i = -1;
+
+                                    @Override
+                                    protected E computeNext() {
+                                        i = copy.nextSetBit(i + 1);
+                                        if (i == -1) {
+                                            return endOfData();
+                                        }
+                                        return index.keySet().asList().get(i);
+                                    }
+                                };
+                            }
+
+                            @Override
+                            public int size() {
+                                return size;
+                            }
+                        };
+                    }
+                };
+            }
+
+            @Override
+            public int size() {
+                return IntMath.binomial(index.size(), size);
+            }
+
+            @Override
+            public String toString() {
+                return "Sets.combinations(" + index.keySet() + ", " + size + ")";
+            }
+        };
+    }
+
+}
\ No newline at end of file
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java
index 5b1eea3..6ce8697 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorApp.java
@@ -16,33 +16,47 @@
 
 package org.onosproject.pi.demo.app.tor;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Maps;
+import com.google.common.collect.Lists;
 import org.apache.felix.scr.annotations.Component;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
+import org.onosproject.net.Path;
+import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.topology.DefaultTopologyVertex;
 import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyGraph;
 import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.collect.Collections2.permutations;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onosproject.pi.demo.app.tor.Combination.combinations;
+import static org.onosproject.pi.demo.app.tor.TorInterpreter.*;
 
 
 /**
  * Implementation of an upgradable fabric app for TOR configuration.
  */
-//TODO implement
 @Component(immediate = true)
 public class TorApp extends AbstractUpgradableFabricApp {
 
     private static final String APP_NAME = "org.onosproject.pi-tor";
 
-    private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
-
     public TorApp() {
         super(APP_NAME, TorPipeconfFactory.getAll());
     }
@@ -54,18 +68,178 @@
     }
 
     @Override
-    public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
+    public List<FlowRule> generateLeafRules(DeviceId leaf, Host localHost, Set<Host> remoteHosts,
                                             Collection<DeviceId> availableSpines, Topology topo)
             throws FlowRuleGeneratorException {
 
-        return ImmutableList.of();
+        // Get ports which connect this leaf switch to hosts.
+        Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
+                .stream()
+                .filter(port -> !isFabricPort(port, topo))
+                .map(Port::number)
+                .collect(Collectors.toSet());
+
+        // Get ports which connect this leaf to the given available spines.
+        TopologyGraph graph = topologyService.getGraph(topo);
+        Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
+                .stream()
+                .filter(e -> availableSpines.contains(e.dst().deviceId()))
+                .map(e -> e.link().src().port())
+                .collect(Collectors.toSet());
+
+        if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
+            log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
+                      hostPorts.size(), fabricPorts.size());
+            throw new FlowRuleGeneratorException();
+        }
+        PortNumber hostPort = hostPorts.iterator().next();
+
+        List<FlowRule> rules = Lists.newArrayList();
+
+        // Filtering rules
+        rules.addAll(generateFilteringRules(leaf, remoteHosts));
+        rules.addAll(generateFilteringRules(leaf, Collections.singleton(localHost)));
+
+        // FIXME: ignore ECMP for the moment
+        // if (fabricPorts.size() > 1) {
+        //     // Do ECMP.
+        //     Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(leaf, fabricPorts);
+        //     rules.addAll(result.getRight());
+        //     treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
+        // } else {
+        //     // Output on port.
+        //     PortNumber outPort = fabricPorts.iterator().next();
+        //     treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
+        // }
+
+
+        PortNumber outPort = fabricPorts.iterator().next();
+
+        // From local host to remote ones.
+        for (Host remoteHost : remoteHosts) {
+            for (IpAddress ipAddr : remoteHost.ipAddresses()) {
+                FlowRule rule = flowRuleBuilder(leaf, L3_FWD_TBL_ID)
+                        .withSelector(
+                                DefaultTrafficSelector.builder()
+                                        .matchIPDst(ipAddr.toIpPrefix())
+                                        .build())
+                        .withTreatment(
+                                DefaultTrafficTreatment.builder()
+                                        .piTableAction(nextHopAction(outPort, localHost.mac(), remoteHost.mac()))
+                                        .build())
+                        .build();
+                rules.add(rule);
+            }
+        }
+
+        // From remote hosts to the local one
+        for (IpAddress dstIpAddr : localHost.ipAddresses()) {
+            for (Host remoteHost : remoteHosts) {
+                FlowRule rule = flowRuleBuilder(leaf, L3_FWD_TBL_ID)
+                        .withSelector(
+                                DefaultTrafficSelector.builder()
+                                        .matchIPDst(dstIpAddr.toIpPrefix())
+                                        .build())
+                        .withTreatment(
+                                DefaultTrafficTreatment.builder()
+                                        .piTableAction(nextHopAction(hostPort, remoteHost.mac(), localHost.mac()))
+                                        .build())
+                        .build();
+                rules.add(rule);
+            }
+        }
+
+        return rules;
+    }
+
+    private PiAction nextHopAction(PortNumber port, MacAddress smac, MacAddress dmac) {
+        return PiAction.builder()
+                .withId(SET_NEXT_HOP_ACT_ID)
+                .withParameter(new PiActionParam(PORT_ACT_PRM_ID, copyFrom(port.toLong())))
+                // Ignore L3 routing behaviour by keeping the original host mac addresses at each hop.
+                .withParameter(new PiActionParam(SMAC_ACT_PRM_ID, copyFrom(smac.toBytes())))
+                .withParameter(new PiActionParam(DMAC_ACT_PRM_ID, copyFrom(dmac.toBytes())))
+                .build();
     }
 
     @Override
-    public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
+    public List<FlowRule> generateSpineRules(DeviceId spine, Set<Host> hosts, Topology topo)
             throws FlowRuleGeneratorException {
 
-        return ImmutableList.of();
+        List<FlowRule> rules = Lists.newArrayList();
+
+        rules.addAll(generateFilteringRules(spine, hosts));
+
+        // For each host pair (src -> dst)
+        for (Set<Host> hostCombs : combinations(hosts, 2)) {
+            for (List<Host> hostPair : permutations(hostCombs)) {
+
+                Host srcHost = hostPair.get(0);
+                Host dstHost = hostPair.get(1);
+
+                Set<Path> paths = topologyService.getPaths(topo, spine, dstHost.location().deviceId());
+
+                if (paths.size() == 0) {
+                    log.warn("Can't find any path between spine {} and host {}", spine, dstHost);
+                    throw new FlowRuleGeneratorException();
+                }
+
+                TrafficTreatment treatment;
+
+                // FIXME: ingore ECMP for the moment
+                // if (paths.size() == 1) {
+                //     // Only one path, do output on port.
+                //     PortNumber port = paths.iterator().next().src().port();
+                //     treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
+                // } else {
+                //     // Multiple paths, do ECMP.
+                //     Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
+                //     Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(deviceId, portNumbers);
+                //     rules.addAll(result.getRight());
+                //     treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
+                // }
+
+                PortNumber port = paths.iterator().next().src().port();
+                treatment = DefaultTrafficTreatment.builder()
+                        .piTableAction(nextHopAction(port, srcHost.mac(), dstHost.mac()))
+                        .build();
+
+                for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
+                    FlowRule rule = flowRuleBuilder(spine, L3_FWD_TBL_ID)
+                            .withSelector(
+                                    DefaultTrafficSelector.builder()
+                                            .matchIPDst(dstIpAddr.toIpPrefix())
+                                            .build())
+                            .withTreatment(treatment)
+                            .build();
+
+                    rules.add(rule);
+                }
+            }
+        }
+
+        return rules;
+    }
+
+    private List<FlowRule> generateFilteringRules(DeviceId deviceId, Collection<Host> dstHosts)
+            throws FlowRuleGeneratorException {
+
+        List<FlowRule> rules = Lists.newArrayList();
+        for (Host host : dstHosts) {
+            MacAddress ethAddr = host.mac();
+            FlowRule rule = flowRuleBuilder(deviceId, L3_FILTER_TBL_ID)
+                    .withSelector(DefaultTrafficSelector.builder()
+                                          .matchEthDst(ethAddr)
+                                          .build())
+                    .withTreatment(DefaultTrafficTreatment.builder()
+                                           .piTableAction(PiAction.builder()
+                                                                  .withId(PiActionId.of("NoAction"))
+                                                                  .build())
+                                           .build())
+                    .build();
+            rules.add(rule);
+        }
+        return rules;
     }
 
 }
\ No newline at end of file
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
index 5bd3864..c893e4a 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
@@ -77,19 +77,34 @@
     private static final String EGRESS_PORT = "egress_physical_port";
     private static final String INGRESS_PORT = "ingress_physical_port";
 
+    static final PiTableId L3_FILTER_TBL_ID = PiTableId.of("l3_fwd", "l3_routing_classifier_table");
+    static final PiTableId L3_FWD_TBL_ID = PiTableId.of("l3_fwd", "l3_ipv4_override_table");
+    static final PiActionId SET_NEXT_HOP_ACT_ID = PiActionId.of("l3_fwd.set_nexthop");
+    static final PiActionParamId PORT_ACT_PRM_ID = PiActionParamId.of("port");
+    static final PiActionParamId SMAC_ACT_PRM_ID = PiActionParamId.of("smac");
+    static final PiActionParamId DMAC_ACT_PRM_ID = PiActionParamId.of("dmac");
+
     // Set as per value in headers.p4 in packet_out_header
     private static final int PORT_FIELD_BITWIDTH = 9;
 
     private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "ether_type");
+    private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet", "dst_addr");
+    private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet", "src_addr");
+    private static final PiHeaderFieldId IPV4_DST_ID = PiHeaderFieldId.of("ipv4_base", "dst_addr");
 
     private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
             // If we use the DefaultSingleTable pipeliner, then we need to map only table ID 0,
             // which in this case will be the one used by proxyarp, lldpprovider, etc., i.e. the punt table.
-            0, PUNT_TABLE_ID);
+            0, PUNT_TABLE_ID,
+            1, L3_FILTER_TBL_ID,
+            2, L3_FWD_TBL_ID);
 
     private ImmutableBiMap<Criterion.Type, PiHeaderFieldId> criterionMap =
             new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+                    .put(Criterion.Type.ETH_DST, ETH_DST_ID)
+                    .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
                     .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
+                    .put(Criterion.Type.IPV4_DST, IPV4_DST_ID)
                     .build();
 
     //FIXME figure out what queque id is, we set as all zeros for now.