[VOL-4568] Changes to onos-mac-learner app to forward the DHCP packets

Change-Id: I85c2185238afc788f40d9e32a4a4de96cb3a1ebd
diff --git a/app/pom.xml b/app/pom.xml
index a518167..3543200 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -44,6 +44,12 @@
             <version>${maclearner.api.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.opencord</groupId>
+            <artifactId>sadis-api</artifactId>
+            <version>${sadis.api.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.onosproject</groupId>
             <artifactId>onos-cli</artifactId>
             <version>${onos.version}</version>
diff --git a/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java b/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java
index 437cd3c..6815c5d 100644
--- a/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java
+++ b/app/src/main/java/org/opencord/maclearner/app/impl/MacLearnerManager.java
@@ -33,13 +33,21 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.Link;
+import org.onosproject.net.Port;
 import org.onosproject.net.device.DeviceEvent;
 import org.onosproject.net.device.DeviceListener;
 import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.host.HostService;
 import org.onosproject.net.link.LinkService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyService;
 import org.onosproject.store.service.ConsistentMap;
@@ -55,6 +63,9 @@
 import org.opencord.maclearner.api.MacLearnerProviderService;
 import org.opencord.maclearner.api.MacLearnerService;
 import org.opencord.maclearner.api.MacLearnerValue;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
 import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
@@ -80,10 +91,13 @@
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.WallClockTimestamp;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.net.URI;
+import java.nio.ByteBuffer;
 import java.util.Date;
 import java.util.Dictionary;
 import java.util.List;
@@ -106,6 +120,8 @@
 import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING_DEFAULT;
 import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION_DEFAULT;
 import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION;
+import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DHCP_FORWARD;
+import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DHCP_FORWARD_DEFAULT;
 import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY;
 
 /**
@@ -114,7 +130,8 @@
 @Component(immediate = true,
         property = {
                 CACHE_DURATION + ":Integer=" + CACHE_DURATION_DEFAULT,
-                AUTO_CLEAR_MAC_MAPPING + ":Boolean=" + AUTO_CLEAR_MAC_MAPPING_DEFAULT
+                AUTO_CLEAR_MAC_MAPPING + ":Boolean=" + AUTO_CLEAR_MAC_MAPPING_DEFAULT,
+                ENABLE_DHCP_FORWARD + ":Boolean=" + ENABLE_DHCP_FORWARD_DEFAULT
         },
         service = MacLearnerService.class
 )
@@ -133,6 +150,14 @@
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
+    protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL,
+            bind = "bindSadisService",
+            unbind = "unbindSadisService",
+            policy = ReferencePolicy.DYNAMIC)
+    protected volatile SadisService sadisService;
+
     @Reference(cardinality = MANDATORY)
     protected CoreService coreService;
 
@@ -160,6 +185,9 @@
     @Reference(cardinality = MANDATORY)
     protected LinkService linkService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY)
+    protected HostService hostService;
+
     private final MacLearnerPacketProcessor macLearnerPacketProcessor =
             new MacLearnerPacketProcessor();
 
@@ -170,6 +198,11 @@
     public static final int HASH_WEIGHT = 10;
 
     /**
+     * Enables Dhcp forwarding.
+     */
+    protected boolean enableDhcpForward = ENABLE_DHCP_FORWARD_DEFAULT;
+
+    /**
      * Minimum duration of mapping, mapping can be exist until 2*cacheDuration because of cleanerTimer fixed rate.
      */
     protected int cacheDurationSec = CACHE_DURATION_DEFAULT;
@@ -219,6 +252,13 @@
         clusterService.addListener(clusterListener);
         deviceService.addListener(deviceListener);
         createSchedulerForClearMacMappings();
+
+        if (sadisService != null) {
+            subsService = sadisService.getSubscriberInfoService();
+        } else {
+            log.warn("Sadis is not running");
+        }
+
         log.info("{} is started.", getClass().getSimpleName());
     }
 
@@ -275,6 +315,16 @@
         return nodeId.equals(clusterService.getLocalNode().id());
     }
 
+    protected void bindSadisService(SadisService service) {
+        this.subsService = service.getSubscriberInfoService();
+        log.info("Sadis service is loaded");
+    }
+
+    protected void unbindSadisService(SadisService service) {
+        this.subsService = null;
+        log.info("Sadis service is unloaded");
+    }
+
     @Deactivate
     public void deactivate() {
         if (scheduledFuture != null) {
@@ -303,6 +353,14 @@
                 setMacMappingCacheDuration(cacheDur);
             }
         }
