Support RADIUS server outside of the ONOS network

Change-Id: I7e64faae6831467e084db878e02023d40fb33f07
diff --git a/src/main/java/org/onosproject/aaa/AAA.java b/src/main/java/org/onosproject/aaa/AAA.java
index 479ec7e..caa9800 100644
--- a/src/main/java/org/onosproject/aaa/AAA.java
+++ b/src/main/java/org/onosproject/aaa/AAA.java
@@ -15,10 +15,13 @@
  */
 package org.onosproject.aaa;
 
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.nio.ByteBuffer;
-import java.util.Optional;
-import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -31,19 +34,14 @@
 import org.onlab.packet.EthType;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IPv4;
-import org.onlab.packet.Ip4Address;
-import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.RADIUS;
 import org.onlab.packet.RADIUSAttribute;
 import org.onlab.packet.TpPort;
-import org.onlab.packet.UDP;
-import org.onlab.packet.VlanId;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.Host;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
@@ -53,7 +51,6 @@
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
-import org.onosproject.net.host.HostService;
 import org.onosproject.net.packet.DefaultOutboundPacket;
 import org.onosproject.net.packet.InboundPacket;
 import org.onosproject.net.packet.OutboundPacket;
@@ -63,6 +60,8 @@
 import org.onosproject.xosintegration.VoltTenantService;
 import org.slf4j.Logger;
 
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
 import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
 import static org.onosproject.net.packet.PacketPriority.CONTROL;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -85,10 +84,6 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PacketService packetService;
 
-    // end host information
-    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected HostService hostService;
-
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected VoltTenantService voltTenantService;
 
@@ -121,6 +116,12 @@
     // our unique identifier
     private ApplicationId appId;
 
+    // Socket used for UDP communications with RADIUS server
+    private DatagramSocket radiusSocket;
+
+    // Executor for RADIUS communication thread
+    private ExecutorService executor;
+
     // Configuration properties factory
     private final ConfigFactory factory =
             new ConfigFactory<ApplicationId, AAAConfig>(APP_SUBJECT_FACTORY,
@@ -184,7 +185,16 @@
 
         StateMachine.initializeMaps();
 
-        hostService.startMonitoringIp(IpAddress.valueOf(radiusIpAddress));
+        try {
+            radiusSocket = new DatagramSocket(radiusServerPort);
+        } catch (Exception ex) {
+            log.error("Can't open RADIUS socket", ex);
+        }
+
+        executor = Executors.newSingleThreadExecutor(
+                new ThreadFactoryBuilder()
+                        .setNameFormat("AAA-radius-%d").build());
+        executor.execute(radiusListener);
     }
 
     @Deactivate
@@ -195,6 +205,24 @@
         packetService.removeProcessor(processor);
         processor = null;
         StateMachine.destroyMaps();
+        radiusSocket.close();
+        executor.shutdownNow();
+    }
+
+    protected void sendRADIUSPacket(RADIUS radiusPacket) {
+
+        try {
+            final byte[] data = radiusPacket.serialize();
+            final DatagramSocket socket = radiusSocket;
+
+            DatagramPacket packet =
+                    new DatagramPacket(data, data.length,
+                                       radiusIpAddress, radiusServerPort);
+
+            socket.send(packet);
+        } catch (IOException e) {
+            log.info("Cannot send packet to RADIUS server", e);
+        }
     }
 
     /**
@@ -232,6 +260,19 @@
         packetService.cancelPackets(radSelector, CONTROL, appId);
     }
 
+    /**
+     * Send the ethernet packet to the supplicant.
+     *
+     * @param ethernetPkt  the ethernet packet
+     * @param connectPoint the connect point to send out
+     */
+    private void sendPacketToSupplicant(Ethernet ethernetPkt, ConnectPoint connectPoint) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
+        OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
+                                                          treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
+        packetService.emit(packet);
+    }
+
     // our handler defined as a private inner class
 
     /**
@@ -253,26 +294,11 @@
                     case EAPOL:
                         handleSupplicantPacket(context.inPacket());
                         break;
-                    case IPV4:
-                        IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
-                        Ip4Address srcIp = Ip4Address.valueOf(ipv4Packet.getSourceAddress());
-                        Ip4Address radiusIp4Address = Ip4Address.valueOf(radiusIpAddress);
-                        if (srcIp.equals(radiusIp4Address) && ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
-                            // TODO: check for port as well when it's configurable
-                            UDP udpPacket = (UDP) ipv4Packet.getPayload();
-
-                            byte[] datagram = udpPacket.getPayload().serialize();
-                            RADIUS radiusPacket;
-                            radiusPacket = RADIUS.deserializer().deserialize(datagram, 0, datagram.length);
-                            handleRadiusPacket(radiusPacket);
-                        }
-
-                        break;
                     default:
                         log.trace("Skipping Ethernet packet type {}",
                                   EthType.EtherType.lookup(ethPkt.getEtherType()));
                 }
-            } catch (DeserializationException | StateMachineException e) {
+            } catch (StateMachineException e) {
                 log.warn("Unable to process RADIUS packet:", e);
             }
         }
@@ -280,23 +306,26 @@
         /**
          * Creates and initializes common fields of a RADIUS packet.
          *
-         * @param identifier RADIUS identifier
+         * @param stateMachine state machine for the request
          * @param eapPacket  EAP packet
          * @return RADIUS packet
          */
