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

Change-Id: Ib81650be0695258bdd1f4cd6131f2206760e0da6
diff --git a/app/src/main/java/org/opencord/aaa/impl/AaaManager.java b/app/src/main/java/org/opencord/aaa/impl/AaaManager.java
new file mode 100755
index 0000000..3e75f17
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/AaaManager.java
@@ -0,0 +1,689 @@
+/*
+ * 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 static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+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.MacAddress;
+import org.onlab.packet.RADIUS;
+import org.onlab.packet.RADIUSAttribute;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.ConfigFactory;
+import org.onosproject.net.config.NetworkConfigEvent;
+import org.onosproject.net.config.NetworkConfigListener;
+import org.onosproject.net.config.NetworkConfigRegistry;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+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.PacketService;
+import org.opencord.aaa.AaaConfig;
+import org.opencord.aaa.AuthenticationEvent;
+import org.opencord.aaa.AuthenticationEventListener;
+import org.opencord.aaa.AuthenticationService;
+import org.opencord.aaa.RadiusCommunicator;
+import org.opencord.aaa.StateMachineDelegate;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.osgi.service.component.annotations.Activate;
+import org.slf4j.Logger;
+
+/**
+ * AAA application for ONOS.
+ */
+@Service
+@Component(immediate = true)
+public class AaaManager
+        extends AbstractListenerManager<AuthenticationEvent, AuthenticationEventListener>
+        implements AuthenticationService {
+
+    private static final String APP_NAME = "org.opencord.aaa";
+
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected CoreService coreService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected NetworkConfigRegistry netCfgService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected SadisService sadisService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
+    protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
+
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+
+    // NAS IP address
+    protected InetAddress nasIpAddress;
+
+    // self MAC address
+    protected String nasMacAddress;
+
+    // Parsed RADIUS server addresses
+    protected InetAddress radiusIpAddress;
+
+    // MAC address of RADIUS server or net hop router
+    protected String radiusMacAddress;
+
+    // RADIUS server secret
+    protected String radiusSecret;
+
+    // bindings
+    protected CustomizationInfo customInfo;
+
+    // our application-specific event handler
+    private ReactivePacketProcessor processor = new ReactivePacketProcessor();
+
+    // our unique identifier
+    private ApplicationId appId;
+
+    // Setup specific customization/attributes on the RADIUS packets
+    PacketCustomizer pktCustomizer;
+
+    // packet customizer to use
+    private String customizer;
+
+    // Type of connection to use to communicate with Radius server, options are
+    // "socket" or "packet_out"
+    private String radiusConnectionType;
+
+    // Object for the specific type of communication with the RADIUS
+    // server, socket based or packet_out based
+    RadiusCommunicator impl = null;
+
+    // latest configuration
+    AaaConfig newCfg;
+
+    // Configuration properties factory
+    private final ConfigFactory factory =
+            new ConfigFactory<ApplicationId, AaaConfig>(APP_SUBJECT_FACTORY,
+                                                         AaaConfig.class,
+                                                         "AAA") {
+                @Override
+                public AaaConfig createConfig() {
+                    return new AaaConfig();
+                }
+            };
+
+    // Listener for config changes
+    private final InternalConfigListener cfgListener = new InternalConfigListener();
+
+    private StateMachineDelegate delegate = new InternalStateMachineDelegate();
+
+    /**
+     * Builds an EAPOL packet based on the given parameters.
+     *
+     * @param dstMac    destination MAC address
+     * @param srcMac    source MAC address
+     * @param vlan      vlan identifier
+     * @param eapolType EAPOL type
+     * @param eap       EAP payload
+     * @return Ethernet frame
+     */
+    private static Ethernet buildEapolResponse(MacAddress dstMac, MacAddress srcMac,
+                                               short vlan, byte eapolType, EAP eap, byte priorityCode) {
+
+        Ethernet eth = new Ethernet();
+        eth.setDestinationMACAddress(dstMac.toBytes());
+        eth.setSourceMACAddress(srcMac.toBytes());
+        eth.setEtherType(EthType.EtherType.EAPOL.ethType().toShort());
+        if (vlan != Ethernet.VLAN_UNTAGGED) {
+            eth.setVlanID(vlan);
+            eth.setPriorityCode(priorityCode);
+        }
+        //eapol header
+        EAPOL eapol = new EAPOL();
+        eapol.setEapolType(eapolType);
+        eapol.setPacketLength(eap.getLength());
+
+        //eap part
+        eapol.setPayload(eap);
+
+        eth.setPayload(eapol);
+        eth.setPad(true);
+        return eth;
+    }
+
+    @Activate
+    public void activate() {
+        appId = coreService.registerApplication(APP_NAME);
+        eventDispatcher.addSink(AuthenticationEvent.class, listenerRegistry);
+        netCfgService.addListener(cfgListener);
+        netCfgService.registerConfigFactory(factory);
+        subsService = sadisService.getSubscriberInfoService();
+        customInfo = new CustomizationInfo(subsService, deviceService);
+        cfgListener.reconfigureNetwork(netCfgService.getConfig(appId, AaaConfig.class));
+        log.info("Starting with config {} {}", this, newCfg);
+
+        configureRadiusCommunication();
+
+        // register our event handler
+        packetService.addProcessor(processor, PacketProcessor.director(2));
+
+
+        StateMachine.initializeMaps();
+        StateMachine.setDelegate(delegate);
+
+        impl.initializeLocalState(newCfg);
+
+        impl.requestIntercepts();
+
+        deviceService.addListener(deviceListener);
+
+        log.info("Started");
+    }
+
+
+    @Deactivate
+    public void deactivate() {
+        impl.withdrawIntercepts();
+        packetService.removeProcessor(processor);
+        netCfgService.removeListener(cfgListener);
+        StateMachine.unsetDelegate(delegate);
+        StateMachine.destroyMaps();
+        impl.deactivate();
+        deviceService.removeListener(deviceListener);
+        eventDispatcher.removeSink(AuthenticationEvent.class);
+        log.info("Stopped");
+    }
+
+    private void configureRadiusCommunication() {
+        if (radiusConnectionType.toLowerCase().equals("socket")) {
+            impl = new SocketBasedRadiusCommunicator(appId, packetService, this);
+        } else {
+            impl = new PortBasedRadiusCommunicator(appId, packetService, mastershipService,
+                    deviceService, subsService, pktCustomizer, this);
+        }
+    }
+
+    private void configurePacketCustomizer() {
+        switch (customizer.toLowerCase()) {
+            case "sample":
+                pktCustomizer = new SamplePacketCustomizer(customInfo);
+                log.info("Created SamplePacketCustomizer");
+                break;
+            case "att":
+                pktCustomizer = new AttPacketCustomizer(customInfo);
+                log.info("Created AttPacketCustomizer");
+                break;
+            default:
+                pktCustomizer = new PacketCustomizer(customInfo);
+                log.info("Created default PacketCustomizer");
+                break;
+        }
+    }
+
+    /**
+     * Send RADIUS packet to the RADIUS server.
+     *
+     * @param radiusPacket RADIUS packet to be sent to server.
+     * @param inPkt        Incoming EAPOL packet
+     */
+    protected void sendRadiusPacket(RADIUS radiusPacket, InboundPacket inPkt) {
+        impl.sendRadiusPacket(radiusPacket, inPkt);
+    }
+
+    /**
+     * Handles RADIUS packets.
+     *
+     * @param radiusPacket RADIUS packet coming from the RADIUS server.
+     * @throws StateMachineException if an illegal state transition is triggered
+     * @throws DeserializationException if packet deserialization fails
+     */
+    public void handleRadiusPacket(RADIUS radiusPacket)
+            throws StateMachineException, DeserializationException {
+        if (log.isTraceEnabled()) {
+            log.trace("Received RADIUS packet {}", radiusPacket);
+        }
+        StateMachine stateMachine = StateMachine.lookupStateMachineById(radiusPacket.getIdentifier());
+        if (stateMachine == null) {
+            log.error("Invalid packet identifier {}, could not find corresponding "
+                    + "state machine ... exiting", radiusPacket.getIdentifier());
+            return;
+        }
+
+        EAP eapPayload;
+        Ethernet eth;
+
+        switch (radiusPacket.getCode()) {
+            case RADIUS.RADIUS_CODE_ACCESS_CHALLENGE:
+                log.debug("RADIUS packet: RADIUS_CODE_ACCESS_CHALLENGE");
+                RADIUSAttribute radiusAttrState = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_STATE);
+                byte[] challengeState = null;
+                if (radiusAttrState != null) {
+                    challengeState = radiusAttrState.getValue();
+                }
+                eapPayload = radiusPacket.decapsulateMessage();
+                stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
+                eth = buildEapolResponse(stateMachine.supplicantAddress(),
+                        MacAddress.valueOf(nasMacAddress),
+                        stateMachine.vlanId(),
+                        EAPOL.EAPOL_PACKET,
+                        eapPayload, stateMachine.priorityCode());
+                log.debug("Send EAP challenge response to supplicant {}", stateMachine.supplicantAddress().toString());
+                sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+                break;
+            case RADIUS.RADIUS_CODE_ACCESS_ACCEPT:
+                log.debug("RADIUS packet: RADIUS_CODE_ACCESS_ACCEPT");
+                //send an EAPOL - Success to the supplicant.
+                byte[] eapMessageSuccess =
+                        radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE).getValue();
+                eapPayload = EAP.deserializer().deserialize(
+                        eapMessageSuccess, 0, eapMessageSuccess.length);
+                eth = buildEapolResponse(stateMachine.supplicantAddress(),
+                        MacAddress.valueOf(nasMacAddress),
+                        stateMachine.vlanId(),
+                        EAPOL.EAPOL_PACKET,
+                        eapPayload, stateMachine.priorityCode());
+                log.info("Send EAP success message to supplicant {}", stateMachine.supplicantAddress().toString());
+                sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+
+                stateMachine.authorizeAccess();
+
+                break;
+            case RADIUS.RADIUS_CODE_ACCESS_REJECT:
+                log.debug("RADIUS packet: RADIUS_CODE_ACCESS_REJECT");
+                //send an EAPOL - Failure to the supplicant.
+                byte[] eapMessageFailure;
+                eapPayload = new EAP();
+                RADIUSAttribute radiusAttrEap = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_EAP_MESSAGE);
+                if (radiusAttrEap == null) {
+                    eapPayload.setCode(EAP.FAILURE);
+                    eapPayload.setIdentifier(stateMachine.challengeIdentifier());
+                    eapPayload.setLength(EAP.EAP_HDR_LEN_SUC_FAIL);
+                } else {
+                    eapMessageFailure = radiusAttrEap.getValue();
+                    eapPayload = EAP.deserializer().deserialize(
+                            eapMessageFailure, 0, eapMessageFailure.length);
+                }
+                eth = buildEapolResponse(stateMachine.supplicantAddress(),
+                        MacAddress.valueOf(nasMacAddress),
+                        stateMachine.vlanId(),
+                        EAPOL.EAPOL_PACKET,
+                        eapPayload, stateMachine.priorityCode());
+                log.warn("Send EAP failure message to supplicant {}", stateMachine.supplicantAddress().toString());
+                sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+                stateMachine.denyAccess();
+
+                break;
+            default:
+                log.warn("Unknown RADIUS message received with code: {}", radiusPacket.getCode());
+        }
+    }
+
+    /**
+     * 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()));
+        if (log.isTraceEnabled()) {
+            EAPOL eap = ((EAPOL) ethernetPkt.getPayload());
+            log.trace("Sending eapol payload {} enclosed in {} to supplicant at {}",
+                      eap, ethernetPkt, connectPoint);
+        }
+        packetService.emit(packet);
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this);
+    }
+
+    // our handler defined as a private inner class
+
+    /**
+     * Packet processor responsible for forwarding packets along their paths.
+     */
+    private class ReactivePacketProcessor implements PacketProcessor {
+        @Override
+        public void process(PacketContext context) {
+
+            // Extract the original Ethernet frame from the packet information
+            InboundPacket pkt = context.inPacket();
+            Ethernet ethPkt = pkt.parsed();
+            if (ethPkt == null) {
+                return;
+            }
+
+            try {
+                // identify if incoming packet comes from supplicant (EAP) or RADIUS
+                switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
+                    case EAPOL:
+                        handleSupplicantPacket(context.inPacket());
+                        break;
+                    default:
+                        // any other packets let the specific implementation handle
+                        impl.handlePacketFromServer(context);
+                }
+            } catch (StateMachineException e) {
+                log.warn("Unable to process packet:", e);
+            }
+        }
+
+        /**
+         * Creates and initializes common fields of a RADIUS packet.
+         *
+         * @param stateMachine state machine for the request
+         * @param eapPacket  EAP packet
+         * @return RADIUS packet
+         */
+        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,
+                                       stateMachine.username());
+
+            radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
+                    AaaManager.this.nasIpAddress.getAddress());
+
+            radiusPayload.encapsulateMessage(eapPacket);
+
+            return radiusPayload;
+        }
+
+        /**
+         * Handles PAE packets (supplicant).
+         *
+         * @param inPacket Ethernet packet coming from the supplicant
+         */
+        private void handleSupplicantPacket(InboundPacket inPacket) throws StateMachineException {
+            Ethernet ethPkt = inPacket.parsed();
+            // Where does it come from?
+            MacAddress srcMac = ethPkt.getSourceMAC();
+
+            DeviceId deviceId = inPacket.receivedFrom().deviceId();
+            PortNumber portNumber = inPacket.receivedFrom().port();
+            String sessionId = deviceId.toString() + portNumber.toString();
+            EAPOL eapol = (EAPOL) ethPkt.getPayload();
+            if (log.isTraceEnabled()) {
+                log.trace("Received EAPOL packet {} in enclosing packet {} from "
+                        + "dev/port: {}/{}", eapol, ethPkt, deviceId,
+                          portNumber);
+            }
+
+            StateMachine stateMachine = StateMachine.lookupStateMachineBySessionId(sessionId);
+            if (stateMachine == null) {
+                log.debug("Creating new state machine for sessionId: {} for "
+                                + "dev/port: {}/{}", sessionId, deviceId, portNumber);
+                stateMachine = new StateMachine(sessionId);
+            } else {
+                log.debug("Using existing state-machine for sessionId: {}", sessionId);
+            }
+
+
+            switch (eapol.getEapolType()) {
+                case EAPOL.EAPOL_START:
+                    log.debug("EAP packet: EAPOL_START");
+                    stateMachine.setSupplicantConnectpoint(inPacket.receivedFrom());
+                    stateMachine.start();
+
+                    //send an EAP Request/Identify to the supplicant
+                    EAP eapPayload = new EAP(EAP.REQUEST, stateMachine.identifier(), EAP.ATTR_IDENTITY, null);
+                    if (ethPkt.getVlanID() != Ethernet.VLAN_UNTAGGED) {
+                       stateMachine.setPriorityCode(ethPkt.getPriorityCode());
+                    }
+                    Ethernet eth = buildEapolResponse(srcMac, MacAddress.valueOf(nasMacAddress),
+                                                      ethPkt.getVlanID(), EAPOL.EAPOL_PACKET,
+                                                      eapPayload, stateMachine.priorityCode());
+
+                    stateMachine.setSupplicantAddress(srcMac);
+                    stateMachine.setVlanId(ethPkt.getVlanID());
+
+                    log.debug("Getting EAP identity from supplicant {}", stateMachine.supplicantAddress().toString());
+                    sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint());
+
+                    break;
+                case EAPOL.EAPOL_LOGOFF:
+                    log.debug("EAP packet: EAPOL_LOGOFF");
+                    if (stateMachine.state() == StateMachine.STATE_AUTHORIZED) {
+                        stateMachine.logoff();
+                    }
+
+                    break;
+                case EAPOL.EAPOL_PACKET:
+                    RADIUS radiusPayload;
+                    // check if this is a Response/Identify or  a Response/TLS
+                    EAP eapPacket = (EAP) eapol.getPayload();
+
+                    byte dataType = eapPacket.getDataType();
+                    switch (dataType) {
+
+                        case EAP.ATTR_IDENTITY:
+                            log.debug("EAP packet: EAPOL_PACKET ATTR_IDENTITY");
+                            // request id access to RADIUS
+                            stateMachine.setUsername(eapPacket.getData());
+
+                            radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
+                            radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
+                            radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret);
+
+                            sendRadiusPacket(radiusPayload, inPacket);
+
+                            // change the state to "PENDING"
+                            stateMachine.requestAccess();
+                            break;
+                        case EAP.ATTR_MD5:
+                            log.debug("EAP packet: EAPOL_PACKET ATTR_MD5");
+                            // verify if the EAP identifier corresponds to the
+                            // challenge identifier from the client state
+                            // machine.
+                            if (eapPacket.getIdentifier() == stateMachine.challengeIdentifier()) {
+                                //send the RADIUS challenge response
+                                radiusPayload =
+                                        getRadiusPayload(stateMachine,
+                                                         stateMachine.identifier(),
+                                                         eapPacket);
+                                radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
+
+                                if (stateMachine.challengeState() != null) {
+                                    radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
+                                            stateMachine.challengeState());
+                                }
+                                radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret);
+                                sendRadiusPacket(radiusPayload, inPacket);
+                            }
+                            break;
+                        case EAP.ATTR_TLS:
+                            log.debug("EAP packet: EAPOL_PACKET ATTR_TLS");
+                            // request id access to RADIUS
+                            radiusPayload = getRadiusPayload(stateMachine, stateMachine.identifier(), eapPacket);
+                            radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
+
+                            if (stateMachine.challengeState() != null) {
+                                radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
+                                        stateMachine.challengeState());
+                            }
+                            stateMachine.setRequestAuthenticator(radiusPayload.generateAuthCode());
+
+                            radiusPayload.addMessageAuthenticator(AaaManager.this.radiusSecret);
+                            sendRadiusPacket(radiusPayload, inPacket);
+
+                            if (stateMachine.state() != StateMachine.STATE_PENDING) {
+                                stateMachine.requestAccess();
+                            }
+
+                            break;
+                        default:
+                            log.warn("Unknown EAP packet type");
+                            return;
+                    }
+                    break;
+                default:
+                    log.debug("Skipping EAPOL message {}", eapol.getEapolType());
+            }
+        }
+    }
+
+    /**
+     * Delegate allowing the StateMachine to notify us of events.
+     */
+    private class InternalStateMachineDelegate implements StateMachineDelegate {
+
+        @Override
+        public void notify(AuthenticationEvent authenticationEvent) {
+            log.info("Auth event {} for {}",
+                    authenticationEvent.type(), authenticationEvent.subject());
+            post(authenticationEvent);
+        }
+    }
+
+    /**
+     * Configuration Listener, handles change in configuration.
+     */
+    private class InternalConfigListener implements NetworkConfigListener {
+
+        /**
+         * Reconfigures the AAA application according to the
+         * configuration parameters passed.
+         *
+         * @param cfg configuration object
+         */
+        private void reconfigureNetwork(AaaConfig cfg) {
+            log.info("Reconfiguring AaaConfig from config: {}", cfg);
+
+            if (cfg == null) {
+                newCfg = new AaaConfig();
+            } else {
+                newCfg = cfg;
+            }
+            if (newCfg.nasIp() != null) {
+                nasIpAddress = newCfg.nasIp();
+            }
+            if (newCfg.radiusIp() != null) {
+                radiusIpAddress = newCfg.radiusIp();
+            }
+            if (newCfg.radiusMac() != null) {
+                radiusMacAddress = newCfg.radiusMac();
+            }
+            if (newCfg.nasMac() != null) {
+                nasMacAddress = newCfg.nasMac();
+            }
+            if (newCfg.radiusSecret() != null) {
+                radiusSecret = newCfg.radiusSecret();
+            }
+
+            boolean reconfigureCustomizer = false;
+            if (customizer == null || !customizer.equals(newCfg.radiusPktCustomizer())) {
+                customizer = newCfg.radiusPktCustomizer();
+                configurePacketCustomizer();
+                reconfigureCustomizer = true;
+            }
+
+            if (radiusConnectionType == null
+                    || reconfigureCustomizer
+                    || !radiusConnectionType.equals(newCfg.radiusConnectionType())) {
+                radiusConnectionType = newCfg.radiusConnectionType();
+                if (impl != null) {
+                    impl.withdrawIntercepts();
+                    impl.clearLocalState();
+                }
+                configureRadiusCommunication();
+                impl.initializeLocalState(newCfg);
+                impl.requestIntercepts();
+            } else if (impl != null) {
+                impl.clearLocalState();
+                impl.initializeLocalState(newCfg);
+            }
+        }
+
+        @Override
+        public void event(NetworkConfigEvent event) {
+
+            if ((event.type() == NetworkConfigEvent.Type.CONFIG_ADDED ||
+                    event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) &&
+                    event.configClass().equals(AaaConfig.class)) {
+
+                AaaConfig cfg = netCfgService.getConfig(appId, AaaConfig.class);
+                reconfigureNetwork(cfg);
+
+                log.info("Reconfigured: {}", cfg.toString());
+            }
+        }
+    }
+
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+
+            switch (event.type()) {
+                case PORT_REMOVED:
+                    DeviceId devId = event.subject().id();
+                    PortNumber portNumber = event.port().number();
+                    String sessionId = devId.toString() + portNumber.toString();
+
+                    Map<String, StateMachine> sessionIdMap = StateMachine.sessionIdMap();
+                    StateMachine removed = sessionIdMap.remove(sessionId);
+                    if (removed != null) {
+                        StateMachine.deleteStateMachineMapping(removed);
+                    }
+
+                    break;
+                default:
+                    return;
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/AaaResetDeviceCommand.java b/app/src/main/java/org/opencord/aaa/impl/AaaResetDeviceCommand.java
new file mode 100644
index 0000000..14b0fe1
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/AaaResetDeviceCommand.java
@@ -0,0 +1,39 @@
+/*
+ * 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.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Argument;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onlab.packet.MacAddress;
+
+/**
+ * Removes a AAA state machine.
+ */
+@Command(scope = "onos", name = "aaa-reset-device",
+         description = "Resets the authentication state machine for a given device")
+public class AaaResetDeviceCommand extends AbstractShellCommand {
+    @Argument(index = 0, name = "mac", description = "MAC of device to reset authention state",
+              required = true, multiValued = true)
+    private String[] macs = null;
+
+    @Override
+    protected void execute() {
+        for (String mac : macs) {
+            StateMachine.deleteByMac(MacAddress.valueOf(mac));
+        }
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/AaaShowUsersCommand.java b/app/src/main/java/org/opencord/aaa/impl/AaaShowUsersCommand.java
new file mode 100644
index 0000000..ed117ac
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/AaaShowUsersCommand.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016-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.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.device.DeviceService;
+
+import org.opencord.sadis.SadisService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+/**
+ * Shows the users in the aaa.
+ */
+@Command(scope = "onos", name = "aaa-users",
+        description = "Shows the aaa users")
+public class AaaShowUsersCommand extends AbstractShellCommand {
+    @Override
+    protected void execute() {
+        String[] state = {
+                "IDLE",
+                "STARTED",
+                "PENDING",
+                "AUTHORIZED",
+                "UNAUTHORIZED"
+        };
+
+        DeviceService devService = AbstractShellCommand.get(DeviceService.class);
+        SadisService sadisService =
+                AbstractShellCommand.get(SadisService.class);
+
+        for (StateMachine stateMachine : StateMachine.sessionIdMap().values()) {
+            String deviceId = stateMachine.supplicantConnectpoint().deviceId().toString();
+            String portNum = stateMachine.supplicantConnectpoint().port().toString();
+
+            String username = "UNKNOWN";
+            if (stateMachine.username() != null) {
+                username = new String(stateMachine.username());
+            }
+            String mac = "UNKNOWN";
+            if (stateMachine.supplicantAddress() != null) {
+                mac = stateMachine.supplicantAddress().toString();
+            }
+
+            String nasPortId = devService.getPort(stateMachine.supplicantConnectpoint()).
+                    annotations().value(AnnotationKeys.PORT_NAME);
+
+            String subsId = "UNKNOWN";
+            SubscriberAndDeviceInformation subscriber = sadisService.getSubscriberInfoService().get(nasPortId);
+            if (subscriber != null) {
+                subsId = subscriber.nasPortId();
+            }
+
+            print("UserName=%s,CurrentState=%s,DeviceId=%s,MAC=%s,PortNumber=%s,SubscriberId=%s",
+                  username, state[stateMachine.state()], deviceId, mac, portNum, subsId);
+        }
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/AttPacketCustomizer.java b/app/src/main/java/org/opencord/aaa/impl/AttPacketCustomizer.java
new file mode 100644
index 0000000..1c23684
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/AttPacketCustomizer.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * AT&T specific RADIUS packet customization.
+ *
+ */
+public class AttPacketCustomizer extends SamplePacketCustomizer {
+
+    public AttPacketCustomizer(CustomizationInfo customInfo) {
+        super(customInfo);
+    }
+
+    @Override
+    protected boolean updateNasIp() {
+        return false;
+    }
+
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/CustomizationInfo.java b/app/src/main/java/org/opencord/aaa/impl/CustomizationInfo.java
new file mode 100755
index 0000000..9277056
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/CustomizationInfo.java
@@ -0,0 +1,45 @@
+/*
+ * 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.onosproject.net.device.DeviceService;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+/**
+ * Info required to do customization to packets.
+ */
+public class CustomizationInfo {
+
+    private DeviceService devService;
+
+    private BaseInformationService<SubscriberAndDeviceInformation> subscriberService;
+
+    public CustomizationInfo(BaseInformationService<SubscriberAndDeviceInformation> subsService,
+                             DeviceService devService) {
+        this.subscriberService = subsService;
+        this.devService = devService;
+    }
+
+    public DeviceService deviceService() {
+        return devService;
+    }
+
+    public BaseInformationService<SubscriberAndDeviceInformation> subscriberService() {
+        return subscriberService;
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/PacketCustomizer.java b/app/src/main/java/org/opencord/aaa/impl/PacketCustomizer.java
new file mode 100755
index 0000000..265edf7
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/PacketCustomizer.java
@@ -0,0 +1,60 @@
+/*
+ * 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.onlab.packet.Ethernet;
+import org.onlab.packet.RADIUS;
+
+import org.onosproject.net.packet.InboundPacket;
+
+/**
+ * Default RADIUS Packet Customization.
+ * Does not change the packet
+ *
+ * Subclasses should implement filling of attributes depending on specifics ofsetup/RADIUS server
+ */
+public class  PacketCustomizer {
+
+    protected CustomizationInfo customInfo;
+
+    public PacketCustomizer(CustomizationInfo info) {
+        this.customInfo = info;
+    }
+
+    /**
+     * Customize the packet as per specific Setup or RADIUS server requirements.
+     *
+     * @param inPkt RADIUS packet to be customized
+     * @param eapPacket Incoming packet containing EAP for which this the RADIUS message is being created
+     * @return Customized RADIUS packet
+     */
+    public RADIUS customizePacket(RADIUS inPkt, InboundPacket eapPacket) {
+        return inPkt;
+    }
+
+    /**
+     * Customize the Ethernet header as per specific Setup or RADIUS server requirements.
+     *
+     * @param inPkt Ethernet packet to be changed
+     * @param eapPacket Incoming packet containing EAP for which this the
+     *                  RADIUS message is being created
+     * @return Changed Ethernet packet
+     */
+    public Ethernet customizeEthernetIPHeaders(Ethernet inPkt,
+                                               InboundPacket eapPacket) {
+        return inPkt;
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/PortBasedRadiusCommunicator.java b/app/src/main/java/org/opencord/aaa/impl/PortBasedRadiusCommunicator.java
new file mode 100755
index 0000000..e1b8bb9
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/PortBasedRadiusCommunicator.java
@@ -0,0 +1,458 @@
+/*
+ * 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.onlab.packet.ARP;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.RADIUS;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketService;
+
+import org.opencord.aaa.AaaConfig;
+import org.opencord.aaa.RadiusCommunicator;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+import org.slf4j.Logger;
+
+import com.google.common.collect.Maps;
+
+import static org.onosproject.net.packet.PacketPriority.CONTROL;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles communication with the RADIUS server through ports
+ * of the SDN switches.
+ */
+public class PortBasedRadiusCommunicator implements RadiusCommunicator {
+
+    // for verbose output
+    private final Logger log = getLogger(getClass());
+
+    // our unique identifier
+    private ApplicationId appId;
+
+    // to receive Packet-in events that we'll respond to
+    PacketService packetService;
+
+    DeviceService deviceService;
+
+    MastershipService mastershipService;
+
+    BaseInformationService<SubscriberAndDeviceInformation> subsService;
+
+    // to store local mapping of IP Address and Serial No of Device
+    private Map<Ip4Address, String> ipToSnMap;
+
+    // connect points to the RADIUS server
+    Set<ConnectPoint> radiusConnectPoints;
+
+    // Parsed RADIUS server addresses
+    protected InetAddress radiusIpAddress;
+
+    // RADIUS server TCP port number
+    protected short radiusServerPort;
+
+    protected String radiusMacAddress;
+
+    // NAS IP address
+    protected InetAddress nasIpAddress;
+
+    protected String nasMacAddress;
+
+    // RADIUS server Vlan ID
+    private short radiusVlanID;
+
+    // RADIUS p-bit
+    private byte radiusPBit;
+
+    PacketCustomizer pktCustomizer;
+    AaaManager aaaManager;
+
+    ConnectPoint radiusServerConnectPoint = null;
+
+    InnerMastershipListener changeListener = new InnerMastershipListener();
+    InnerDeviceListener deviceListener = new InnerDeviceListener();
+
+    PortBasedRadiusCommunicator(ApplicationId appId, PacketService pktService,
+                                MastershipService masService, DeviceService devService,
+                                BaseInformationService<SubscriberAndDeviceInformation> subsService,
+                                PacketCustomizer pktCustomizer, AaaManager aaaManager) {
+        this.appId = appId;
+        this.packetService = pktService;
+        this.mastershipService = masService;
+        this.deviceService = devService;
+        this.subsService = subsService;
+        this.pktCustomizer = pktCustomizer;
+        this.aaaManager = aaaManager;
+
+        ipToSnMap = Maps.newConcurrentMap();
+        mastershipService.addListener(changeListener);
+        deviceService.addListener(deviceListener);
+
+        log.info("Created PortBased");
+    }
+
+    private void initializeLocalState() {
+        synchronized (this) {
+            radiusServerConnectPoint = null;
+            if (radiusConnectPoints != null) {
+                // find a connect point through a device for which we are master
+                for (ConnectPoint cp: radiusConnectPoints) {
+                    if (mastershipService.isLocalMaster(cp.deviceId())) {
+                        if (deviceService.isAvailable(cp.deviceId())) {
+                            radiusServerConnectPoint = cp;
+                        }
+                        break;
+                    }
+                }
+            }
+
+            log.info("RADIUS connectPoint in initializeLocalState is {}", radiusServerConnectPoint);
+
+            if (radiusServerConnectPoint == null) {
+                log.error("Master of none, can't send radius Message to server");
+            }
+        }
+    }
+
+    @Override
+    public void initializeLocalState(AaaConfig newCfg) {
+        if (newCfg.nasIp() != null) {
+            nasIpAddress = newCfg.nasIp();
+        }
+        if (newCfg.radiusIp() != null) {
+            radiusIpAddress = newCfg.radiusIp();
+        }
+        if (newCfg.radiusMac() != null) {
+            radiusMacAddress = newCfg.radiusMac();
+        }
+        if (newCfg.nasMac() != null) {
+            nasMacAddress = newCfg.nasMac();
+        }
+
+        radiusServerPort = newCfg.radiusServerUdpPort();
+        radiusVlanID = newCfg.radiusServerVlanId();
+        radiusPBit = newCfg.radiusServerPBit();
+
+        radiusConnectPoints = newCfg.radiusServerConnectPoints();
+
+        initializeLocalState();
+    }
+
+    @Override
+    public void clearLocalState() {
+        mastershipService.removeListener(changeListener);
+        deviceService.removeListener(deviceListener);
+    }
+
+    @Override
+    public void deactivate() {
+        mastershipService.removeListener(changeListener);
+        deviceService.removeListener(deviceListener);
+    }
+
+    @Override
+    public void requestIntercepts() {
+        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP);
+        packetService.requestPackets(selectorArpServer.build(), CONTROL, appId);
+
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(radiusServerPort));
+        packetService.requestPackets(selectorServer.build(), CONTROL, appId);
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+               selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
+               packetService.requestPackets(selector.build(), CONTROL, appId);
+    }
+
+    @Override
+    public void withdrawIntercepts() {
+        TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_ARP);
+        packetService.cancelPackets(selectorArpServer.build(), CONTROL, appId);
+
+        TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPProtocol(IPv4.PROTOCOL_UDP)
+                .matchUdpSrc(TpPort.tpPort(radiusServerPort));
+        packetService.cancelPackets(selectorServer.build(), CONTROL, appId);
+
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
+        packetService.cancelPackets(selector.build(), CONTROL, appId);
+    }
+
+    @Override
+    public void sendRadiusPacket(RADIUS radiusPacket, InboundPacket inPkt) {
+        // create the packet
+        Ethernet ethReply = new Ethernet();
+        ethReply.setSourceMACAddress(nasMacAddress);
+        ethReply.setDestinationMACAddress(radiusMacAddress);
+        ethReply.setEtherType(Ethernet.TYPE_IPV4);
+        ethReply.setVlanID(radiusVlanID);
+        ethReply.setPriorityCode(radiusPBit);
+
+        IPv4 ipv4Packet = new IPv4();
+        ipv4Packet.setTtl((byte) 64);
+        ipv4Packet.setSourceAddress(Ip4Address.
+                valueOf(nasIpAddress).toInt());
+        ipv4Packet.setDestinationAddress(Ip4Address.
+                valueOf(radiusIpAddress).toInt());
+
+        UDP udpPacket = new UDP();
+        udpPacket.setSourcePort(radiusServerPort);
+        udpPacket.setDestinationPort(radiusServerPort);
+
+        udpPacket.setPayload(radiusPacket);
+        ipv4Packet.setPayload(udpPacket);
+        ethReply.setPayload(ipv4Packet);
+
+        // store the IP address and SN of the device, later to be used
+        // for ARP responses
+        String serialNo = deviceService.getDevice(inPkt.
+                receivedFrom().deviceId()).serialNumber();
+
+        SubscriberAndDeviceInformation deviceInfo = subsService.get(serialNo);
+
+        if (deviceInfo == null) {
+            log.warn("No Device found with SN {}", serialNo);
+            return;
+        }
+        ipToSnMap.put(deviceInfo.ipAddress(), serialNo);
+
+        // send the message out
+        sendFromRadiusServerPort(pktCustomizer.
+                customizeEthernetIPHeaders(ethReply, inPkt));
+    }
+
+    /**
+     * Sends packet to the RADIUS server using one of the switch ports.
+     *
+     * @param packet Ethernet packet to be sent
+     */
+    private void sendFromRadiusServerPort(Ethernet packet) {
+        if (radiusServerConnectPoint != null) {
+            log.trace("AAA Manager sending Ethernet packet = {}", packet);
+            TrafficTreatment t = DefaultTrafficTreatment.builder()
+                    .setOutput(radiusServerConnectPoint.port()).build();
+            OutboundPacket o = new DefaultOutboundPacket(
+                    radiusServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
+            packetService.emit(o);
+        } else {
+            log.error("Unable to send RADIUS packet, connectPoint is null");
+        }
+    }
+
+    @Override
+    public void handlePacketFromServer(PacketContext context) {
+        // Extract the original Ethernet frame from the packet information
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+        if (ethPkt == null) {
+            return;
+        }
+
+        // identify if incoming packet
+        switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
+            case ARP:
+                handleArpPacketFromServer(context);
+                break;
+            case IPV4:
+                handleIPv4PacketFromServer(context);
+                break;
+            default:
+                log.debug("Skipping Ethernet packet type {}",
+                        EthType.EtherType.lookup(ethPkt.getEtherType()));
+        }
+    }
+
+    /**
+     * Handles ARP packets from RADIUS server.
+     *
+     * @param context Context for the packet
+     */
+    private void handleArpPacketFromServer(PacketContext context) {
+        // Extract the original Ethernet frame from the packet information
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+        if (ethPkt == null) {
+            return;
+        }
+
+        ARP arpPacket = (ARP) ethPkt.getPayload();
+
+        Ip4Address targetAddress = Ip4Address.valueOf(arpPacket.
+                getTargetProtocolAddress());
+
+        String serialNo = ipToSnMap.get(targetAddress);
+        if (serialNo == null) {
+            log.info("No mapping found for ARP reply, target address {}",
+                    targetAddress);
+            return;
+        }
+        MacAddress senderMac = subsService.get(serialNo).hardwareIdentifier();
+        if (senderMac == null) {
+            log.warn("ARP resolution, MAC address not found for SN {}", serialNo);
+            return;
+        }
+
+        ARP arpReply = (ARP) arpPacket.clone();
+        arpReply.setOpCode(ARP.OP_REPLY);
+        arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
+        arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
+        arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
+        arpReply.setSenderHardwareAddress(senderMac.toBytes());
+
+        log.debug("AAA Manager: Query for ARP of IP : {}", arpPacket.getTargetProtocolAddress());
+
+        // Ethernet Frame.
+        Ethernet ethReply = new Ethernet();
+        ethReply.setSourceMACAddress(senderMac);
+        ethReply.setDestinationMACAddress(ethPkt.getSourceMAC());
+        ethReply.setEtherType(Ethernet.TYPE_ARP);
+        ethReply.setVlanID(radiusVlanID);
+        ethReply.setPriorityCode(ethPkt.getPriorityCode());
+
+        ethReply.setPayload(arpReply);
+        sendFromRadiusServerPort(ethReply);
+    }
+
+    /**
+     * Handles IP packets from RADIUS server.
+     *
+     * @param context Context for the packet
+     */
+    private void handleIPv4PacketFromServer(PacketContext context) {
+        // Extract the original Ethernet frame from the packet information
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+        if (ethPkt == null) {
+            return;
+        }
+
+        IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+
+        if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
+            UDP udpPacket = (UDP) ipv4Packet.getPayload();
+
+            if (udpPacket.getSourcePort() == radiusServerPort) {
+                //This packet is RADIUS packet from the server.
+                RADIUS radiusMsg;
+                try {
+                    radiusMsg =
+                            RADIUS.deserializer()
+                                    .deserialize(udpPacket.serialize(),
+                                            8,
+                                            udpPacket.getLength() - 8);
+                    try {
+                        aaaManager.handleRadiusPacket(radiusMsg);
+                    }  catch (StateMachineException sme) {
+                        log.error("Illegal state machine operation", sme);
+                    }
+                } catch (DeserializationException dex) {
+                    log.error("Cannot deserialize packet", dex);
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles Mastership changes for the devices which connect
+     * to the RADIUS server.
+     */
+    private class InnerMastershipListener implements MastershipListener {
+        @Override
+        public void event(MastershipEvent event) {
+            if (radiusServerConnectPoint != null &&
+                    radiusServerConnectPoint.deviceId().
+                            equals(event.subject())) {
+                log.trace("Mastership Event recevived for {}", event.subject());
+                // mastership of the device for our connect point has changed
+                // reselect
+                initializeLocalState();
+            }
+        }
+    }
+
+    /**
+     * Handles Device status change for the devices which connect
+     * to the RADIUS server.
+     */
+    private class InnerDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            log.trace("Device Event recevived for {} event {}", event.subject(), event.type());
+            if (radiusServerConnectPoint == null) {
+                switch (event.type()) {
+                    case DEVICE_ADDED:
+                    case DEVICE_AVAILABILITY_CHANGED:
+                        // some device is available check if we can get one
+                        initializeLocalState();
+                        break;
+                    default:
+                        break;
+                }
+                return;
+            }
+            if (radiusServerConnectPoint.deviceId().
+                    equals(event.subject().id())) {
+                switch (event.type()) {
+                    case DEVICE_AVAILABILITY_CHANGED:
+                    case DEVICE_REMOVED:
+                    case DEVICE_SUSPENDED:
+                        // state of our device has changed, check if we need
+                        // to re-select
+                        initializeLocalState();
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/SamplePacketCustomizer.java b/app/src/main/java/org/opencord/aaa/impl/SamplePacketCustomizer.java
new file mode 100755
index 0000000..d8dd486
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/SamplePacketCustomizer.java
@@ -0,0 +1,160 @@
+/*
+ * 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 static org.slf4j.LoggerFactory.getLogger;
+
+import java.nio.ByteBuffer;
+
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.RADIUS;
+import org.onlab.packet.RADIUSAttribute;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Port;
+import org.onosproject.net.packet.InboundPacket;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.slf4j.Logger;
+
+
+/**
+ * Sample RADIUS Packet Customization.
+ *
+ */
+public class  SamplePacketCustomizer extends PacketCustomizer {
+
+    private final Logger log = getLogger(getClass());
+
+    public SamplePacketCustomizer(CustomizationInfo customInfo) {
+        super(customInfo);
+    }
+
+    /**
+     * Determines if NAS IP Attribute should be updated or not.
+     *
+     * @return true if updating NAS IP is desired
+     */
+    protected boolean updateNasIp() {
+        return true;
+    }
+
+    /**
+     * Customize the packet as per specific Setup or RADIUS
+     * server requirements.
+     *
+     * @param inPkt RADIUS packet to be customized
+     * @param eapPacket Incoming packet containing EAP for which this the
+     *                  RADIUS message is being created
+     * @return Customized RADIUS packet
+     */
+    @Override
+    public RADIUS customizePacket(RADIUS inPkt, InboundPacket eapPacket) {
+        Port p = customInfo.deviceService().getPort(eapPacket.receivedFrom());
+
+        String id = p.annotations().value(AnnotationKeys.PORT_NAME);
+
+        log.info("Customizing packet Port received for {}", id);
+
+        SubscriberAndDeviceInformation subscriber = customInfo.
+                subscriberService().get(id);
+
+        if (subscriber == null) {
+            log.warn("No subscriber found with id {}", id);
+            return inPkt;
+        }
+
+        String nasPortId = subscriber.nasPortId();
+
+        Ethernet ethPkt = eapPacket.parsed();
+        MacAddress srcMac = ethPkt.getSourceMAC();
+
+        // Get the nasId from subscriber service using the Serial Number
+        String serialNo = customInfo.deviceService().getDevice(eapPacket.
+                receivedFrom().deviceId()).serialNumber();
+
+        log.info("SampleRadiusCustomizer serial = {}", serialNo);
+        SubscriberAndDeviceInformation deviceInfo = customInfo.
+                subscriberService().get(serialNo);
+
+        if (deviceInfo == null) {
+            log.warn("No Device found with SN {}", serialNo);
+            return inPkt;
+        }
+        String nodeName = deviceInfo.nasId();
+
+        log.info("Setting nasId={} nasPortId{}", nodeName, nasPortId);
+
+        if (updateNasIp()) {
+            inPkt.updateAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_IP,
+                                  deviceInfo.ipAddress().toOctets());
+        }
+
+        inPkt.setAttribute(RADIUSAttribute.RADIUS_ATTR_CALLING_STATION_ID,
+                srcMac.toBytes());
+
+        // Check value - 16 was used in PoC2, as per PoC3 TS value should be 15
+        inPkt.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_PORT_TYPE,
+                ByteBuffer.allocate(4).putInt(15).array());
+
+        inPkt.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_PORT,
+                ByteBuffer.allocate(4).putInt((int) p.number().toLong()).array());
+        // Check - If this is needed, worked with this value in PoC2
+        inPkt.setAttribute(RADIUSAttribute.RADIUS_ATTR_ACCT_SESSION_ID,
+                "023:27:46:00000".getBytes());
+
+        inPkt.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_ID,
+                nodeName.getBytes());
+        inPkt.setAttribute(RADIUSAttribute.RADIUS_ATTR_NAS_PORT_ID,
+                nasPortId.getBytes());
+
+        return inPkt;
+    }
+
+    /**
+     * Customize the Ethernet header as per specific Setup or RADIUS
+     * server requirements.
+     *
+     * @param inPkt Ethernet packet to be changed
+     * @param eapPacket Incoming packet containing EAP for which this the
+     *                  RADIUS message is being created
+     * @return Changed Ethernet packet
+     */
+    @Override
+    public Ethernet customizeEthernetIPHeaders(Ethernet inPkt,
+                                               InboundPacket eapPacket) {
+
+        String serialNo = customInfo.deviceService().getDevice(eapPacket.
+                receivedFrom().deviceId()).serialNumber();
+
+        log.info("SampleRadiusCustomzer customizer serial = {}", serialNo);
+        SubscriberAndDeviceInformation deviceInfo = customInfo.
+                subscriberService().get(serialNo);
+
+        if (deviceInfo == null) {
+            log.warn("No Device found with SN {}", serialNo);
+            return inPkt;
+        }
+
+        inPkt.setSourceMACAddress(deviceInfo.hardwareIdentifier());
+
+        IPv4 ipv4Packet = (IPv4) inPkt.getPayload();
+        ipv4Packet.setSourceAddress(deviceInfo.ipAddress().toString());
+        inPkt.setPayload(ipv4Packet);
+
+        return inPkt;
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java b/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java
new file mode 100755
index 0000000..e2bce35
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java
@@ -0,0 +1,210 @@
+/*
+ * 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 static org.onosproject.net.packet.PacketPriority.CONTROL;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.RADIUS;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketService;
+import org.opencord.aaa.AaaConfig;
+import org.opencord.aaa.RadiusCommunicator;
+import org.slf4j.Logger;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+/**
+ * Handles Socket based communication with the RADIUS server.
+ */
+public class SocketBasedRadiusCommunicator implements RadiusCommunicator {
+
+    // for verbose output
+    private final Logger log = getLogger(getClass());
+
+    // our unique identifier
+    private ApplicationId appId;
+
+    // to receive Packet-in events that we'll respond to
+    PacketService packetService;
+
+    // Socket used for UDP communications with RADIUS server
+    private DatagramSocket radiusSocket;
+
+    private String radiusHost;
+
+    // Parsed RADIUS server addresses
+    protected InetAddress radiusIpAddress;
+
+    // RADIUS server TCP port number
+    protected short radiusServerPort;
+
+    // Executor for RADIUS communication thread
+    private ExecutorService executor;
+
+    AaaManager aaaManager;
+
+    SocketBasedRadiusCommunicator(ApplicationId appId, PacketService pktService,
+                                  AaaManager aaaManager) {
+        this.appId = appId;
+        this.packetService = pktService;
+        this.aaaManager = aaaManager;
+    }
+
+    @Override
+    public void initializeLocalState(AaaConfig newCfg) {
+        if (newCfg.radiusIp() != null) {
+            radiusIpAddress = newCfg.radiusIp();
+        }
+        radiusServerPort = newCfg.radiusServerUdpPort();
+        radiusHost = newCfg.radiusHostName();
+
+        try {
+            radiusSocket = new DatagramSocket(null);
+            radiusSocket.setReuseAddress(true);
+            radiusSocket.bind(new InetSocketAddress(radiusServerPort));
+        } catch (Exception ex) {
+            log.error("Can't open RADIUS socket", ex);
+        }
+
+        log.info("Remote RADIUS Server: {}:{}", radiusIpAddress, radiusServerPort);
+
+        executor = Executors.newSingleThreadExecutor(
+                new ThreadFactoryBuilder()
+                        .setNameFormat("AAA-radius-%d").build());
+        executor.execute(radiusListener);
+    }
+
+    @Override
+    public void clearLocalState() {
+        radiusSocket.close();
+        executor.shutdownNow();
+    }
+
+    @Override
+    public void deactivate() {
+       clearLocalState();
+    }
+
+    @Override
+    public void requestIntercepts() {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
+        packetService.requestPackets(selector.build(), CONTROL, appId);
+    }
+
+    @Override
+    public void withdrawIntercepts() {
+        TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+        selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
+        packetService.cancelPackets(selector.build(), CONTROL, appId);
+    }
+
+    @Override
+    public void sendRadiusPacket(RADIUS radiusPacket, InboundPacket inPkt) {
+        try {
+            final byte[] data = radiusPacket.serialize();
+            final DatagramSocket socket = radiusSocket;
+
+            try {
+                InetAddress address;
+                if (radiusHost != null) {
+                    address = InetAddress.getByName(radiusHost);
+                } else {
+                    address = radiusIpAddress;
+                }
+                DatagramPacket packet =
+                        new DatagramPacket(data, data.length, address, radiusServerPort);
+                if (log.isTraceEnabled()) {
+                    log.trace("Sending packet {} to Radius Server {}:{} using socket",
+                              radiusPacket, address, radiusServerPort);
+                }
+                socket.send(packet);
+            } catch (UnknownHostException uhe) {
+                log.warn("Unable to resolve host {}", radiusHost);
+            }
+        } catch (IOException e) {
+            log.info("Cannot send packet to RADIUS server", e);
+        }
+    }
+
+    @Override
+    public void handlePacketFromServer(PacketContext context) {
+        InboundPacket pkt = context.inPacket();
+        Ethernet ethPkt = pkt.parsed();
+        if (log.isTraceEnabled() && ethPkt.getEtherType() != Ethernet.TYPE_LLDP
+                && ethPkt.getEtherType() != Ethernet.TYPE_BSN) {
+            log.trace("Skipping Ethernet packet type {}",
+                      EthType.EtherType.lookup(ethPkt.getEtherType()));
+        }
+    }
+
+    class RadiusListener implements Runnable {
+
+        @Override
+        public void run() {
+            boolean done = false;
+            int packetNumber = 1;
+
+            log.info("UDP listener thread starting up");
+            RADIUS inboundRadiusPacket;
+            while (!done) {
+                try {
+                    byte[] packetBuffer = new byte[RADIUS.RADIUS_MAX_LENGTH];
+                    DatagramPacket inboundBasePacket =
+                            new DatagramPacket(packetBuffer, packetBuffer.length);
+                    DatagramSocket socket = radiusSocket;
+                    socket.receive(inboundBasePacket);
+                    log.debug("Packet #{} received", packetNumber++);
+                    try {
+                        inboundRadiusPacket =
+                                RADIUS.deserializer()
+                                        .deserialize(inboundBasePacket.getData(),
+                                                0,
+                                                inboundBasePacket.getLength());
+                        aaaManager.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;
+                }
+            }
+        }
+    }
+
+    RadiusListener radiusListener = new RadiusListener();
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/StateMachine.java b/app/src/main/java/org/opencord/aaa/impl/StateMachine.java
new file mode 100644
index 0000000..f9e5f25
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/StateMachine.java
@@ -0,0 +1,572 @@
+/*
+ * 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 com.google.common.collect.Maps;
+import org.onlab.packet.MacAddress;
+import org.onosproject.net.ConnectPoint;
+import org.opencord.aaa.AuthenticationEvent;
+import org.opencord.aaa.StateMachineDelegate;
+import org.slf4j.Logger;
+
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * AAA Finite State Machine.
+ */
+
+class StateMachine {
+    //INDEX to identify the state in the transition table
+    static final int STATE_IDLE = 0;
+    static final int STATE_STARTED = 1;
+    static final int STATE_PENDING = 2;
+    static final int STATE_AUTHORIZED = 3;
+    static final int STATE_UNAUTHORIZED = 4;
+
+    //INDEX to identify the transition in the transition table
+    static final int TRANSITION_START = 0; // --> started
+    static final int TRANSITION_REQUEST_ACCESS = 1;
+    static final int TRANSITION_AUTHORIZE_ACCESS = 2;
+    static final int TRANSITION_DENY_ACCESS = 3;
+    static final int TRANSITION_LOGOFF = 4;
+
+    private static int identifier = -1;
+    private byte challengeIdentifier;
+    private byte[] challengeState;
+    private byte[] username;
+    private byte[] requestAuthenticator;
+
+    // Supplicant connectivity info
+    private ConnectPoint supplicantConnectpoint;
+    private MacAddress supplicantAddress;
+    private short vlanId;
+    private byte priorityCode;
+
+    private String sessionId = null;
+
+    private final Logger log = getLogger(getClass());
+
+
+    private State[] states = {
+            new Idle(), new Started(), new Pending(), new Authorized(), new Unauthorized()
+    };
+
+
+    //State transition table
+    /*
+
+                state       IDLE    |   STARTED         |   PENDING         |   AUTHORIZED  |   UNAUTHORIZED
+             ////
+       input
+       ----------------------------------------------------------------------------------------------------
+
+       START                STARTED |   _               |   _               |   STARTED     |   STARTED
+
+       REQUEST_ACCESS       _       |   PENDING         |   _               |   _           |   _
+
+       AUTHORIZE_ACCESS     _       |   _               |   AUTHORIZED      |   _           |   _
+
+       DENY_ACCESS          _       |   -               |   UNAUTHORIZED    |   _           |   _
+
+       LOGOFF               _       |   _               |   _               |   IDLE        |   IDLE
+     */
+
+    private int[] idleTransition =
+            {STATE_STARTED, STATE_IDLE, STATE_IDLE, STATE_IDLE, STATE_IDLE};
+    private int[] startedTransition =
+            {STATE_STARTED, STATE_PENDING, STATE_STARTED, STATE_STARTED, STATE_STARTED};
+    private int[] pendingTransition =
+            {STATE_PENDING, STATE_PENDING, STATE_AUTHORIZED, STATE_UNAUTHORIZED, STATE_PENDING};
+    private int[] authorizedTransition =
+            {STATE_STARTED, STATE_AUTHORIZED, STATE_AUTHORIZED, STATE_AUTHORIZED, STATE_IDLE};
+    private int[] unauthorizedTransition =
+            {STATE_STARTED, STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_UNAUTHORIZED, STATE_IDLE};
+
+    //THE TRANSITION TABLE
+    private int[][] transition =
+            {idleTransition, startedTransition, pendingTransition, authorizedTransition,
+                    unauthorizedTransition};
+
+    private int currentState = STATE_IDLE;
+
+    // Maps of state machines. Each state machine is represented by an
+    // unique identifier on the switch: dpid + port number
+    private static Map<String, StateMachine> sessionIdMap;
+    private static Map<Integer, StateMachine> identifierMap;
+
+    private static StateMachineDelegate delegate;
+
+    public static void initializeMaps() {
+        sessionIdMap = Maps.newConcurrentMap();
+        identifierMap = Maps.newConcurrentMap();
+        identifier = -1;
+    }
+
+    public static void destroyMaps() {
+        sessionIdMap = null;
+        identifierMap = null;
+    }
+
+    public static void setDelegate(StateMachineDelegate delegate) {
+        StateMachine.delegate = delegate;
+    }
+
+    public static void unsetDelegate(StateMachineDelegate delegate) {
+        if (StateMachine.delegate == delegate) {
+            StateMachine.delegate = null;
+        }
+    }
+
+    public static Map<String, StateMachine> sessionIdMap() {
+        return sessionIdMap;
+    }
+
+    public static StateMachine lookupStateMachineById(byte identifier) {
+        return identifierMap.get((int) identifier);
+    }
+
+    public static StateMachine lookupStateMachineBySessionId(String sessionId) {
+        return sessionIdMap.get(sessionId);
+    }
+
+    public static void deleteStateMachineMapping(StateMachine machine) {
+        identifierMap.entrySet().removeIf(e -> e.getValue().equals(machine));
+    }
+
+    /**
+     * Deletes authentication state machine records for a given MAC address.
+     * @param mac mac address of the suppliant who's state machine should be removed
+     */
+    public static void deleteByMac(MacAddress mac) {
+
+        // Walk the map from session IDs to state machines looking for a MAC match
+        for (Map.Entry<String, StateMachine> e : sessionIdMap.entrySet()) {
+
+            // If a MAC match is found then delete the entry from the session ID
+            // and identifier map as well as call delete identifier to clean up
+            // the identifier bit set.
+            if (e.getValue() != null && e.getValue().supplicantAddress != null &&
+                   e.getValue().supplicantAddress.equals(mac)) {
+                sessionIdMap.remove(e.getValue().sessionId);
+                if (e.getValue().identifier != -1) {
+                    deleteStateMachineMapping(e.getValue());
+                }
+                break;
+            }
+        }
+    }
+
+    /**
+     * Creates a new StateMachine with the given session ID.
+     *
+     * @param sessionId session Id represented by the switch dpid +  port number
+     */
+    public StateMachine(String sessionId) {
+        log.info("Creating a new state machine for {}", sessionId);
+        this.sessionId = sessionId;
+        sessionIdMap.put(sessionId, this);
+    }
+
+    /**
+     * Gets the connect point for the supplicant side.
+     *
+     * @return supplicant connect point
+     */
+    public ConnectPoint supplicantConnectpoint() {
+        return supplicantConnectpoint;
+    }
+
+    /**
+     * Sets the supplicant side connect point.
+     *
+     * @param supplicantConnectpoint supplicant select point.
+     */
+    public void setSupplicantConnectpoint(ConnectPoint supplicantConnectpoint) {
+        this.supplicantConnectpoint = supplicantConnectpoint;
+    }
+
+    /**
+     * Gets the MAC address of the supplicant.
+     *
+     * @return supplicant MAC address
+     */
+    public MacAddress supplicantAddress() {
+        return supplicantAddress;
+    }
+
+    /**
+     * Sets the supplicant MAC address.
+     *
+     * @param supplicantAddress new supplicant MAC address
+     */
+    public void setSupplicantAddress(MacAddress supplicantAddress) {
+        this.supplicantAddress = supplicantAddress;
+    }
+
+    /**
+     * Gets the client's Vlan ID.
+     *
+     * @return client vlan ID
+     */
+    public short vlanId() {
+        return vlanId;
+    }
+
+    /**
+     * Sets the client's vlan ID.
+     *
+     * @param vlanId new client vlan ID
+     */
+    public void setVlanId(short vlanId) {
+        this.vlanId = vlanId;
+    }
+
+    /**
+     * Gets the client's priority Code.
+     *
+     * @return client Priority code
+     */
+    public byte priorityCode() {
+        return priorityCode;
+    }
+
+    /**
+     * Sets the client's priority Code.
+     *
+     * @param priorityCode new client priority Code
+     */
+    public void setPriorityCode(byte priorityCode) {
+        this.priorityCode = priorityCode;
+    }
+
+    /**
+     * Gets the client id that is requesting for access.
+     *
+     * @return The client id.
+     */
+    public String sessionId() {
+        return this.sessionId;
+    }
+
+    /**
+     * Set the challenge identifier and the state issued by the RADIUS.
+     *
+     * @param challengeIdentifier The challenge identifier set into the EAP packet from the RADIUS message.
+     * @param challengeState      The challenge state from the RADIUS.
+     */
+    protected void setChallengeInfo(byte challengeIdentifier, byte[] challengeState) {
+        this.challengeIdentifier = challengeIdentifier;
+        this.challengeState = challengeState;
+    }
+
+    /**
+     * Set the challenge identifier issued by the RADIUS on the access challenge request.
+     *
+     * @param challengeIdentifier The challenge identifier set into the EAP packet from the RADIUS message.
+     */
+    protected void setChallengeIdentifier(byte challengeIdentifier) {
+        log.info("Set Challenge Identifier to {}", challengeIdentifier);
+        this.challengeIdentifier = challengeIdentifier;
+    }
+
+    /**
+     * Gets the challenge EAP identifier set by the RADIUS.
+     *
+     * @return The challenge EAP identifier.
+     */
+    protected byte challengeIdentifier() {
+        return this.challengeIdentifier;
+    }
+
+
+    /**
+     * Set the challenge state info issued by the RADIUS.
+     *
+     * @param challengeState The challenge state from the RADIUS.
+     */
+    protected void setChallengeState(byte[] challengeState) {
+        log.info("Set Challenge State");
+        this.challengeState = challengeState;
+    }
+
+    /**
+     * Gets the challenge state set by the RADIUS.
+     *
+     * @return The challenge state.
+     */
+    protected byte[] challengeState() {
+        return this.challengeState;
+    }
+
+    /**
+     * Set the username.
+     *
+     * @param username The username sent to the RADIUS upon access request.
+     */
+    protected void setUsername(byte[] username) {
+        this.username = username;
+    }
+
+    /**
+     * Gets the username.
+     *
+     * @return The requestAuthenticator.
+     */
+    protected byte[] requestAuthenticator() {
+        return this.requestAuthenticator;
+    }
+
+    /**
+     * Sets the authenticator.
+     *
+     * @param authenticator The username sent to the RADIUS upon access request.
+     */
+    protected void setRequestAuthenticator(byte[] authenticator) {
+        this.requestAuthenticator = authenticator;
+    }
+
+
+    /**
+     * Gets the username.
+     *
+     * @return The username.
+     */
+    protected byte[] username() {
+        return this.username;
+    }
+
+    /**
+     * Return the identifier of the state machine.
+     *
+     * @return The state machine identifier.
+     */
+    public synchronized byte identifier() {
+        identifier = (identifier + 1) % 255;
+        identifierMap.put(identifier, this);
+        return (byte) identifier;
+    }
+
+    /**
+     * Move to the next state.
+     *
+     * @param msg message
+     */
+    private void next(int msg) {
+        currentState = transition[currentState][msg];
+        log.info("Current State " + currentState);
+    }
+
+    /**
+     * Client has requested the start action to allow network access.
+     *
+     * @throws StateMachineException if authentication protocol is violated
+     */
+    public void start() throws StateMachineException {
+        states[currentState].start();
+
+        delegate.notify(new AuthenticationEvent(
+                AuthenticationEvent.Type.STARTED, supplicantConnectpoint));
+
+        //move to the next state
+        next(TRANSITION_START);
+        identifier = this.identifier();
+    }
+
+    /**
+     * An Identification information has been sent by the supplicant.
+     * Move to the next state if possible.
+     *
+     * @throws StateMachineException if authentication protocol is violated
+     */
+    public void requestAccess() throws StateMachineException {
+        states[currentState].requestAccess();
+
+        delegate.notify(new AuthenticationEvent(
+                AuthenticationEvent.Type.REQUESTED, supplicantConnectpoint));
+
+        //move to the next state
+        next(TRANSITION_REQUEST_ACCESS);
+    }
+
+    /**
+     * RADIUS has accepted the identification.
+     * Move to the next state if possible.
+     *
+     * @throws StateMachineException if authentication protocol is violated
+     */
+    public void authorizeAccess() throws StateMachineException {
+        states[currentState].radiusAccepted();
+        //move to the next state
+        next(TRANSITION_AUTHORIZE_ACCESS);
+
+        delegate.notify(new AuthenticationEvent(
+                AuthenticationEvent.Type.APPROVED, supplicantConnectpoint));
+
+        // Clear mapping
+        deleteStateMachineMapping(this);
+    }
+
+    /**
+     * RADIUS has denied the identification.
+     * Move to the next state if possible.
+     *
+     * @throws StateMachineException if authentication protocol is violated
+     */
+    public void denyAccess() throws StateMachineException {
+        states[currentState].radiusDenied();
+        //move to the next state
+        next(TRANSITION_DENY_ACCESS);
+
+        delegate.notify(new AuthenticationEvent(
+                AuthenticationEvent.Type.DENIED, supplicantConnectpoint));
+
+        // Clear mappings
+        deleteStateMachineMapping(this);
+    }
+
+    /**
+     * Logoff request has been requested.
+     * Move to the next state if possible.
+     *
+     * @throws StateMachineException if authentication protocol is violated
+     */
+    public void logoff() throws StateMachineException {
+        states[currentState].logoff();
+        //move to the next state
+        next(TRANSITION_LOGOFF);
+    }
+
+    /**
+     * Gets the current state.
+     *
+     * @return The current state. Could be STATE_IDLE, STATE_STARTED, STATE_PENDING, STATE_AUTHORIZED,
+     * STATE_UNAUTHORIZED.
+     */
+    public int state() {
+        return currentState;
+    }
+
+    @Override
+    public String toString() {
+        return ("sessionId: " + this.sessionId) + "\t" + ("identifier: " + this.identifier) + "\t" +
+                ("state: " + this.currentState);
+    }
+
+    abstract class State {
+        private final Logger log = getLogger(getClass());
+
+        private String name = "State";
+
+        public void start() throws StateMachineInvalidTransitionException {
+            log.warn("START transition from this state is not allowed.");
+        }
+
+        public void requestAccess() throws StateMachineInvalidTransitionException {
+            log.warn("REQUEST ACCESS transition from this state is not allowed.");
+        }
+
+        public void radiusAccepted() throws StateMachineInvalidTransitionException {
+            log.warn("AUTHORIZE ACCESS transition from this state is not allowed.");
+        }
+
+        public void radiusDenied() throws StateMachineInvalidTransitionException {
+            log.warn("DENY ACCESS transition from this state is not allowed.");
+        }
+
+        public void logoff() throws StateMachineInvalidTransitionException {
+            log.warn("LOGOFF transition from this state is not allowed.");
+        }
+    }
+
+    /**
+     * Idle state: supplicant is logged of from the network.
+     */
+    class Idle extends State {
+        private final Logger log = getLogger(getClass());
+        private String name = "IDLE_STATE";
+
+        public void start() {
+            log.info("Moving from IDLE state to STARTED state.");
+        }
+    }
+
+    /**
+     * Started state: supplicant has entered the network and informed the authenticator.
+     */
+    class Started extends State {
+        private final Logger log = getLogger(getClass());
+        private String name = "STARTED_STATE";
+
+        public void requestAccess() {
+            log.info("Moving from STARTED state to PENDING state.");
+        }
+    }
+
+    /**
+     * Pending state: supplicant has been identified by the authenticator but has not access yet.
+     */
+    class Pending extends State {
+        private final Logger log = getLogger(getClass());
+        private String name = "PENDING_STATE";
+
+        public void radiusAccepted() {
+            log.info("Moving from PENDING state to AUTHORIZED state.");
+        }
+
+        public void radiusDenied() {
+            log.info("Moving from PENDING state to UNAUTHORIZED state.");
+        }
+    }
+
+    /**
+     * Authorized state: supplicant port has been accepted, access is granted.
+     */
+    class Authorized extends State {
+        private final Logger log = getLogger(getClass());
+        private String name = "AUTHORIZED_STATE";
+
+        public void start() {
+            log.info("Moving from AUTHORIZED state to STARTED state.");
+        }
+
+        public void logoff() {
+
+            log.info("Moving from AUTHORIZED state to IDLE state.");
+        }
+    }
+
+    /**
+     * Unauthorized state: supplicant port has been rejected, access is denied.
+     */
+    class Unauthorized extends State {
+        private final Logger log = getLogger(getClass());
+        private String name = "UNAUTHORIZED_STATE";
+
+        public void start() {
+            log.info("Moving from UNAUTHORIZED state to STARTED state.");
+        }
+
+        public void logoff() {
+            log.info("Moving from UNAUTHORIZED state to IDLE state.");
+        }
+    }
+
+
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/StateMachineException.java b/app/src/main/java/org/opencord/aaa/impl/StateMachineException.java
new file mode 100644
index 0000000..6dd7bb4
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/StateMachineException.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * Exception for the State Machine.
+ */
+class StateMachineException extends Exception {
+    public StateMachineException(String message) {
+        super(message);
+
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/StateMachineInvalidTransitionException.java b/app/src/main/java/org/opencord/aaa/impl/StateMachineInvalidTransitionException.java
new file mode 100644
index 0000000..6ccd3fc
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/StateMachineInvalidTransitionException.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Exception raised when the transition from one state to another is invalid.
+ */
+class StateMachineInvalidTransitionException extends StateMachineException {
+    public StateMachineInvalidTransitionException(String message) {
+        super(message);
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/package-info.java b/app/src/main/java/org/opencord/aaa/impl/package-info.java
new file mode 100644
index 0000000..7737b52
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * AAA implmentation.
+ */
+package org.opencord.aaa.impl;