+
+        Boolean o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_FORWARD);
+        if (o != null) {
+            if (o != enableDhcpForward) {
+                log.info("Changing enableDhcpForward to: {} from {}", o, enableDhcpForward);
+                enableDhcpForward = o;
+            }
+        }
     }
 
     private Integer setMacMappingCacheDuration(Integer second) {
@@ -557,11 +615,92 @@
                         DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
                         //This packet is dhcp.
                         processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlan);
+
+                        if (enableDhcpForward) {
+                            // Forward DHCP Packet to either uni or nni.
+                            forwardDhcpPacket(packet, dhcpPayload, device, vlan);
+                        }
                     }
                 }
             }
         }
 
+        /**
+         * Returns the connectPoint which is the uplink port of the OLT.
+         */
+        private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
+
+            Device device = deviceService.getDevice(dId);
+
+            if (device == null) {
+                log.warn("Could not find device for device ID {}", dId);
+                return null;
+            }
+
+            SubscriberAndDeviceInformation deviceInfo = subsService.get(device.serialNumber());
+            if (deviceInfo != null) {
+                log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
+                PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
+                Port port = deviceService.getPort(device.id(), pNum);
+                if (port != null) {
+                    return new ConnectPoint(device.id(), pNum);
+                } else {
+                    log.warn("Unable to find Port in deviceService for deice ID : {}, port : {}", dId, pNum);
+                }
+            } else {
+                log.warn("Unable to find Sadis entry for device ID : {}, device serial : {}",
+                        dId, device.serialNumber());
+            }
+
+            return null;
+        }
+
+        /***
+         * Forwards the packet to uni port or nni port based on the DHCP source port.
+         * Client DHCP packets are transparently forwarded to the nni port.
+         * Server DHCP replies are forwared to the respective uni port based on the (mac,vlan) lookup
+         */
+        private void forwardDhcpPacket(Ethernet packet, DHCP dhcpPayload, Device device, VlanId vlan) {
+            UDP udpPacket = (UDP) dhcpPayload.getParent();
+            int udpSourcePort = udpPacket.getSourcePort();
+            MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
+
+            ConnectPoint destinationCp = null;
+
+            if (udpSourcePort == UDP.DHCP_CLIENT_PORT) {
+                destinationCp = getUplinkConnectPointOfOlt(device.id());
+            } else if (udpSourcePort == UDP.DHCP_SERVER_PORT) {
+                Host host = hostService.getHost(HostId.hostId(clientMacAddress, vlan));
+
+                ElementId elementId = host.location().elementId();
+                PortNumber portNumber = host.location().port();
+
+                destinationCp = new ConnectPoint(elementId, portNumber);
+            }
+
+            if (destinationCp == null) {
+                log.error("No connect point to send msg to DHCP message");
+                return;
+            }
+
+            if (log.isTraceEnabled()) {
+                VlanId printVlan = VlanId.NONE;
+
+                if (vlan != null) {
+                    printVlan = vlan;
+                }
+
+                log.trace("Emitting : packet {}, with MAC {}, with VLAN {}, with connect point {}",
+                        getDhcpPacketType(dhcpPayload), clientMacAddress, printVlan, destinationCp);
+            }
+
+            TrafficTreatment t = DefaultTrafficTreatment.builder()
+                    .setOutput(destinationCp.port()).build();
+            OutboundPacket o = new DefaultOutboundPacket(destinationCp
+                    .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
+            packetService.emit(o);
+        }
+
         //process the dhcp packet before forwarding
         private void processDhcpPacket(PacketContext context, Ethernet packet,
                                        DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
@@ -622,7 +761,6 @@
                 sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
             }
         }
-
     }
 
     private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey, boolean vanishHost) {
diff --git a/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java b/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java
index 104b58e..11fdf2f 100644
--- a/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java
+++ b/app/src/main/java/org/opencord/maclearner/app/impl/OsgiPropertyConstants.java
@@ -29,4 +29,7 @@
     public static final String AUTO_CLEAR_MAC_MAPPING = "autoClearMacMapping";
     public static final boolean AUTO_CLEAR_MAC_MAPPING_DEFAULT = false;
 
+    public static final String ENABLE_DHCP_FORWARD = "enableDhcpForward";
+    public static final boolean ENABLE_DHCP_FORWARD_DEFAULT = false;
+
 }