-        private RADIUS getRadiusPayload(byte identifier, EAP eapPacket) {
+        private RADIUS getRadiusPayload(StateMachine stateMachine, byte identifier, EAP eapPacket) {
             RADIUS radiusPayload =
                     new RADIUS(RADIUS.RADIUS_CODE_ACCESS_REQUEST,
                                eapPacket.getIdentifier());
+
+            // set Request Authenticator in StateMachine
+            stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
+
             radiusPayload.setIdentifier(identifier);
             radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME,
-                                       eapPacket.getData());
+                                       stateMachine.username());
 
             radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
                                        AAA.this.nasIpAddress.getAddress());
 
             radiusPayload.encapsulateMessage(eapPacket);
-            radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
 
             return radiusPayload;
         }
@@ -329,13 +358,13 @@
 
                     //send an EAP Request/Identify to the supplicant
                     EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.identifier(), EAP.ATTR_IDENTITY, null);
-                    Ethernet eth = buildEapolResponse(srcMAC, MacAddress.valueOf(1L),
+                    Ethernet eth = buildEapolResponse(srcMAC, MacAddress.valueOf(nasMacAddress),
                                                       ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
                                                       eapPayload);
                     stateMachine.setSupplicantAddress(srcMAC);
                     stateMachine.setVlanId(ethPkt.getVlanID());
 
-                    this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+                    sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
 
                     break;
                 case EAPOL.EAPOL_PACKET:
@@ -350,11 +379,10 @@
                             // request id access to RADIUS
                             stateMachine.setUsername(eapPacket.getData());
 
-                            radiusPayload = getRadiusPayload(stateMachine.identifier(), eapPacket);
+                            radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
+                            radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
 
-                            // set Request Authenticator in StateMachine
-                            stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
-                            sendRadiusMessage(radiusPayload);
+                            sendRADIUSPacket(radiusPayload);
 
                             // change the state to "PENDING"
                             stateMachine.requestAccess();
@@ -365,22 +393,27 @@
                             // machine.
                             if (eapPacket.getIdentifier() == stateMachine.challengeIdentifier()) {
                                 //send the RADIUS challenge response
-                                radiusPayload = getRadiusPayload(stateMachine.challengeIdentifier(), eapPacket);
+                                radiusPayload =
+                                        getRadiusPayload(stateMachine,
+                                                         stateMachine.identifier(),
+                                                         eapPacket);
 
                                 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
                                                            stateMachine.challengeState());
-                                sendRadiusMessage(radiusPayload);
+                                radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
+                                sendRADIUSPacket(radiusPayload);
                             }
                             break;
                         case EAP.ATTR_TLS:
                             // request id access to RADIUS
-                            radiusPayload = getRadiusPayload(stateMachine.identifier(), eapPacket);
+                            radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
 
                             radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
                                                        stateMachine.challengeState());
                             stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
 
-                            sendRadiusMessage(radiusPayload);
+                            sendRADIUSPacket(radiusPayload);
+                            radiusPayload.addMessageAuthenticator(AAA.this.radiusSecret);
                             // TODO: this gets called on every fragment, should only be called at TLS-Start
                             stateMachine.requestAccess();
 
@@ -392,14 +425,18 @@
                 default:
                     log.trace("Skipping EAPOL message {}", eapol.getEapolType());
             }
