[SEBA-593] Splitting aaa app in implementation and api bundles

Change-Id: Ib81650be0695258bdd1f4cd6131f2206760e0da6
diff --git a/app/src/test/java/org/opencord/aaa/impl/AaaIntegrationTest.java b/app/src/test/java/org/opencord/aaa/impl/AaaIntegrationTest.java
new file mode 100644
index 0000000..8ded2f8
--- /dev/null
+++ b/app/src/test/java/org/opencord/aaa/impl/AaaIntegrationTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2015-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.opencord.aaa.impl;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.onlab.packet.EAP;
+import org.onlab.packet.EAPOL;
+import org.onlab.packet.Ethernet;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.opencord.aaa.AaaConfig;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Set of tests of the ONOS application component. These use an existing RADIUS
+ * server and sends live packets over the network to it.
+ */
+@Ignore ("This should not be run as part of the standard build")
+public class AaaIntegrationTest extends AaaTestBase {
+
+    private AaaManager aaa;
+
+    /**
+     * Mocks the network config registry.
+     */
+    @SuppressWarnings("unchecked")
+    static final class TestNetworkConfigRegistry
+            extends NetworkConfigRegistryAdapter {
+        @Override
+        public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+            return (C) new AaaConfig();
+        }
+    }
+
+    /**
+     * Sets up the services required by the AAA application.
+     */
+    @Before
+    public void setUp() {
+        aaa = new AaaManager();
+        aaa.netCfgService = new TestNetworkConfigRegistry();
+        aaa.coreService = new CoreServiceAdapter();
+        aaa.packetService = new MockPacketService();
+        aaa.activate();
+    }
+
+    /**
+     * Fetches the sent packet at the given index. The requested packet
+     * must be the last packet on the list.
+     *
+     * @param index index into sent packets array
+     * @return packet
+     */
+    private Ethernet fetchPacket(int index) {
+        for (int iteration = 0; iteration < 20; iteration++) {
+            if (savedPackets.size() > index) {
+                return (Ethernet) savedPackets.get(index);
+            } else {
+                try {
+                    Thread.sleep(250);
+                } catch (Exception ex) {
+                    return null;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Tests the authentication path through the AAA application by sending
+     * packets to the RADIUS server and checking the state machine
+     * transitions.
+     *
+     * @throws Exception when an unhandled error occurs
+     */
+    @Test
+    public void testAuthentication()  throws Exception {
+
+        //  (1) Supplicant start up
+
+        Ethernet startPacket = constructSupplicantStartPacket();
+        sendPacket(startPacket);
+
+        Ethernet responsePacket = fetchPacket(0);
+        assertThat(responsePacket, notNullValue());
+        checkRadiusPacket(aaa, responsePacket, EAP.REQUEST);
+
+        //  (2) Supplicant identify
+
+        Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null);
+        sendPacket(identifyPacket);
+
+        //  State machine should have been created by now
+
+        StateMachine stateMachine =
+                StateMachine.lookupStateMachineBySessionId(SESSION_ID);
+        assertThat(stateMachine, notNullValue());
+        assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
+
+        // (3) RADIUS MD5 challenge
+
+        Ethernet radiusChallengeMD5Packet = fetchPacket(1);
+        assertThat(radiusChallengeMD5Packet, notNullValue());
+        checkRadiusPacket(aaa, radiusChallengeMD5Packet, EAP.REQUEST);
+
+
+        // (4) Supplicant MD5 response
+
+        Ethernet md5RadiusPacket =
+                constructSupplicantIdentifyPacket(stateMachine,
+                                                  EAP.ATTR_MD5,
+                                                  stateMachine.challengeIdentifier(),
+                                                  radiusChallengeMD5Packet);
+        sendPacket(md5RadiusPacket);
+
+
+        // (5) RADIUS Success
+
+        Ethernet successRadiusPacket = fetchPacket(2);
+        assertThat(successRadiusPacket, notNullValue());
+        EAPOL successEapol = (EAPOL) successRadiusPacket.getPayload();
+        EAP successEap = (EAP) successEapol.getPayload();
+        assertThat(successEap.getCode(), is(EAP.SUCCESS));
+
+        //  State machine should be in authorized state
+
+        assertThat(stateMachine, notNullValue());
+        assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
+
+    }
+
+}
+
diff --git a/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java b/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java
new file mode 100644
index 0000000..8827c1a
--- /dev/null
+++ b/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2015-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.opencord.aaa.impl;
+
+import com.google.common.base.Charsets;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestUtils;
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.EAP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.RADIUS;
+import org.onlab.packet.RADIUSAttribute;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
+import org.onosproject.net.config.Config;
+import org.onosproject.net.config.NetworkConfigRegistryAdapter;
+import org.onosproject.net.packet.InboundPacket;
+import org.opencord.aaa.AaaConfig;
+
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Set of tests of the ONOS application component.
+ */
+public class AaaManagerTest extends AaaTestBase {
+
+    static final String BAD_IP_ADDRESS = "198.51.100.0";
+
+    private AaaManager aaaManager;
+
+    class AaaManagerWithoutRadiusServer extends AaaManager {
+        protected void sendRadiusPacket(RADIUS radiusPacket, InboundPacket inPkt) {
+            savePacket(radiusPacket);
+        }
+    }
+
+    /**
+     * Mocks the AAAConfig class to force usage of an unroutable address for the
+     * RADIUS server.
+     */
+    static class MockAaaConfig extends AaaConfig {
+        @Override
+        public InetAddress radiusIp() {
+            try {
+                return InetAddress.getByName(BAD_IP_ADDRESS);
+            } catch (UnknownHostException ex) {
+                // can't happen
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    /**
+     * Mocks the network config registry.
+     */
+    @SuppressWarnings("unchecked")
+    private static final class TestNetworkConfigRegistry
+            extends NetworkConfigRegistryAdapter {
+        @Override
+        public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
+            AaaConfig aaaConfig = new MockAaaConfig();
+            return (C) aaaConfig;
+        }
+    }
+
+    public static class TestEventDispatcher extends DefaultEventSinkRegistry
+            implements EventDeliveryService {
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public synchronized void post(Event event) {
+            EventSink sink = getSink(event.getClass());
+            checkState(sink != null, "No sink for event %s", event);
+            sink.process(event);
+        }
+
+        @Override
+        public void setDispatchTimeLimit(long millis) {
+        }
+
+        @Override
+        public long getDispatchTimeLimit() {
+            return 0;
+        }
+    }
+
+    /**
+     * Constructs an Ethernet packet containing a RADIUS challenge
+     * packet.
+     *
+     * @param challengeCode code to use in challenge packet
+     * @param challengeType type to use in challenge packet
+     * @return Ethernet packet
+     */
+    private RADIUS constructRadiusCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
+
+        String challenge = "12345678901234567";
+
+        EAP eap = new EAP(challengeType, (byte) 1, challengeType,
+                          challenge.getBytes(Charsets.US_ASCII));
+        eap.setIdentifier((byte) 1);
+
+        RADIUS radius = new RADIUS();
+        radius.setCode(challengeCode);
+
+        radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
+                            challenge.getBytes(Charsets.US_ASCII));
+
+        radius.setPayload(eap);
+        radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
+                            eap.serialize());
+
+        return radius;
+    }
+
+    public static void injectEventDispatcher(Object manager, EventDeliveryService svc) {
+        Class mc = manager.getClass();
+        for (Field f : mc.getSuperclass().getDeclaredFields()) {
+            if (f.getType().equals(EventDeliveryService.class)) {
+                try {
+                    TestUtils.setField(manager, f.getName(), svc);
+                } catch (TestUtils.TestUtilsException e) {
+                    throw new IllegalArgumentException("Unable to inject reference", e);
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Sets up the services required by the AAA application.
+     */
+    @Before
+    public void setUp() {
+        aaaManager = new AaaManagerWithoutRadiusServer();
+        aaaManager.netCfgService = new TestNetworkConfigRegistry();
+        aaaManager.coreService = new CoreServiceAdapter();
+        aaaManager.packetService = new MockPacketService();
+        aaaManager.deviceService = new TestDeviceService();
+        aaaManager.sadisService = new MockSadisService();
+        TestUtils.setField(aaaManager, "eventDispatcher", new TestEventDispatcher());
+        aaaManager.activate();
+    }
+
+    /**
+     * Tears down the AAA application.
+     */
+    @After
+    public void tearDown() {
+        aaaManager.deactivate();
+    }
+
+    /**
+     * Extracts the RADIUS packet from a packet sent by the supplicant.
+     *
+     * @param radius RADIUS packet sent by the supplicant
+     * @throws DeserializationException if deserialization of the packet contents
+     *         fails.
+     */
+    private void checkRadiusPacketFromSupplicant(RADIUS radius)
+            throws DeserializationException {
+        assertThat(radius, notNullValue());
+
+        EAP eap = radius.decapsulateMessage();
+        assertThat(eap, notNullValue());
+    }
+
+    /**
+     * Fetches the sent packet at the given index. The requested packet
+     * must be the last packet on the list.
+     *
+     * @param index index into sent packets array
+     * @return packet
+     */
+    private BasePacket fetchPacket(int index) {
+        BasePacket packet = savedPackets.get(index);
+        assertThat(packet, notNullValue());
+        return packet;
+    }
+
+    /**
+     * Tests the authentication path through the AAA application.
+     *
+     * @throws DeserializationException if packed deserialization fails.
+     */
+    @Test
+    public void testAuthentication()  throws Exception {
+
+        //  (1) Supplicant start up
+
+        Ethernet startPacket = constructSupplicantStartPacket();
+        sendPacket(startPacket);
+
+        Ethernet responsePacket = (Ethernet) fetchPacket(0);
+        checkRadiusPacket(aaaManager, responsePacket, EAP.ATTR_IDENTITY);
+
+        //  (2) Supplicant identify
+
+        Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null);
+        sendPacket(identifyPacket);
+
+        RADIUS radiusIdentifyPacket = (RADIUS) fetchPacket(1);
+
+        checkRadiusPacketFromSupplicant(radiusIdentifyPacket);
+
+        assertThat(radiusIdentifyPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
+        assertThat(new String(radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()),
+                   is("testuser"));
+
+        IpAddress nasIp =
+                IpAddress.valueOf(IpAddress.Version.INET,
+                                  radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
+                                          .getValue());
+        assertThat(nasIp.toString(), is(aaaManager.nasIpAddress.getHostAddress()));
+
+        //  State machine should have been created by now
+
+        StateMachine stateMachine =
+                StateMachine.lookupStateMachineBySessionId(SESSION_ID);
+        assertThat(stateMachine, notNullValue());
+        assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
+
+        // (3) RADIUS MD5 challenge
+
+        RADIUS radiusCodeAccessChallengePacket =
+                constructRadiusCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5);
+        aaaManager.handleRadiusPacket(radiusCodeAccessChallengePacket);
+
+        Ethernet radiusChallengeMD5Packet = (Ethernet) fetchPacket(2);
+        checkRadiusPacket(aaaManager, radiusChallengeMD5Packet, EAP.ATTR_MD5);
+
+        // (4) Supplicant MD5 response
+
+        Ethernet md5RadiusPacket =
+                constructSupplicantIdentifyPacket(stateMachine,
+                                                  EAP.ATTR_MD5,
+                                                  stateMachine.challengeIdentifier(),
+                                                  radiusChallengeMD5Packet);
+        sendPacket(md5RadiusPacket);
+
+        RADIUS responseMd5RadiusPacket = (RADIUS) fetchPacket(3);
+
+        checkRadiusPacketFromSupplicant(responseMd5RadiusPacket);
+        assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 3));
+        assertThat(responseMd5RadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
+
+        //  State machine should be in pending state
+
+        assertThat(stateMachine, notNullValue());
+        assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
+
+        // (5) RADIUS Success
+
+        RADIUS successPacket =
+                constructRadiusCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS);
+        aaaManager.handleRadiusPacket((successPacket));
+        Ethernet supplicantSuccessPacket = (Ethernet) fetchPacket(4);
+
+        checkRadiusPacket(aaaManager, supplicantSuccessPacket, EAP.SUCCESS);
+
+        //  State machine should be in authorized state
+
+        assertThat(stateMachine, notNullValue());
+        assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
+
+    }
+
+    /**
+     * Tests the default configuration.
+     */
+    @Test
+    public void testConfig() {
+        assertThat(aaaManager.nasIpAddress.getHostAddress(), is(AaaConfig.DEFAULT_NAS_IP));
+        assertThat(aaaManager.nasMacAddress, is(AaaConfig.DEFAULT_NAS_MAC));
+        assertThat(aaaManager.radiusIpAddress.getHostAddress(), is(BAD_IP_ADDRESS));
+        assertThat(aaaManager.radiusMacAddress, is(AaaConfig.DEFAULT_RADIUS_MAC));
+    }
+}
diff --git a/app/src/test/java/org/opencord/aaa/impl/AaaTestBase.java b/app/src/test/java/org/opencord/aaa/impl/AaaTestBase.java
new file mode 100644
index 0000000..2e21ea3
--- /dev/null
+++ b/app/src/test/java/org/opencord/aaa/impl/AaaTestBase.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2015-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.opencord.aaa.impl;
+
+import org.onlab.packet.BasePacket;
+import org.onlab.packet.EAP;
+import org.onlab.packet.EAPOL;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+
+import org.onosproject.net.Annotations;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Element;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.DefaultPacketContext;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketServiceAdapter;
+
+import org.opencord.sadis.BandwidthProfileInformation;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.onosproject.net.NetTestTools.connectPoint;
+
+/**
+ * Common methods for AAA app testing.
+ */
+public class AaaTestBase {
+
+    MacAddress clientMac = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
+    MacAddress serverMac = MacAddress.valueOf("2a:2a:2a:2a:2a:2a");
+
+    // Our session id will be the device ID ("of:1") with the port ("1") concatenated
+    static final String SESSION_ID = "of:11";
+
+    List<BasePacket> savedPackets = new LinkedList<>();
+    PacketProcessor packetProcessor;
+
+    /**
+     * Saves the given packet onto the saved packets list.
+     *
+     * @param packet packet to save
+     */
+    void savePacket(BasePacket packet) {
+        savedPackets.add(packet);
+    }
+
+    /**
+     * Keeps a reference to the PacketProcessor and saves the OutboundPackets.
+     */
+    class MockPacketService extends PacketServiceAdapter {
+
+        @Override
+        public void addProcessor(PacketProcessor processor, int priority) {
+            packetProcessor = processor;
+        }
+
+        @Override
+        public void emit(OutboundPacket packet) {
+            try {
+                Ethernet eth = Ethernet.deserializer().deserialize(packet.data().array(),
+                                                                   0, packet.data().array().length);
+                savePacket(eth);
+            } catch (Exception e) {
+                fail(e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Mocks the DeviceService.
+     */
+    final class TestDeviceService extends DeviceServiceAdapter {
+        @Override
+        public Port getPort(ConnectPoint cp) {
+            return new MockPort();
+        }
+    }
+    private class  MockPort implements Port {
+
+        @Override
+        public boolean isEnabled() {
+            return true;
+        }
+        public long portSpeed() {
+            return 1000;
+        }
+        public Element element() {
+            return null;
+        }
+        public PortNumber number() {
+            return null;
+        }
+        public Annotations annotations() {
+            return new MockAnnotations();
+        }
+        public Type type() {
+            return Port.Type.FIBER;
+        }
+
+        private class MockAnnotations implements Annotations {
+
+            @Override
+            public String value(String val) {
+                return "PON 1/1";
+            }
+            public Set<String> keys() {
+                return null;
+            }
+        }
+    }
+
+    private class MockSubscriberAndDeviceInformation extends SubscriberAndDeviceInformation {
+
+        MockSubscriberAndDeviceInformation(String id, VlanId ctag,
+                                           VlanId stag, String nasPortId,
+                                           String circuitId, MacAddress hardId,
+                                           Ip4Address ipAddress) {
+            this.setCTag(ctag);
+            this.setHardwareIdentifier(hardId);
+            this.setId(id);
+            this.setIPAddress(ipAddress);
+            this.setSTag(stag);
+            this.setNasPortId(nasPortId);
+            this.setCircuitId(circuitId);
+        }
+    }
+
+    final class MockSadisService implements SadisService {
+
+        @Override
+        public BaseInformationService<SubscriberAndDeviceInformation> getSubscriberInfoService() {
+            return new MockSubService();
+        }
+
+        @Override
+        public BaseInformationService<BandwidthProfileInformation> getBandwidthProfileService() {
+            return null;
+        }
+    }
+
+    final class MockSubService implements BaseInformationService<SubscriberAndDeviceInformation> {
+        private final VlanId clientCtag = VlanId.vlanId((short) 999);
+        private final VlanId clientStag = VlanId.vlanId((short) 111);
+        private final String clientNasPortId = "PON 1/1";
+        private final String clientCircuitId = "CIR-PON 1/1";
+
+        MockSubscriberAndDeviceInformation sub =
+                new MockSubscriberAndDeviceInformation(clientNasPortId, clientCtag,
+                        clientStag, clientNasPortId, clientCircuitId, null, null);
+        @Override
+        public SubscriberAndDeviceInformation get(String id) {
+
+                return  sub;
+
+        }
+
+        @Override
+        public void invalidateAll() {}
+        public void invalidateId(String id) {}
+        public SubscriberAndDeviceInformation getfromCache(String id) {
+            return null;
+        }
+    }
+    /**
+     * Mocks the DefaultPacketContext.
+     */
+    final class TestPacketContext extends DefaultPacketContext {
+
+        private TestPacketContext(long time, InboundPacket inPkt,
+                                  OutboundPacket outPkt, boolean block) {
+            super(time, inPkt, outPkt, block);
+        }
+
+        @Override
+        public void send() {
+            // We don't send anything out.
+        }
+    }
+
+    /**
+     * Sends an Ethernet packet to the process method of the Packet Processor.
+     *
+     * @param reply Ethernet packet
+     */
+    void sendPacket(Ethernet reply) {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(reply.serialize());
+        InboundPacket inPacket = new DefaultInboundPacket(connectPoint("1", 1),
+                                                          reply,
+                                                          byteBuffer);
+
+        PacketContext context = new TestPacketContext(127L, inPacket, null, false);
+        packetProcessor.process(context);
+    }
+
+    /**
+     * Constructs an Ethernet packet containing identification payload.
+     *
+     * @return Ethernet packet
+     */
+    Ethernet constructSupplicantIdentifyPacket(StateMachine stateMachine,
+                                                       byte type,
+                                                       byte id,
+                                                       Ethernet radiusChallenge)
+            throws Exception {
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(clientMac.toBytes());
+        eth.setSourceMACAddress(serverMac.toBytes());
+        eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
+        eth.setVlanID((short) 2);
+
+        String username = "testuser";
+        byte[] data = username.getBytes();
+
+
+        if (type == EAP.ATTR_MD5) {
+            String password = "testpassword";
+            EAPOL eapol = (EAPOL) radiusChallenge.getPayload();
+            EAP eap = (EAP) eapol.getPayload();
+
+            byte[] identifier = new byte[password.length() + eap.getData().length];
+
+            identifier[0] = stateMachine.challengeIdentifier();
+            System.arraycopy(password.getBytes(), 0, identifier, 1, password.length());
+            System.arraycopy(eap.getData(), 1, identifier, 1 + password.length(), 16);
+
+            MessageDigest md = MessageDigest.getInstance("MD5");
+            byte[] hash = md.digest(identifier);
+            data = new byte[17];
+            data[0] = (byte) 16;
+            System.arraycopy(hash, 0, data, 1, 16);
+        }
+        EAP eap = new EAP(EAP.RESPONSE, (byte) 1, type,
+                          data);
+        eap.setIdentifier(id);
+
+        // eapol header
+        EAPOL eapol = new EAPOL();
+        eapol.setEapolType(EAPOL.EAPOL_PACKET);
+        eapol.setPacketLength(eap.getLength());
+
+        // eap part
+        eapol.setPayload(eap);
+
+        eth.setPayload(eapol);
+        eth.setPad(true);
+        return eth;
+    }
+
+    /**
+     * Constructs an Ethernet packet containing a EAPOL_START Payload.
+     *
+     * @return Ethernet packet
+     */
+    Ethernet constructSupplicantStartPacket() {
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(clientMac.toBytes());
+        eth.setSourceMACAddress(serverMac.toBytes());
+        eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
+        eth.setVlanID((short) 2);
+
+        EAP eap = new EAP(EAPOL.EAPOL_START, (byte) 2, EAPOL.EAPOL_START, null);
+
+        // eapol header
+        EAPOL eapol = new EAPOL();
+        eapol.setEapolType(EAPOL.EAPOL_START);
+        eapol.setPacketLength(eap.getLength());
+
+        // eap part
+        eapol.setPayload(eap);
+
+        eth.setPayload(eapol);
+        eth.setPad(true);
+        return eth;
+    }
+
+    /**
+     * Checks the contents of a RADIUS packet being sent to the RADIUS server.
+     *
+     * @param radiusPacket packet to check
+     * @param code expected code
+     */
+    void checkRadiusPacket(AaaManager aaaManager, Ethernet radiusPacket, byte code) {
+
+        assertThat(radiusPacket.getSourceMAC(),
+                   is(MacAddress.valueOf(aaaManager.nasMacAddress)));
+        assertThat(radiusPacket.getDestinationMAC(), is(serverMac));
+
+        assertThat(radiusPacket.getPayload(), instanceOf(EAPOL.class));
+        EAPOL eapol = (EAPOL) radiusPacket.getPayload();
+        assertThat(eapol, notNullValue());
+
+        assertThat(eapol.getEapolType(), is(EAPOL.EAPOL_PACKET));
+        assertThat(eapol.getPayload(), instanceOf(EAP.class));
+        EAP eap = (EAP) eapol.getPayload();
+        assertThat(eap, notNullValue());
+
+        assertThat(eap.getCode(), is(code));
+    }
+}
diff --git a/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java b/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java
new file mode 100644
index 0000000..4053ff9
--- /dev/null
+++ b/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java
@@ -0,0 +1,300 @@
+/*
+ * 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.opencord.aaa.impl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.MacAddress;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class StateMachineTest {
+    StateMachine stateMachine = null;
+
+    @Before
+    public void setUp() {
+        System.out.println("Set Up.");
+        StateMachine.initializeMaps();
+        StateMachine.setDelegate(e -> { });
+        stateMachine = new StateMachine("session0");
+    }
+
+    @After
+    public void tearDown() {
+        System.out.println("Tear Down.");
+        StateMachine.destroyMaps();
+        stateMachine = null;
+    }
+
+    @Test
+    /**
+     * Test all the basic inputs from state to state: IDLE -> STARTED -> PENDING -> AUTHORIZED -> IDLE
+     */
+    public void basic() throws StateMachineException {
+        System.out.println("======= BASIC =======.");
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+    }
+
+    @Test
+    /**
+     * Test all inputs from an IDLE state (starting with the ones that are not impacting the current state)
+     */
+    public void testIdleState() throws StateMachineException {
+        System.out.println("======= IDLE STATE TEST =======.");
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+
+        stateMachine.denyAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+    }
+
+    @Test
+    /**
+     * Test all inputs from an STARTED state (starting with the ones that are not impacting the current state)
+     */
+    public void testStartedState() throws StateMachineException {
+        System.out.println("======= STARTED STATE TEST =======.");
+        stateMachine.start();
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.denyAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+    }
+
+    @Test
+    /**
+     * Test all inputs from a PENDING state (starting with the ones that are not impacting the current state).
+     * The next valid state for this test is AUTHORIZED
+     */
+    public void testPendingStateToAuthorized() throws StateMachineException {
+        System.out.println("======= PENDING STATE TEST (AUTHORIZED) =======.");
+        stateMachine.start();
+        stateMachine.requestAccess();
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+
+        stateMachine.denyAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+    }
+
+    @Test
+    /**
+     * Test all inputs from an PENDING state (starting with the ones that are not impacting the current state).
+     * The next valid state for this test is UNAUTHORIZED
+     */
+    public void testPendingStateToUnauthorized() throws StateMachineException {
+        System.out.println("======= PENDING STATE TEST (DENIED) =======.");
+        stateMachine.start();
+        stateMachine.requestAccess();
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.denyAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_UNAUTHORIZED);
+    }
+
+    @Test
+    /**
+     * Test all inputs from an AUTHORIZED state (starting with the ones that are not impacting the current state).
+     */
+    public void testAuthorizedState() throws StateMachineException {
+        System.out.println("======= AUTHORIZED STATE TEST =======.");
+        stateMachine.start();
+        stateMachine.requestAccess();
+        stateMachine.authorizeAccess();
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+
+        stateMachine.denyAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+    }
+
+    @Test
+    /**
+     * Test all inputs from an UNAUTHORIZED state (starting with the ones that are not impacting the current state).
+     */
+    public void testUnauthorizedState() throws StateMachineException {
+        System.out.println("======= UNAUTHORIZED STATE TEST =======.");
+        stateMachine.start();
+        stateMachine.requestAccess();
+        stateMachine.denyAccess();
+
+        stateMachine.start();
+        assertEquals(stateMachine.state(), StateMachine.STATE_STARTED);
+
+        stateMachine.requestAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_PENDING);
+
+        stateMachine.authorizeAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+
+        stateMachine.denyAccess();
+        assertEquals(stateMachine.state(), StateMachine.STATE_AUTHORIZED);
+
+        stateMachine.logoff();
+        assertEquals(stateMachine.state(), StateMachine.STATE_IDLE);
+    }
+
+    @Test
+    public void testSessionIdLookups() {
+        String sessionId1 = "session1";
+        String sessionId2 = "session2";
+        String sessionId3 = "session3";
+
+        StateMachine machine1ShouldBeNull =
+                StateMachine.lookupStateMachineBySessionId(sessionId1);
+        assertNull(machine1ShouldBeNull);
+        StateMachine machine2ShouldBeNull =
+                StateMachine.lookupStateMachineBySessionId(sessionId2);
+        assertNull(machine2ShouldBeNull);
+
+        StateMachine stateMachine1 = new StateMachine(sessionId1);
+        StateMachine stateMachine2 = new StateMachine(sessionId2);
+
+        assertEquals(stateMachine1,
+                     StateMachine.lookupStateMachineBySessionId(sessionId1));
+        assertEquals(stateMachine2,
+                     StateMachine.lookupStateMachineBySessionId(sessionId2));
+        assertNull(StateMachine.lookupStateMachineBySessionId(sessionId3));
+    }
+
+    @Test
+    public void testIdentifierLookups() throws StateMachineException {
+        String sessionId1 = "session1";
+        String sessionId2 = "session2";
+
+        StateMachine machine1ShouldBeNull =
+                StateMachine.lookupStateMachineById((byte) 1);
+        assertNull(machine1ShouldBeNull);
+        StateMachine machine2ShouldBeNull =
+                StateMachine.lookupStateMachineById((byte) 2);
+        assertNull(machine2ShouldBeNull);
+
+        StateMachine stateMachine1 = new StateMachine(sessionId1);
+        stateMachine1.start();
+        StateMachine stateMachine2 = new StateMachine(sessionId2);
+        stateMachine2.start();
+
+        assertEquals(stateMachine1,
+                     StateMachine.lookupStateMachineById(stateMachine1.identifier()));
+        assertEquals(stateMachine2,
+                     StateMachine.lookupStateMachineById(stateMachine2.identifier()));
+    }
+
+    @Test
+    /**
+     * Test state machine deletes
+     */
+    public void testStateMachineReset() throws StateMachineException {
+
+        int count = 256;
+
+        //StateMachine.initializeMaps();
+        StateMachine.lookupStateMachineById((byte) 1);
+
+        // Instantiate a bunch of state machines
+        for (int i = 0; i < count; i += 1) {
+            String mac = String.format("00:00:00:00:00:%02x", i);
+            StateMachine sm = new StateMachine(mac);
+            sm.start();
+            sm.setSupplicantAddress(MacAddress.valueOf(mac));
+        }
+
+        // Verify all state machines with a "even" MAC exist
+        for (int i = 0; i < count; i += 2) {
+            String mac = String.format("00:00:00:00:00:%02x", i);
+            assertNotNull(StateMachine.lookupStateMachineBySessionId(mac));
+        }
+
+        // Delete all state machines with a "even" MAC
+        for (int i = 0; i < count; i += 2) {
+            String mac = String.format("00:00:00:00:00:%02x", i);
+            StateMachine.deleteByMac(MacAddress.valueOf(mac));
+        }
+
+        // Verify all the delete state machines no long exist
+        for (int i = 0; i < count; i += 2) {
+            String mac = String.format("00:00:00:00:00:%02x", i);
+            assertNull(StateMachine.lookupStateMachineBySessionId(mac));
+        }
+    }
+}