diff --git a/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java b/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java
index adfbc5a..7ce813e 100644
--- a/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/MacLearnerManagerTest.java
@@ -26,8 +26,14 @@
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.OutboundPacket;
 
 import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
 import java.util.Optional;
 
 import static org.junit.Assert.assertEquals;
@@ -54,7 +60,7 @@
     }
 
     private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
-    private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
+    public static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
     private static final VlanId CLIENT_QINQ_VLAN = VlanId.vlanId("200");
     public static final DeviceId OLT_DEVICE_ID = DeviceId.deviceId("of:0000b86a974385f7");
     public static final PortNumber UNI_PORT = PortNumber.portNumber(16);
@@ -62,6 +68,9 @@
     public static final DeviceId AGG_DEVICE_ID = DeviceId.deviceId("of:0000000000000001");
     public static final PortNumber AGG_OLT_PORT = PortNumber.portNumber(10);
     public static final PortNumber OLT_NNI_PORT = PortNumber.portNumber(1048576);
+    public static final ConnectPoint NNI_CP = new ConnectPoint(OLT_DEVICE_ID, OLT_NNI_PORT);
+    public static final String OLT_SERIAL_NUMBER = "BBSIM_OLT_1";
+    private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:00:00:00:00:11");
 
     @Test
     public void testSingleTagDhcpPacket() {
@@ -112,4 +121,62 @@
         });
     }
 
+    @Test
+    public void testDhcpForwardClientRequest() {
+        this.macLearnerManager.enableDhcpForward = true;
+        TestDhcpRequestPacketContext dhcpRequest = new TestDhcpRequestPacketContext(CLIENT_MAC, CLIENT_VLAN,
+                VlanId.NONE, CLIENT_CP);
+        ByteBuffer inBuffer = dhcpRequest.inPacket().unparsed();
+
+        packetService.processPacket(dhcpRequest);
+
+        assertAfter(DELAY, PROCESSING_LENGTH, () -> {
+            OutboundPacket emittedPacket = packetService.emittedPacket;
+            ByteBuffer outBuffer = emittedPacket.data();
+            DeviceId deviceId = emittedPacket.sendThrough();
+            TrafficTreatment treatment = emittedPacket.treatment();
+            List<Instruction> instructions = treatment.allInstructions();
+
+            assertEquals(deviceId, OLT_DEVICE_ID);
+            for (Instruction instruction : instructions) {
+                if (instruction instanceof Instructions.OutputInstruction) {
+                    assertEquals(OLT_NNI_PORT, ((Instructions.OutputInstruction) instruction).port());
+                }
+            }
+
+            // Test for packet not modified
+            assertEquals(0, inBuffer.compareTo(outBuffer));
+       });
+    }
+
+    @Test
+    public void testDhcpForwardServerResponse() {
+        this.macLearnerManager.enableDhcpForward = true;
+        testDhcpForwardClientRequest();
+
+        TestDhcpResponsePacketContext dhcpResponse = new TestDhcpResponsePacketContext(CLIENT_MAC, SERVER_MAC,
+                CLIENT_VLAN, VlanId.NONE, NNI_CP);
+        ByteBuffer inBuffer = dhcpResponse.inPacket().unparsed();
+
+        packetService.processPacket(dhcpResponse);
+
+        assertAfter(DELAY, PROCESSING_LENGTH, () -> {
+            OutboundPacket emittedPacket = packetService.emittedPacket;
+            ByteBuffer outBuffer = emittedPacket.data();
+
+            DeviceId deviceId = emittedPacket.sendThrough();
+            TrafficTreatment treatment = emittedPacket.treatment();
+            List<Instruction> instructions = treatment.allInstructions();
+
+            assertEquals(deviceId, OLT_DEVICE_ID);
+            for (Instruction instruction : instructions) {
+                if (instruction instanceof Instructions.OutputInstruction) {
+                    assertEquals(UNI_PORT, ((Instructions.OutputInstruction) instruction).port());
+                }
+            }
+
+            // Test for packet not modified
+            assertEquals(0, inBuffer.compareTo(outBuffer));
+        });
+    }
 }
diff --git a/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java b/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
index ffbf0c3..c90522f 100644
--- a/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/TestBaseMacLearner.java
@@ -47,16 +47,20 @@
 import org.onosproject.event.Event;
 import org.onosproject.event.EventDeliveryService;
 import org.onosproject.event.EventSink;
+import org.onosproject.net.Annotations;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultDevice;
 import org.onosproject.net.DefaultHost;
 import org.onosproject.net.DefaultLink;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Element;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.config.ConfigApplyDelegate;
 import org.onosproject.net.config.basics.HostLearningConfig;