+
         }
+    }
+
+    class RadiusListener implements Runnable {
 
         /**
          * Handles RADIUS packets.
          *
          * @param radiusPacket RADIUS packet coming from the RADIUS server.
          */
-        private void handleRadiusPacket(RADIUS radiusPacket) throws StateMachineException {
+        protected void handleRadiusPacket(RADIUS radiusPacket) throws StateMachineException {
             StateMachine stateMachine = StateMachine.lookupStateMachineById(radiusPacket.getIdentifier());
             if (stateMachine == null) {
                 log.error("Invalid session identifier, exiting...");
@@ -410,13 +447,16 @@
             Ethernet eth;
             switch (radiusPacket.getCode()) {
                 case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE:
-                    byte[] challengeState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE).getValue();
+                    byte[] challengeState =
+                            radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE).getValue();
                     eapPayload = radiusPacket.decapsulateMessage();
                     stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
                     eth = buildEapolResponse(stateMachine.supplicantAddress(),
-                                             MacAddress.valueOf(1L), stateMachine.vlanId(), EAPOL.EAPOL_PACKET,
+                                             MacAddress.valueOf(nasMacAddress),
+                                             stateMachine.vlanId(),
+                                             EAPOL.EAPOL_PACKET,
                                              eapPayload);
-                    this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+                    sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
                     break;
                 case RADIUS.RADIUS_CODE_ACCESS_ACCEPT:
                     //send an EAPOL - Success to the supplicant.
@@ -425,9 +465,11 @@
                     eapPayload = new EAP();
                     eapPayload = (EAP) eapPayload.deserialize(eapMessage, 0, eapMessage.length);
                     eth = buildEapolResponse(stateMachine.supplicantAddress(),
-                                             MacAddress.valueOf(1L), stateMachine.vlanId(), EAPOL.EAPOL_PACKET,
+                                             MacAddress.valueOf(nasMacAddress),
+                                             stateMachine.vlanId(),
+                                             EAPOL.EAPOL_PACKET,
                                              eapPayload);
-                    this.sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+                    sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
 
                     stateMachine.authorizeAccess();
                     break;
@@ -439,59 +481,43 @@
             }
         }
 
-        private void sendRadiusMessage(RADIUS radiusMessage) {
-            Set<Host> hosts = hostService.getHostsByIp(IpAddress.valueOf(radiusIpAddress));
-            Optional<Host> odst = hosts.stream().filter(h -> h.vlan().toShort() == VlanId.UNTAGGED).findFirst();
 
-            Host dst;
-            if (!odst.isPresent()) {
-                log.info("Radius server {} is not present", radiusIpAddress);
-                return;
-            } else {
-                dst = odst.get();
+        @Override
+        public void run() {
+            boolean done = false;
+            int packetNumber = 1;
+
+            log.info("UDP listener thread starting up");
+            RADIUS inboundRadiusPacket;
+            while (!done) {
+                try {
+                    DatagramPacket inboundBasePacket = new DatagramPacket(new byte[1000], 1000);
+                    DatagramSocket socket = radiusSocket;
+                    socket.receive(inboundBasePacket);
+                    log.info("Packet #{} received", packetNumber++);
+                    try {
+                        inboundRadiusPacket =
+                                RADIUS.deserializer()
+                                        .deserialize(inboundBasePacket.getData(),
+                                                     0,
+                                                     inboundBasePacket.getLength());
+                        handleRadiusPacket(inboundRadiusPacket);
+                    } catch (DeserializationException dex) {
+                        log.error("Cannot deserialize packet", dex);
+                    } catch (StateMachineException sme) {
+                        log.error("Illegal state machine operation", sme);
+                    }
+
+                } catch (IOException e) {
+                    log.info("Socket was closed, exiting listener thread");
+                    done = true;
+                }
             }
-
-            UDP udp = new UDP();
-            IPv4 ip4Packet = new IPv4();
-            Ethernet ethPkt = new Ethernet();
-            radiusMessage.setParent(udp);
-            udp.setDestinationPort(radiusServerPort);
-            udp.setSourcePort(radiusServerPort);
-            udp.setPayload(radiusMessage);
-            udp.setParent(ip4Packet);
-            ip4Packet.setSourceAddress(AAA.this.nasIpAddress.getHostAddress());
-            ip4Packet.setDestinationAddress(AAA.this.radiusIpAddress.getHostAddress());
-            ip4Packet.setProtocol(IPv4.PROTOCOL_UDP);
-            ip4Packet.setPayload(udp);
-            ip4Packet.setParent(ethPkt);
-            ethPkt.setDestinationMACAddress(radiusMacAddress);
-            ethPkt.setSourceMACAddress(nasMacAddress);
-            ethPkt.setEtherType(Ethernet.TYPE_IPV4);
-            ethPkt.setPayload(ip4Packet);
-
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder()
-                    .setOutput(PortNumber.portNumber(radiusPort)).build();
-            OutboundPacket packet = new DefaultOutboundPacket(DeviceId.deviceId(radiusSwitch),
-                                                              treatment, ByteBuffer.wrap(ethPkt.serialize()));
-            packetService.emit(packet);
-
         }
-
-        /**
-         * Send the ethernet packet to the supplicant.
-         *
-         * @param ethernetPkt  the ethernet packet
-         * @param connectPoint the connect point to send out
-         */
-        private void sendPacketToSupplicant(Ethernet ethernetPkt, ConnectPoint connectPoint) {
-            TrafficTreatment treatment = DefaultTrafficTreatment.builder().setOutput(connectPoint.port()).build();
-            OutboundPacket packet = new DefaultOutboundPacket(connectPoint.deviceId(),
-                                                              treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
-            packetService.emit(packet);
-        }
-
     }
 
+    RadiusListener radiusListener = new RadiusListener();
+
     private class InternalConfigListener implements NetworkConfigListener {
 
         /**
diff --git a/src/main/java/org/onosproject/aaa/AAAConfig.java b/src/main/java/org/onosproject/aaa/AAAConfig.java
index c18d2bf..73be769 100644
--- a/src/main/java/org/onosproject/aaa/AAAConfig.java
+++ b/src/main/java/org/onosproject/aaa/AAAConfig.java
@@ -37,13 +37,13 @@
     private static final String RADIUS_PORT = "radiusPort";
 
     // RADIUS server IP address
-    protected static final String DEFAULT_RADIUS_IP = "192.168.1.10";
+    protected static final String DEFAULT_RADIUS_IP = "10.128.10.4";
 
     // RADIUS MAC address
     protected static final String DEFAULT_RADIUS_MAC = "00:00:00:00:01:10";
 
     // NAS IP address
-    protected static final String DEFAULT_NAS_IP = "192.168.1.11";
+    protected static final String DEFAULT_NAS_IP = "10.128.9.244";
 
     // NAS MAC address
     protected static final String DEFAULT_NAS_MAC = "00:00:00:00:10:01";
diff --git a/src/test/java/org/onosproject/aaa/AAAIntegrationTest.java b/src/test/java/org/onosproject/aaa/AAAIntegrationTest.java
new file mode 100644
index 0000000..fb513ce
--- /dev/null
+++ b/src/test/java/org/onosproject/aaa/AAAIntegrationTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.aaa;
+
+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 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 AAA 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 AAA();
+        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/src/test/java/org/onosproject/aaa/AAATest.java b/src/test/java/org/onosproject/aaa/AAATest.java
index 75a6033..860a7db 100644
--- a/src/test/java/org/onosproject/aaa/AAATest.java
+++ b/src/test/java/org/onosproject/aaa/AAATest.java
@@ -15,164 +15,61 @@
  */
 package org.onosproject.aaa;
 
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
-import org.onlab.packet.Data;
+import org.onlab.packet.BasePacket;
 import org.onlab.packet.DeserializationException;
 import org.onlab.packet.EAP;
-import org.onlab.packet.EAPOL;
-import org.onlab.packet.EthType;
 import org.onlab.packet.Ethernet;
-import org.onlab.packet.IPv4;
 import org.onlab.packet.IpAddress;
-import org.onlab.packet.MacAddress;
 import org.onlab.packet.RADIUS;
 import org.onlab.packet.RADIUSAttribute;
-import org.onlab.packet.UDP;
-import org.onlab.packet.VlanId;
 import org.onosproject.core.CoreServiceAdapter;
-import org.onosproject.net.Annotations;
-import org.onosproject.net.Host;
-import org.onosproject.net.HostId;
-import org.onosproject.net.HostLocation;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.config.NetworkConfigRegistryAdapter;
-import org.onosproject.net.host.HostServiceAdapter;
-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.onosproject.net.provider.ProviderId;
 
 import com.google.common.base.Charsets;
-import com.google.common.collect.ImmutableSet;
 
-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;
 
 /**
  * Set of tests of the ONOS application component.
  */
-public class AAATest {
+public class AAATest extends AAATestBase {
 
-    MacAddress clientMac = MacAddress.valueOf("1a:1a:1a:1a:1a:1a");
-    MacAddress serverMac = MacAddress.valueOf("2a:2a:2a:2a:2a:2a");
+    static final String BAD_IP_ADDRESS = "198.51.100.0";
 
-    PacketProcessor packetProcessor;
     private AAA aaa;
-    List<Ethernet> savedPackets = new LinkedList<>();
 
-    /**
-     * Saves the given packet onto the saved packets list.
-     *
-     * @param eth packet to save
-     */
-    private void savePacket(Ethernet eth) {
-        savedPackets.add(eth);
+    class AAAWithoutRadiusServer extends AAA {
+        protected void sendRADIUSPacket(RADIUS radiusPacket) {
+            savePacket(radiusPacket);
+        }
     }
 
     /**
-     * Keeps a reference to the PacketProcessor and saves the OutboundPackets.
+     * Mocks the AAAConfig class to force usage of an unroutable address for the
+     * RADIUS server.
      */
-    private class MockPacketService extends PacketServiceAdapter {
-
+    static class MockAAAConfig extends AAAConfig {
         @Override
-        public void addProcessor(PacketProcessor processor, int priority) {
-            packetProcessor = processor;
-        }
-
-        @Override
-        public void emit(OutboundPacket packet) {
+        public InetAddress radiusIp() {
             try {
-                Ethernet eth = Ethernet.deserializer().deserialize(packet.data().array(),
-                                                                   0, packet.data().array().length);
-                savePacket(eth);
-            } catch (Exception e) {
-                fail(e.getMessage());
+                return InetAddress.getByName(BAD_IP_ADDRESS);
+            } catch (UnknownHostException ex) {
+                // can't happen
+                throw new IllegalStateException(ex);
             }
         }
     }
 
     /**
-     * Mocks the DefaultPacketContext.
-     */
-    private 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.
-        }
-    }
-
-    /**
-     * Mocks a host to allow locating the Radius server.
-     */
-    private static final class MockHost implements Host {
-        @Override
-        public HostId id() {
-            return null;
-        }
-
-        @Override
-        public MacAddress mac() {
-            return null;
-        }
-
-        @Override
-        public VlanId vlan() {
-            return VlanId.vlanId(VlanId.UNTAGGED);
-        }
-
-        @Override
-        public Set<IpAddress> ipAddresses() {
-            return null;
-        }
-
-        @Override
-        public HostLocation location() {
-            return null;
-        }
-
-        @Override
-        public Annotations annotations() {
-            return null;
-        }
-
-        @Override
-        public ProviderId providerId() {
-            return null;
-        }
-    }
-
-    /**
-     * Mocks the Host service.
-     */
-    private static final class MockHostService extends HostServiceAdapter {
-        @Override
-        public Set<Host> getHostsByIp(IpAddress ip) {
-            return ImmutableSet.of(new MockHost());
-        }
-    }
-
-    /**
      * Mocks the network config registry.
      */
     @SuppressWarnings("unchecked")
@@ -180,83 +77,12 @@
             extends NetworkConfigRegistryAdapter {
         @Override
         public <S, C extends Config<S>> C getConfig(S subject, Class<C> configClass) {
-            return (C) new AAAConfig();
+            AAAConfig aaaConfig = new MockAAAConfig();
+            return (C) aaaConfig;
         }
     }
 
     /**
-     * Sends an Ethernet packet to the process method of the Packet Processor.
-     *
-     * @param reply Ethernet packet
-     */
-    private 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 a EAPOL_START Payload.
-     *
-     * @return Ethernet packet
-     */
-    private 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) 1, 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;
-    }
-
-    /**
-     * Constructs an Ethernet packet containing identification payload.
-     *
-     * @return Ethernet packet
-     */
-    private Ethernet constructSupplicantIdentifyPacket(byte type) {
-        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 = "user";
-        EAP eap = new EAP(EAP.REQUEST, (byte) 1, type,
-                          username.getBytes(Charsets.US_ASCII));
-        eap.setIdentifier((byte) 1);
-
-        // 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 RADIUS challenge
      * packet.
      *
@@ -264,18 +90,9 @@
      * @param challengeType type to use in challenge packet
      * @return Ethernet packet
      */
-    private Ethernet constructRADIUSCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
-        Ethernet eth = new Ethernet();
-        eth.setDestinationMACAddress(clientMac.toBytes());
-        eth.setSourceMACAddress(serverMac.toBytes());
-        eth.setEtherType(EthType.EtherType.IPV4.ethType().toShort());
-        eth.setVlanID((short) 2);
+    private RADIUS constructRADIUSCodeAccessChallengePacket(byte challengeCode, byte challengeType) {
 
-        IPv4 ipv4 = new IPv4();
-        ipv4.setProtocol(IPv4.PROTOCOL_UDP);
-        ipv4.setSourceAddress(aaa.radiusIpAddress.getHostAddress());
-
-        String challenge = "1234";
+        String challenge = "12345678901234567";
 
         EAP eap = new EAP(challengeType, (byte) 1, challengeType,
                           challenge.getBytes(Charsets.US_ASCII));
@@ -291,13 +108,7 @@
         radius.setAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE,
                             eap.serialize());
 
-        UDP udp = new UDP();
-        udp.setPayload(radius);
-        ipv4.setPayload(udp);
-
-        eth.setPayload(ipv4);
-        eth.setPad(true);
-        return eth;
+        return radius;
     }
 
     /**
@@ -305,11 +116,10 @@
      */
     @Before
     public void setUp() {
-        aaa = new AAA();
+        aaa = new AAAWithoutRadiusServer();
         aaa.netCfgService = new TestNetworkConfigRegistry();
         aaa.coreService = new CoreServiceAdapter();
         aaa.packetService = new MockPacketService();
-        aaa.hostService = new MockHostService();
         aaa.activate();
     }
 
@@ -324,60 +134,16 @@
     /**
      * Extracts the RADIUS packet from a packet sent by the supplicant.
      *
-     * @param supplicantPacket packet sent by the supplicant
-     * @return RADIUS packet
+     * @param radius RADIUS packet sent by the supplicant
      * @throws DeserializationException if deserialization of the packet contents
      *         fails.
      */
-    private RADIUS checkAndFetchRADIUSPacketFromSupplicant(Ethernet supplicantPacket)
+    private void checkRADIUSPacketFromSupplicant(RADIUS radius)
             throws DeserializationException {
-        assertThat(supplicantPacket, notNullValue());
-        assertThat(supplicantPacket.getVlanID(), is(VlanId.UNTAGGED));
-        assertThat(supplicantPacket.getSourceMAC().toString(), is(aaa.nasMacAddress));
-        assertThat(supplicantPacket.getDestinationMAC().toString(), is(aaa.radiusMacAddress));
-
-        assertThat(supplicantPacket.getPayload(), instanceOf(IPv4.class));
-        IPv4 ipv4 = (IPv4) supplicantPacket.getPayload();
-        assertThat(ipv4, notNullValue());
-        assertThat(IpAddress.valueOf(ipv4.getSourceAddress()).toString(),
-                   is(aaa.nasIpAddress.getHostAddress()));
-        assertThat(IpAddress.valueOf(ipv4.getDestinationAddress()).toString(),
-                   is(aaa.radiusIpAddress.getHostAddress()));
-
-        assertThat(ipv4.getPayload(), instanceOf(UDP.class));
-        UDP udp = (UDP) ipv4.getPayload();
-        assertThat(udp, notNullValue());
-
-        assertThat(udp.getPayload(), instanceOf(Data.class));
-        Data data = (Data) udp.getPayload();
-        RADIUS radius = RADIUS.deserializer()
-                .deserialize(data.getData(), 0, data.getData().length);
         assertThat(radius, notNullValue());
-        return radius;
-    }
 
-    /**
-     * Checks the contents of a RADIUS packet being sent to the RADIUS server.
-     *
-     * @param radiusPacket packet to check
-     * @param code expected code
-     */
-    private void checkRadiusPacket(Ethernet radiusPacket, byte code) {
-        assertThat(radiusPacket.getVlanID(), is((short) 2));
-
-        // TODO: These address values seem wrong, but are produced by the current AAA implementation
-        assertThat(radiusPacket.getSourceMAC(), is(MacAddress.valueOf(1L)));
-        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();
+        EAP eap = radius.decapsulateMessage();
         assertThat(eap, notNullValue());
-        assertThat(eap.getCode(), is(code));
     }
 
     /**
@@ -387,11 +153,10 @@
      * @param index index into sent packets array
      * @return packet
      */
-    private Ethernet fetchPacket(int index) {
-        assertThat(savedPackets.size(), is(index + 1));
-        Ethernet eth = savedPackets.get(index);
-        assertThat(eth, notNullValue());
-        return eth;
+    private BasePacket fetchPacket(int index) {
+        BasePacket packet = savedPackets.get(index);
+        assertThat(packet, notNullValue());
+        return packet;
     }
 
     /**
@@ -400,61 +165,64 @@
      * @throws DeserializationException if packed deserialization fails.
      */
     @Test
-    public void testAuthentication()  throws DeserializationException {
-
-        // Our session id will be the device ID ("of:1") with the port ("1") concatenated
-        String sessionId = "of:11";
+    public void testAuthentication()  throws Exception {
 
         //  (1) Supplicant start up
 
         Ethernet startPacket = constructSupplicantStartPacket();
         sendPacket(startPacket);
 
-        Ethernet responsePacket = fetchPacket(0);
-        checkRadiusPacket(responsePacket, EAP.ATTR_IDENTITY);
+        Ethernet responsePacket = (Ethernet) fetchPacket(0);
+        checkRadiusPacket(aaa, responsePacket, EAP.ATTR_IDENTITY);
 
         //  (2) Supplicant identify
 
-        Ethernet identifyPacket = constructSupplicantIdentifyPacket(EAP.ATTR_IDENTITY);
+        Ethernet identifyPacket = constructSupplicantIdentifyPacket(null, EAP.ATTR_IDENTITY, (byte) 1, null);
         sendPacket(identifyPacket);
 
-        Ethernet radiusIdentifyPacket = fetchPacket(1);
+        RADIUS radiusIdentifyPacket = (RADIUS) fetchPacket(1);
 
-        RADIUS radiusAccessRequest = checkAndFetchRADIUSPacketFromSupplicant(radiusIdentifyPacket);
-        assertThat(radiusAccessRequest, notNullValue());
-        assertThat(radiusAccessRequest.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
-        assertThat(new String(radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_USERNAME).getValue()),
-                   is("user"));
+        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,
-                                  radiusAccessRequest.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
+                                  radiusIdentifyPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP)
                                           .getValue());
         assertThat(nasIp.toString(), is(aaa.nasIpAddress.getHostAddress()));
 
         //  State machine should have been created by now
 
         StateMachine stateMachine =
-                StateMachine.lookupStateMachineBySessionId(sessionId);
+                StateMachine.lookupStateMachineBySessionId(SESSION_ID);
         assertThat(stateMachine, notNullValue());
         assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
 
         // (3) RADIUS MD5 challenge
 
-        Ethernet radiusCodeAccessChallengePacket =
+        RADIUS radiusCodeAccessChallengePacket =
                 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_MD5);
-        sendPacket(radiusCodeAccessChallengePacket);
+        aaa.radiusListener.handleRadiusPacket(radiusCodeAccessChallengePacket);
 
-        Ethernet radiusChallengeMD5Packet = fetchPacket(2);
-        checkRadiusPacket(radiusChallengeMD5Packet, EAP.ATTR_MD5);
+        Ethernet radiusChallengeMD5Packet = (Ethernet) fetchPacket(2);
+        checkRadiusPacket(aaa, radiusChallengeMD5Packet, EAP.ATTR_MD5);
 
         // (4) Supplicant MD5 response
 
-        Ethernet md5RadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_MD5);
+        Ethernet md5RadiusPacket =
+                constructSupplicantIdentifyPacket(stateMachine,
+                                                  EAP.ATTR_MD5,
+                                                  stateMachine.challengeIdentifier(),
+                                                  radiusChallengeMD5Packet);
         sendPacket(md5RadiusPacket);
-        Ethernet supplicantMD5ResponsePacket = fetchPacket(3);
-        RADIUS responseMd5RadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantMD5ResponsePacket);
-        assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 1));
+
+        RADIUS responseMd5RadiusPacket = (RADIUS) fetchPacket(3);
+
+        checkRADIUSPacketFromSupplicant(responseMd5RadiusPacket);
+        assertThat(responseMd5RadiusPacket.getIdentifier(), is((byte) 0));
         assertThat(responseMd5RadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
 
         //  State machine should be in pending state
@@ -462,37 +230,20 @@
         assertThat(stateMachine, notNullValue());
         assertThat(stateMachine.state(), is(StateMachine.STATE_PENDING));
 
-        // (5) RADIUS TLS Challenge
+        // (5) RADIUS Success
 
-        Ethernet radiusCodeAccessChallengeTLSPacket =
-                constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_CHALLENGE, EAP.ATTR_TLS);
-        sendPacket(radiusCodeAccessChallengeTLSPacket);
-
-        Ethernet radiusChallengeTLSPacket = fetchPacket(4);
-        checkRadiusPacket(radiusChallengeTLSPacket, EAP.ATTR_TLS);
-
-        // (6) Supplicant TLS response
-
-        Ethernet tlsRadiusPacket = constructSupplicantIdentifyPacket(EAP.ATTR_TLS);
-        sendPacket(tlsRadiusPacket);
-        Ethernet supplicantTLSResponsePacket = fetchPacket(5);
-        RADIUS responseTLSRadiusPacket = checkAndFetchRADIUSPacketFromSupplicant(supplicantTLSResponsePacket);
-        assertThat(responseTLSRadiusPacket.getIdentifier(), is((byte) 0));
-        assertThat(responseTLSRadiusPacket.getCode(), is(RADIUS.RADIUS_CODE_ACCESS_REQUEST));
-
-        // (7) RADIUS Success
-
-        Ethernet successPacket =
+        RADIUS successPacket =
                 constructRADIUSCodeAccessChallengePacket(RADIUS.RADIUS_CODE_ACCESS_ACCEPT, EAP.SUCCESS);
-        sendPacket(successPacket);
-        Ethernet supplicantSuccessPacket = fetchPacket(6);
+        aaa.radiusListener.handleRadiusPacket((successPacket));
+        Ethernet supplicantSuccessPacket = (Ethernet) fetchPacket(4);
 
-        checkRadiusPacket(supplicantSuccessPacket, EAP.SUCCESS);
+        checkRadiusPacket(aaa, supplicantSuccessPacket, EAP.SUCCESS);
 
         //  State machine should be in authorized state
 
         assertThat(stateMachine, notNullValue());
         assertThat(stateMachine.state(), is(StateMachine.STATE_AUTHORIZED));
+
     }
 
     /**
@@ -502,7 +253,7 @@
     public void testConfig() {
         assertThat(aaa.nasIpAddress.getHostAddress(), is(AAAConfig.DEFAULT_NAS_IP));
         assertThat(aaa.nasMacAddress, is(AAAConfig.DEFAULT_NAS_MAC));
-        assertThat(aaa.radiusIpAddress.getHostAddress(), is(AAAConfig.DEFAULT_RADIUS_IP));
+        assertThat(aaa.radiusIpAddress.getHostAddress(), is(BAD_IP_ADDRESS));
         assertThat(aaa.radiusMacAddress, is(AAAConfig.DEFAULT_RADIUS_MAC));
     }
 }
diff --git a/src/test/java/org/onosproject/aaa/AAATestBase.java b/src/test/java/org/onosproject/aaa/AAATestBase.java
new file mode 100644
index 0000000..dffcba2
--- /dev/null
+++ b/src/test/java/org/onosproject/aaa/AAATestBase.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.aaa;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.util.LinkedList;
+import java.util.List;
+
+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.MacAddress;
+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 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 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(AAA aaa, Ethernet radiusPacket, byte code) {
+
+        assertThat(radiusPacket.getSourceMAC(),
+                   is(MacAddress.valueOf(aaa.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/src/test/java/org/onosproject/aaa/StateMachineTest.java b/src/test/java/org/onosproject/aaa/StateMachineTest.java
index 04837e8..1838c63 100644
--- a/src/test/java/org/onosproject/aaa/StateMachineTest.java
+++ b/src/test/java/org/onosproject/aaa/StateMachineTest.java
@@ -21,7 +21,9 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import static org.junit.Assert.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 
 public class StateMachineTest {