@@ -93,6 +97,10 @@
 import org.onosproject.store.service.SetEventListener;
 import org.onosproject.store.service.StorageServiceAdapter;
 import org.onosproject.store.service.Versioned;
+import org.opencord.sadis.BandwidthProfileInformation;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -113,8 +121,10 @@
 import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.AGG_DEVICE_ID;
 import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.AGG_OLT_PORT;
 import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.CLIENT_CP;
+import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.CLIENT_VLAN;
 import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.OLT_DEVICE_ID;
 import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.OLT_NNI_PORT;
+import static org.opencord.maclearner.app.impl.MacLearnerManagerTest.OLT_SERIAL_NUMBER;
 
 /**
  * Mac Learner mock services class.
@@ -150,6 +160,7 @@
     protected MockLinkService linkService = new MockLinkService();
     protected MacLearnerHostProvider macLearnerHostProvider = new MacLearnerHostProvider();
     protected MockHostService hostService = new MockHostService(Sets.newHashSet());
+    protected MockSadisService sadisService = new MockSadisService();
 
     public void setUpApp() throws IOException {
         macLearnerManager = new MacLearnerManager();
@@ -161,6 +172,8 @@
         macLearnerManager.deviceService = this.deviceService;
         macLearnerManager.topologyService = this.topologyService;
         macLearnerManager.linkService = this.linkService;
+        macLearnerManager.sadisService = this.sadisService;
+        macLearnerManager.hostService = this.hostService;
         hostLearningConfig = new HostLearningConfig();
         InputStream jsonStream = HostLearningConfigTest.class
                 .getResourceAsStream("/host-learning-config.json");
@@ -316,13 +329,18 @@
             if (deviceId.equals(OLT_DEVICE_ID)) {
                 return new DefaultDevice(null, OLT_DEVICE_ID, Device.Type.SWITCH,
                         "VOLTHA Project", "open_pon", "open_pon",
-                        "BBSIM_OLT_1", new ChassisId("a0a0a0a0a01"));
+                        OLT_SERIAL_NUMBER, new ChassisId("a0a0a0a0a01"));
             } else {
                 return null;
             }
         }
 
         @Override
+        public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+            return new TestBaseMacLearner.MockPort(new ConnectPoint(deviceId, portNumber));
+        }
+
+        @Override
         public void addListener(DeviceListener listener) {
             listeners.add(listener);
         }
@@ -333,6 +351,56 @@
         }
     }
 
+    static class MockPort implements Port {
+        private ConnectPoint cp;
+
+        public MockPort(ConnectPoint cp) {
+            this.cp = cp;
+        }
+        @Override
+        public boolean isEnabled() {
+            return true;
+        }
+        @Override
+        public long portSpeed() {
+            return 1000;
+        }
+        @Override
+        public Element element() {
+            return null;
+        }
+        @Override
+        public PortNumber number() {
+            return null;
+        }
+        @Override
+        public Annotations annotations() {
+            return new MockAnnotations();
+        }
+        @Override
+        public Type type() {
+            return Port.Type.FIBER;
+        }
+
+        private class MockAnnotations implements Annotations {
+
+            @Override
+            public String value(String val) {
+                if (cp.port().toLong() == 32) {
+                    return "ALPHe3d1cea3-1";
+                } else if (cp.port().toLong() == 4112) {
+                    return "ALPHe3d1ceb7-1";
+                } else {
+                    return "PON 1/1";
+                }
+            }
+            @Override
+            public Set<String> keys() {
+                return null;
+            }
+        }
+    }
+
     /**
      * Mocks the topology service of ONOS so that the application under test can
      * check fake topology.
@@ -693,6 +761,7 @@
                     hostDescription.ipAddress(), VlanId.NONE,
                     EthType.EtherType.UNKNOWN.ethType(), false, false));
             hostService = new MockHostService(previousHosts);
+            macLearnerManager.hostService = hostService;
         }
 
         @Override
@@ -703,6 +772,7 @@
             Host removedHost = hostService.getHost(hostId);
             previousHosts.remove(removedHost);
             hostService = new MockHostService(previousHosts);
+            macLearnerManager.hostService = hostService;
         }
 
         @Override
@@ -778,4 +848,112 @@
         }
     }
 
+    /**
+     * Generates DHCP RESPONSE packet.
+     */
+    protected static class TestDhcpResponsePacketContext extends PacketContextAdapter {
+
+        private InboundPacket inPacket;
+
+        public TestDhcpResponsePacketContext(MacAddress clientMacAddress, MacAddress serverMacAddress, VlanId vlanId,
+                                            VlanId qinqQVid, ConnectPoint connectPoint) {
+            super(0, null, null, false);
+            byte[] dhcpMsgType = new byte[1];
+            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPOFFER.getValue();
+
+            DhcpOption dhcpOption = new DhcpOption();
+            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
+            dhcpOption.setData(dhcpMsgType);
+            dhcpOption.setLength((byte) 1);
+            DhcpOption endOption = new DhcpOption();
+            endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());
+
+            DHCP dhcp = new DHCP();
+            dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
+            dhcp.setHardwareAddressLength((byte) 6);
+            dhcp.setClientHardwareAddress(clientMacAddress.toBytes());
+            dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
+            dhcp.setYourIPAddress(Ip4Address.valueOf("1.1.1.1").toInt());
+
+            UDP udp = new UDP();
+            udp.setPayload(dhcp);
+            udp.setSourcePort(UDP.DHCP_SERVER_PORT);
+            udp.setDestinationPort(UDP.DHCP_CLIENT_PORT);
+
+            IPv4 ipv4 = new IPv4();
+            ipv4.setPayload(udp);
+            ipv4.setDestinationAddress(INTERFACE_IP.toInt());
+            ipv4.setSourceAddress(SERVER_IP.toInt());
+
+            Ethernet eth = new Ethernet();
+            eth.setEtherType(Ethernet.TYPE_IPV4)
+                    .setVlanID(vlanId.toShort())
+                    .setQinQVID(qinqQVid.toShort())
+                    .setSourceMACAddress(serverMacAddress)
+                    .setDestinationMACAddress(clientMacAddress)
+                    .setPayload(ipv4);
+
+            this.inPacket = new DefaultInboundPacket(connectPoint, eth,
+                    ByteBuffer.wrap(eth.serialize()));
+        }
+
+        @Override
+        public InboundPacket inPacket() {
+            return this.inPacket;
+        }
+    }
+
+    /**
+     * Mock Sadis service.
+     */
+    static class MockSadisService implements SadisService {
+        @Override
+        public BaseInformationService<SubscriberAndDeviceInformation> getSubscriberInfoService() {
+            return new TestBaseMacLearner.MockSubService();
+        }
+
+        @Override
+        public BaseInformationService<BandwidthProfileInformation> getBandwidthProfileService() {
+            return null;
+        }
+    }
+
+    static class MockSubService implements BaseInformationService<SubscriberAndDeviceInformation> {
+        TestBaseMacLearner.MockSubscriberAndDeviceInformation device =
+                new TestBaseMacLearner.MockSubscriberAndDeviceInformation(OLT_SERIAL_NUMBER, CLIENT_VLAN, VlanId.NONE,
+                        null, null, null, null, (int) OLT_NNI_PORT.toLong());
+        @Override
+        public SubscriberAndDeviceInformation get(String id) {
+            if (id.equals(OLT_SERIAL_NUMBER)) {
+                return device;
+            }
+            return null;
+        }
+
+        @Override
+        public void clearLocalData() {}
+        @Override
+        public void invalidateAll() {}
+        @Override
+        public void invalidateId(String id) {}
+        @Override
+        public SubscriberAndDeviceInformation getfromCache(String id) {
+            return null;
+        }
+    }
+
+   static class MockSubscriberAndDeviceInformation extends SubscriberAndDeviceInformation {
+
+        MockSubscriberAndDeviceInformation(String id, VlanId cTag,
+                                           VlanId sTag, String nasPortId,
+                                           String circuitId, MacAddress hardId,
+                                           Ip4Address ipAddress, int uplinkPort) {
+            this.setHardwareIdentifier(hardId);
+            this.setId(id);
+            this.setIPAddress(ipAddress);
+            this.setNasPortId(nasPortId);
+            this.setUplinkPort(uplinkPort);
+            this.setCircuitId(circuitId);
+        }
+    }
 }
diff --git a/app/src/test/java/org/opencord/maclearner/app/impl/package-info.java b/app/src/test/java/org/opencord/maclearner/app/impl/package-info.java
new file mode 100644
index 0000000..dd64e64
--- /dev/null
+++ b/app/src/test/java/org/opencord/maclearner/app/impl/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+/**
+ * Mac Learner Tests.
+ */
+package org.opencord.maclearner.app.impl;
\ No newline at end of file