Several improvements for AAA

- Skip radius in 802.1x authentication (forgeEapol)
- Avoid deadlock in the IdentifierManager and optimizes locking
- Periodic pruning of the stalled auths
- Protection for the RadiusLister against the exceptions
- Check attributes before getting them
- Offload radius packet to another worker thread
- Improve unit tests

Change-Id: Idc4000dfb0a44f6a7fbc9aeea8ec754659f98545
diff --git a/app/src/main/java/org/opencord/aaa/impl/AaaManager.java b/app/src/main/java/org/opencord/aaa/impl/AaaManager.java
index 660ad33..d4f0fbb 100644
--- a/app/src/main/java/org/opencord/aaa/impl/AaaManager.java
+++ b/app/src/main/java/org/opencord/aaa/impl/AaaManager.java
@@ -121,6 +121,7 @@
         OPERATIONAL_STATUS_SERVER_TIMEOUT + ":Integer=" + OPERATIONAL_STATUS_SERVER_TIMEOUT_DEFAULT,
         STATUS_SERVER_MODE + ":String=" + STATUS_SERVER_MODE_DEFAULT,
         PACKET_PROCESSOR_THREADS + ":Integer=" + PACKET_PROCESSOR_THREADS_DEFAULT,
+        FORGE_EAPOL_PACKETS + ":Boolean=" + FORGE_EAPOL_PACKETS_DEFAULT,
 })
 public class AaaManager
         extends AbstractListenerManager<AuthenticationEvent, AuthenticationEventListener>
@@ -171,6 +172,12 @@
     private int operationalStatusEventGenerationPeriodInSeconds = OPERATIONAL_STATUS_SERVER_EVENT_GENERATION_DEFAULT;
     private int operationalStatusServerTimeoutInSeconds = OPERATIONAL_STATUS_SERVER_TIMEOUT_DEFAULT;
     protected String operationalStatusEvaluationMode = STATUS_SERVER_MODE_DEFAULT;
+
+    /**
+     * If set to true the RADIUS server won't be involved in authentication.
+     **/
+    private Boolean forgeEapolPackets = FORGE_EAPOL_PACKETS_DEFAULT;
+
     /**
      * Number of threads used to process the packet.
      */
@@ -329,7 +336,7 @@
         getConfiguredAaaServerAddress();
         radiusOperationalStatusService.initialize(nasIpAddress.getAddress(), radiusSecret, impl);
         serverStatusAndStateMachineTimeoutExecutor = Executors.newScheduledThreadPool(STATE_MACHINE_THREADS,
-                                              groupedThreads("onos/aaa", "aaa-machine-%d", log));
+              groupedThreads("onos/aaa", "machine-%d", log));
 
         scheduledStatusServerChecker = serverStatusAndStateMachineTimeoutExecutor.scheduleAtFixedRate(
                 new ServerStatusChecker(), 0,
@@ -369,6 +376,9 @@
         operationalStatusServerTimeoutInSeconds = Strings.isNullOrEmpty(s) ? OPERATIONAL_STATUS_SERVER_TIMEOUT_DEFAULT
                 : Integer.parseInt(s.trim());
 
+        Boolean p = Tools.isPropertyEnabled(properties, FORGE_EAPOL_PACKETS);
+        forgeEapolPackets = (p == null) ? FORGE_EAPOL_PACKETS_DEFAULT : p;
+
         s = Tools.get(properties, "operationalStatusEvaluationMode");
         String newEvaluationModeString = Strings.isNullOrEmpty(s) ? STATUS_SERVER_MODE_DEFAULT : s.trim();
 
@@ -392,7 +402,7 @@
                 packetProcessorExecutor.shutdown();
             }
             packetProcessorExecutor = newSingleThreadExecutor(
-                    groupedThreads("onos/aaa", "aaa-packet-%d", log));
+                    groupedThreads("onos/aaa", "packet-%d", log));
         }
     }
 
@@ -446,7 +456,20 @@
     private boolean checkResponseMessageAuthenticator(String key, RADIUS radiusPacket, byte[] requestAuthenticator) {
         byte[] newHash = new byte[16];
         Arrays.fill(newHash, (byte) 0);
-        byte[] messageAuthenticator = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH).getValue();
+        // looking for the attributes - exit if there are no such attributes
+        if (radiusPacket.getAttributeList(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH).isEmpty()) {
+            log.warn("Empty Attribute List for packet {} with identifier {}",
+                      radiusPacket, radiusPacket.getIdentifier());
+            return false;
+        }
+        // get the attribute - further verify if it is null or not (not really needed)
+        RADIUSAttribute attribute = radiusPacket.getAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH);
+        if (attribute == null) {
+            log.warn("Null Message Authenticator for packet {} with identifier {}",
+                      radiusPacket, radiusPacket.getIdentifier());
+            return false;
+        }
+        byte[] messageAuthenticator = attribute.getValue();
         byte[] authenticator = radiusPacket.getAuthenticator();
         radiusPacket.updateAttribute(RADIUSAttribute.RADIUS_ATTR_MESSAGE_AUTH, newHash);
         radiusPacket.setAuthenticator(requestAuthenticator);
@@ -495,13 +518,23 @@
      */
     public void handleRadiusPacket(RADIUS radiusPacket) {
         if (log.isTraceEnabled()) {
-            log.trace("Received RADIUS packet {}", radiusPacket);
+            log.trace("Received RADIUS packet {} with identifier {}",
+                      radiusPacket, radiusPacket.getIdentifier() & 0xff);
         }
         if (radiusOperationalStatusService.isRadiusResponseForOperationalStatus(radiusPacket.getIdentifier())) {
+            if (log.isTraceEnabled()) {
+                log.trace("Handling operational status RADIUS packet {} with identifier {}",
+                          radiusPacket, radiusPacket.getIdentifier() & 0xff);
+            }
             radiusOperationalStatusService.handleRadiusPacketForOperationalStatus(radiusPacket);
             return;
         }
 
+        if (log.isTraceEnabled()) {
+            log.trace("Handling actual RADIUS packet for supplicant {} with identifier {}",
+                      radiusPacket, radiusPacket.getIdentifier() & 0xff);
+        }
+
         RequestIdentifier identifier = RequestIdentifier.of(radiusPacket.getIdentifier());
         String sessionId = idManager.getSessionId(identifier);
 
@@ -638,7 +671,7 @@
                                          stateMachine.vlanId(),
                                          EAPOL.EAPOL_PACKET,
                                          eapPayload, stateMachine.priorityCode());
-                log.warn("Send EAP failure message to supplicant {} on dev/port: {}/{}" +
+                log.warn("Send EAP failure message to supplicant on dev/port: {}/{}" +
                                  " with MacAddress {} and Identifier {}", supplicantCp.deviceId(), supplicantCp.port(),
                          dstMac, stateMachine.challengeIdentifier() & 0xff);
                 sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint(), false);
@@ -685,8 +718,8 @@
                                                           treatment, ByteBuffer.wrap(ethernetPkt.serialize()));
         EAPOL eap = ((EAPOL) ethernetPkt.getPayload());
         if (log.isTraceEnabled()) {
-            log.trace("Sending eapol payload {} enclosed in {} to supplicant at {} with MacAddress {}",
-                      eap, ethernetPkt, connectPoint, ethernetPkt.getDestinationMAC());
+            log.trace("Sending eapol payload {} to supplicant at {} with MacAddress {}",
+                      eap, connectPoint, ethernetPkt.getDestinationMAC());
         }
         packetService.emit(packet);
         if (isChallengeResponse) {
@@ -740,35 +773,48 @@
         @Override
         public void process(PacketContext context) {
             packetProcessorExecutor.execute(() -> {
-                // Extract the original Ethernet frame from the packet information
-                InboundPacket pkt = context.inPacket();
-                if (pkt == null) {
-                    log.error("InboundPacket is null when parsed from {}", context);
-                    return;
-                }
-                Ethernet ethPkt = pkt.parsed();
-                if (ethPkt == null) {
-                    log.error("EthPkt is null when parsed from {}", pkt);
-                    return;
-                }
+                try {
+                    // Extract the original Ethernet frame from the packet information
+                    InboundPacket pkt = context.inPacket();
+                    if (pkt == null) {
+                        log.warn("Dropping inbound packet as it can't be parsed (inpacket)");
+                        return;
+                    }
+                    Ethernet ethPkt = pkt.parsed();
+                    if (ethPkt == null) {
+                        log.warn("Dropping inbound packet as it can't be parsed (ethpacket)");
+                        return;
+                    }
 
-                // identify if incoming packet comes from supplicant (EAP) or RADIUS
-                switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
-                    case EAPOL:
-                        if (log.isTraceEnabled()) {
-                            log.trace("Received EAPOL supplicant packet from dev/port: {} with MacAddress {}",
-                                      context.inPacket().receivedFrom(), ethPkt.getSourceMAC());
-                        }
-                        handleSupplicantPacket(context.inPacket());
-                        break;
-                    default:
-                        if (log.isTraceEnabled()) {
-                            log.trace("Received packet-in from RADIUS server {} in enclosing packet {} from "
-                                              + "dev/port: {} with MacAddress {}", ethPkt, context.inPacket(),
-                                      context.inPacket().receivedFrom(), ethPkt.getSourceMAC());
-                        }
-                        // any other packets let the specific implementation handle
-                        impl.handlePacketFromServer(context);
+                    EthType.EtherType pktType;
+                    try {
+                        short ethType = ethPkt.getEtherType();
+                        pktType = EthType.EtherType.lookup(ethType);
+                    } catch (Exception e) {
+                        log.error("Exception while reading packet type", e);
+                        return;
+                    }
+
+                    // identify if incoming packet comes from supplicant (EAP) or RADIUS
+                    switch (pktType) {
+                        case EAPOL:
+                            if (log.isTraceEnabled()) {
+                                log.trace("Received EAPOL supplicant packet from dev/port: {} with MacAddress {}",
+                                          context.inPacket().receivedFrom(), ethPkt.getSourceMAC());
+                            }
+                            handleSupplicantPacket(context.inPacket());
+                            break;
+                        default:
+                            // any other packets let the specific implementation handle
+                            if (log.isTraceEnabled()) {
+                                log.trace("Received packet-in from RADIUS server {} in enclosing packet {} from "
+                                                  + "dev/port: {} with MacAddress {}", ethPkt, context.inPacket(),
+                                          context.inPacket().receivedFrom(), ethPkt.getSourceMAC());
+                            }
+                            impl.handlePacketFromServer(context);
+                    }
+                } catch (Exception e) {
+                    log.error("Error while processing packet", e);
                 }
             });
         }
@@ -800,6 +846,107 @@
             return radiusPayload;
         }
 
+        private void handleEapolStart(InboundPacket inPacket, StateMachine stateMachine) {
+
+            DeviceId deviceId = inPacket.receivedFrom().deviceId();
+            PortNumber portNumber = inPacket.receivedFrom().port();
+            Ethernet ethPkt = inPacket.parsed();
+            MacAddress srcMac = ethPkt.getSourceMAC();
+
+            log.debug("EAP packet: EAPOL_START from dev/port: {}/{} with MacAddress {}",
+                      deviceId, portNumber, srcMac);
+            stateMachine.setSupplicantConnectpoint(inPacket.receivedFrom());
+            stateMachine.setSupplicantAddress(srcMac);
+            stateMachine.start();
+
+            aaaStatisticsManager.getAaaStats().incrementEapolStartReqRx();
+            //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.setVlanId(ethPkt.getVlanID());
+            log.debug("Getting EAP identity from supplicant {}", stateMachine.supplicantAddress().toString());
+            sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint(), false);
+            aaaStatisticsManager.getAaaStats().incrementRequestIdFramesTx();
+        }
+
+        private void hangleEapolLogoff(InboundPacket inPacket, StateMachine stateMachine) {
+
+            DeviceId deviceId = inPacket.receivedFrom().deviceId();
+            PortNumber portNumber = inPacket.receivedFrom().port();
+            Ethernet ethPkt = inPacket.parsed();
+            MacAddress srcMac = ethPkt.getSourceMAC();
+
+            log.debug("EAP packet: EAPOL_LOGOFF from dev/port: {}/{} with MacAddress {}",
+                      deviceId, portNumber, srcMac);
+            //posting the machine stat data for current supplicant device.
+            if (stateMachine.getSessionTerminateReason() == null ||
+                    stateMachine.getSessionTerminateReason().equals("")) {
+                stateMachine.setSessionTerminateReason(
+                        StateMachine.SessionTerminationReasons.SUPPLICANT_LOGOFF.getReason());
+            }
+            AaaSupplicantMachineStats obj = aaaSupplicantStatsManager.getSupplicantStats(stateMachine);
+            aaaSupplicantStatsManager.getMachineStatsDelegate()
+                    .notify(new AaaMachineStatisticsEvent(AaaMachineStatisticsEvent.Type.STATS_UPDATE, obj));
+            if (stateMachine.state() == StateMachine.STATE_AUTHORIZED) {
+                stateMachine.logoff();
+                aaaStatisticsManager.getAaaStats().incrementEapolLogoffRx();
+            }
+            if (stateMachine.state() == StateMachine.STATE_IDLE) {
+                aaaStatisticsManager.getAaaStats().incrementAuthStateIdle();
+            }
+        }
+
+        private void handleForgedEapolChallengeAuth(StateMachine stateMachine) {
+            stateMachine.requestAccess();
+
+            log.info("Forging EAP auth challenge");
+            byte[] challengeState = EapolPacketGenerator.hexStringToByteArray("19056d66190469d738db2f7dc1e02591");
+            EAP eapPayload = EapolPacketGenerator.forgeEapolChallengeAuth();
+
+            Ethernet eth = buildEapolResponse(stateMachine.supplicantAddress(),
+                                              MacAddress.valueOf(nasMacAddress),
+                                              stateMachine.vlanId(),
+                                              EAPOL.EAPOL_PACKET,
+                                              eapPayload, stateMachine.priorityCode());
+            stateMachine.setChallengeInfo(eapPayload.getIdentifier(), challengeState);
+
+            ConnectPoint supplicantCp = stateMachine.supplicantConnectpoint();
+            MacAddress dstMac = stateMachine.supplicantAddress();
+            log.info("Send FORGED EAP auth challenge to supplicant {} on dev/port: {}/{} with MacAddress {}",
+                     supplicantCp.deviceId(), supplicantCp.port(), dstMac);
+
+            sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint(),
+                                   true);
+
+            // NOTE do we care about stats?
+        }
+
+        private void handleForgedEapolSuccess(StateMachine stateMachine) {
+            ConnectPoint supplicantCp = stateMachine.supplicantConnectpoint();
+            MacAddress dstMac = stateMachine.supplicantAddress();
+            log.info("Forging EAP auth success");
+            EAP eapPayload = EapolPacketGenerator.forgeEapolSuccess();
+
+            Ethernet eth = buildEapolResponse(stateMachine.supplicantAddress(),
+                                              MacAddress.valueOf(nasMacAddress),
+                                              stateMachine.vlanId(),
+                                              EAPOL.EAPOL_PACKET,
+                                              eapPayload, stateMachine.priorityCode());
+            log.info("Send FORGED EAP success message to supplicant {} on dev/port: {}/{} with MacAddress {}",
+                     supplicantCp.deviceId(), supplicantCp.port(), dstMac);
+            sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint(), false);
+            aaaStatisticsManager.getAaaStats().incrementEapolAuthSuccessTrans();
+
+            stateMachine.authorizeAccess();
+        }
+
+
         /**
          * Handles PAE packets (supplicant).
          *
@@ -826,13 +973,14 @@
             byte[] eapPayLoadBuffer = eapol.serialize();
             int len = eapPayLoadBuffer.length;
             if (len != (HEADER_LENGTH + pktlen)) {
-                log.error("Invalid EAPOL pkt length {} (shoudl be {}) for packet {} from dev/port: {}/{} " +
-                          "with MacAddress {}", len, HEADER_LENGTH + pktlen, eapol, deviceId, portNumber, srcMac);
+                log.warn("Invalid EAPOL pkt length {} (shoudl be {}) for packet {} from dev/port: {}/{} " +
+                          "with MacAddress {}, dropping it",
+                          len, HEADER_LENGTH + pktlen, eapol, deviceId, portNumber, srcMac);
                 aaaStatisticsManager.getAaaStats().incrementInvalidBodyLength();
                 return;
             }
             if (!VALID_EAPOL_TYPE.contains(eapol.getEapolType())) {
-                log.error("Invalid EAPOL Type {} for packet {} from dev/port: {}/{} with MacAddress {}",
+                log.warn("Invalid EAPOL Type {} for packet {} from dev/port: {}/{} with MacAddress {}, dropping it",
                           eapol.getEapolType(), eapol, deviceId, portNumber, srcMac);
                 aaaStatisticsManager.getAaaStats().incrementInvalidPktType();
                 return;
@@ -846,75 +994,34 @@
 
             switch (eapol.getEapolType()) {
                 case EAPOL.EAPOL_START:
-                    log.debug("EAP packet: EAPOL_START from dev/port: {}/{} with MacAddress {}",
-                              deviceId, portNumber, srcMac);
-                    stateMachine.setSupplicantConnectpoint(inPacket.receivedFrom());
-                    stateMachine.setSupplicantAddress(srcMac);
-                    stateMachine.start();
-
-                    aaaStatisticsManager.getAaaStats().incrementEapolStartReqRx();
-                    //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.setVlanId(ethPkt.getVlanID());
-                    log.debug("Getting EAP identity from supplicant {} and Identifier {}",
-                              stateMachine.supplicantAddress().toString(), stateMachine.identifier() & 0xff);
-                    sendPacketToSupplicant(eth, stateMachine.supplicantConnectpoint(), false);
-                    aaaStatisticsManager.getAaaStats().incrementRequestIdFramesTx();
+                    handleEapolStart(inPacket, stateMachine);
                     break;
                 case EAPOL.EAPOL_LOGOFF:
-                    log.debug("EAP packet: EAPOL_LOGOFF from dev/port: {}/{} with MacAddress {}",
-                              deviceId, portNumber, srcMac);
-                    //posting the machine stat data for current supplicant device.
-                    if (stateMachine.getSessionTerminateReason() == null ||
-                            stateMachine.getSessionTerminateReason().equals("")) {
-                        stateMachine.setSessionTerminateReason(
-                                StateMachine.SessionTerminationReasons.SUPPLICANT_LOGOFF.getReason());
-                    }
-                    AaaSupplicantMachineStats obj = aaaSupplicantStatsManager.getSupplicantStats(stateMachine);
-                    aaaSupplicantStatsManager.getMachineStatsDelegate()
-                            .notify(new AaaMachineStatisticsEvent(AaaMachineStatisticsEvent.Type.STATS_UPDATE, obj));
-                    if (stateMachine.state() == StateMachine.STATE_AUTHORIZED) {
-                        stateMachine.logoff();
-                        aaaStatisticsManager.getAaaStats().incrementEapolLogoffRx();
-                    }
-                    if (stateMachine.state() == StateMachine.STATE_IDLE) {
-                        aaaStatisticsManager.getAaaStats().incrementAuthStateIdle();
-                    }
+                    hangleEapolLogoff(inPacket, stateMachine);
                     break;
                 case EAPOL.EAPOL_PACKET:
 
                     // check if this is a Response/Identify or  a Response/TLS
                     EAP eapPacket = (EAP) eapol.getPayload();
                     Byte identifier = new Byte(eapPacket.getIdentifier());
+
                     log.debug("EAP packet: EAPOL_PACKET from dev/port: {}/{} with MacAddress {} with Identifier {}",
                               deviceId, portNumber, srcMac, identifier.doubleValue());
-                    // get identifier for request and store mapping to session ID
-                    RequestIdentifier radiusIdentifier = idManager.getNewIdentifier(sessionId);
-                    if (radiusIdentifier == null) {
-                        log.error("Cannot get RADIUS Identifier, dropping packet");
-                        return;
-                    }
 
                     byte dataType = eapPacket.getDataType();
 
                     switch (dataType) {
                         case EAP.ATTR_IDENTITY:
                             handleAttrIdentity(inPacket, srcMac, deviceId, portNumber,
-                                               eapol, stateMachine, eapPacket, radiusIdentifier);
+                                               eapol, stateMachine, eapPacket, sessionId);
                             break;
                         case EAP.ATTR_MD5:
                             handleMD5(inPacket, srcMac, deviceId, portNumber, stateMachine,
-                                      eapPacket, identifier, radiusIdentifier);
+                                      eapPacket, identifier, sessionId);
                             break;
                         case EAP.ATTR_TLS:
                             handleTls(inPacket, srcMac, deviceId, portNumber, stateMachine,
-                                      eapPacket, identifier, radiusIdentifier);
+                                      eapPacket, identifier, sessionId);
                             break;
                         default:
                             log.warn("Unknown EAP packet type from dev/port: {}/{} with MacAddress {} and " +
@@ -932,44 +1039,65 @@
 
         private void handleAttrIdentity(InboundPacket inPacket, MacAddress srcMac, DeviceId deviceId,
                                         PortNumber portNumber, EAPOL eapol, StateMachine stateMachine,
-                                        EAP eapPacket, RequestIdentifier radiusIdentifier) {
-            RADIUS radiusPayload;
-            log.debug("EAP packet: EAPOL_PACKET ATTR_IDENTITY from dev/port: {}/{} with MacAddress {}" +
-                              " and Identifier {}", deviceId, portNumber, srcMac, eapPacket.getIdentifier() & 0xff);
-            //Setting the time of this response from RG, only when its not a re-transmission.
-            if (stateMachine.getLastPacketReceivedTime() == 0) {
-                stateMachine.setLastPacketReceivedTime(System.currentTimeMillis());
-            }
-            // request id access to RADIUS
-            stateMachine.setUsername(eapPacket.getData());
+                                        EAP eapPacket, String sessionId) {
 
-            radiusPayload = getRadiusPayload(stateMachine, radiusIdentifier.identifier(), eapPacket);
-            radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
-            radiusPayload.addMessageAuthenticator(radiusSecret);
+            if (forgeEapolPackets) {
+                handleForgedEapolChallengeAuth(stateMachine);
+                return;
+            } else {
+                // get identifier for request and store mapping to session ID
+                RequestIdentifier radiusIdentifier = idManager.getNewIdentifier(sessionId);
+                if (radiusIdentifier == null) {
+                    log.warn("Cannot get identifier supplicant at dev/port: {}/{} " +
+                                      "with MacAddress {}, dropping packet",
+                              deviceId, portNumber, srcMac);
+                    return;
+                }
+                log.debug("EAP packet: EAPOL_PACKET ATTR_IDENTITY from dev/port: {}/{} with MacAddress {}" +
+                                  " and Identifier {}", deviceId, portNumber, srcMac, eapPacket.getIdentifier() & 0xff);
+                //Setting the time of this response from RG, only when its not a re-transmission.
+                if (stateMachine.getLastPacketReceivedTime() == 0) {
+                    stateMachine.setLastPacketReceivedTime(System.currentTimeMillis());
+                }
+                // request id access to RADIUS
+                stateMachine.setUsername(eapPacket.getData());
 
-            if (log.isTraceEnabled()) {
-                log.trace("Sending ATTR_IDENTITY packet to RADIUS for supplicant at dev/port: " +
-                                  "{}/{} with MacAddress {} and Identifier {}", deviceId, portNumber,
-                          srcMac, radiusIdentifier.identifier() & 0xff);
-            }
+                RADIUS radiusPayload = getRadiusPayload(stateMachine, radiusIdentifier.identifier(), eapPacket);
+                radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
+                radiusPayload.addMessageAuthenticator(radiusSecret);
 
-            sendRadiusPacket(radiusPayload, inPacket);
-            stateMachine.setWaitingForRadiusResponse(true);
-            aaaStatisticsManager.getAaaStats().incrementRadiusReqIdTx();
-            aaaStatisticsManager.getAaaStats().incrementEapolAtrrIdentity();
-            // change the state to "PENDING"
-            if (stateMachine.state() == StateMachine.STATE_PENDING) {
-                aaaStatisticsManager.getAaaStats().increaseRequestReTx();
-                stateMachine.incrementTotalPacketsSent();
-                stateMachine.incrementTotalOctetSent(eapol.getPacketLength());
+                if (log.isTraceEnabled()) {
+                    log.trace("Sending ATTR_IDENTITY packet to RADIUS for supplicant at dev/port: " +
+                                      "{}/{} with MacAddress {} and Identifier {}", deviceId, portNumber,
+                              srcMac, radiusIdentifier.identifier() & 0xff);
+                }
+
+                sendRadiusPacket(radiusPayload, inPacket);
+                stateMachine.setWaitingForRadiusResponse(true);
+                aaaStatisticsManager.getAaaStats().incrementRadiusReqIdTx();
+                aaaStatisticsManager.getAaaStats().incrementEapolAtrrIdentity();
+                // change the state to "PENDING"
+                if (stateMachine.state() == StateMachine.STATE_PENDING) {
+                    aaaStatisticsManager.getAaaStats().increaseRequestReTx();
+                    stateMachine.incrementTotalPacketsSent();
+                    stateMachine.incrementTotalOctetSent(eapol.getPacketLength());
+                }
+                stateMachine.requestAccess();
             }
-            stateMachine.requestAccess();
         }
 
         private void handleMD5(InboundPacket inPacket, MacAddress srcMac, DeviceId deviceId,
                                PortNumber portNumber, StateMachine stateMachine, EAP eapPacket,
-                               Byte identifier, RequestIdentifier radiusIdentifier) {
-            RADIUS radiusPayload;
+                               Byte identifier, String sessionId) {
+            // get identifier for request and store mapping to session ID
+            RequestIdentifier radiusIdentifier = idManager.getNewIdentifier(sessionId);
+            if (radiusIdentifier == null) {
+                log.warn("Cannot get identifier supplicant at dev/port: {}/{} " +
+                                  "with MacAddress {}, dropping packet",
+                          deviceId, portNumber, srcMac);
+                return;
+            }
+
             log.debug("EAP packet: EAPOL_PACKET ATTR_MD5 from dev/port: {}/{} with MacAddress {}" +
                               " and Identifier {}", deviceId, portNumber, srcMac, eapPacket.getIdentifier() & 0xff);
             // verify if the EAP identifier corresponds to the
@@ -978,28 +1106,32 @@
             stateMachine.setLastPacketReceivedTime(System.currentTimeMillis());
             if (eapPacket.getIdentifier() == stateMachine.challengeIdentifier()) {
                 //send the RADIUS challenge response
-                radiusPayload = getRadiusPayload(stateMachine,
-                                                 radiusIdentifier.identifier(), eapPacket);
-                radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
+                if (forgeEapolPackets) {
+                    handleForgedEapolSuccess(stateMachine);
+                } else {
+                    RADIUS radiusPayload = getRadiusPayload(stateMachine,
+                                                            radiusIdentifier.identifier(), eapPacket);
+                    radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
 
-                if (stateMachine.challengeState() != null) {
-                    radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
-                                               stateMachine.challengeState());
+                    if (stateMachine.challengeState() != null) {
+                        radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
+                                                   stateMachine.challengeState());
+                    }
+                    radiusPayload.addMessageAuthenticator(radiusSecret);
+                    if (outPacketSupp.contains(eapPacket.getIdentifier())) {
+                        aaaStatisticsManager.getAaaStats().decrementPendingReqSupp();
+                        outPacketSupp.remove(identifier);
+                    }
+                    if (log.isTraceEnabled()) {
+                        log.trace("Sending ATTR_MD5 packet to RADIUS for supplicant at dev/port: {}/{}" +
+                                          " with MacAddress {} and Identifier {}", deviceId, portNumber, srcMac,
+                                  radiusIdentifier.identifier() & 0xff);
+                    }
+                    sendRadiusPacket(radiusPayload, inPacket);
+                    stateMachine.setWaitingForRadiusResponse(true);
+                    aaaStatisticsManager.getAaaStats().incrementRadiusReqChallengeTx();
+                    aaaStatisticsManager.getAaaStats().incrementEapolMd5RspChall();
                 }
-                radiusPayload.addMessageAuthenticator(radiusSecret);
-                if (outPacketSupp.contains(eapPacket.getIdentifier())) {
-                    aaaStatisticsManager.getAaaStats().decrementPendingReqSupp();
-                    outPacketSupp.remove(identifier);
-                }
-                if (log.isTraceEnabled()) {
-                    log.trace("Sending ATTR_MD5 packet to RADIUS for supplicant at dev/port: {}/{}" +
-                                      " with MacAddress {} and Identifier {}", deviceId, portNumber, srcMac,
-                              radiusIdentifier.identifier() & 0xff);
-                }
-                sendRadiusPacket(radiusPayload, inPacket);
-                stateMachine.setWaitingForRadiusResponse(true);
-                aaaStatisticsManager.getAaaStats().incrementRadiusReqChallengeTx();
-                aaaStatisticsManager.getAaaStats().incrementEapolMd5RspChall();
             } else {
                 log.error("eapolIdentifier {} and stateMachine Identifier {} do not " +
                                   "correspond for packet from dev/port: {}/{} with MacAddress {}",
@@ -1017,12 +1149,19 @@
 
         private void handleTls(InboundPacket inPacket, MacAddress srcMac, DeviceId deviceId,
                                PortNumber portNumber, StateMachine stateMachine, EAP eapPacket,
-                               Byte identifier, RequestIdentifier radiusIdentifier) {
-            RADIUS radiusPayload;
+                               Byte identifier, String sessionId) {
+            // get identifier for request and store mapping to session ID
+            RequestIdentifier radiusIdentifier = idManager.getNewIdentifier(sessionId);
+
+            if (radiusIdentifier == null) {
+                log.warn("Cannot get identifier supplicant at dev/port: {}/{} " +
+                                  "with MacAddress {}, dropping packet", deviceId, portNumber, srcMac);
+                return;
+            }
             log.debug("EAP packet: EAPOL_PACKET ATTR_TLS from dev/port: {}/{} with MacAddress {} " +
                               "and Identifier {}", deviceId, portNumber, srcMac, eapPacket.getIdentifier() & 0xff);
             // request id access to RADIUS
-            radiusPayload = getRadiusPayload(stateMachine, radiusIdentifier.identifier(), eapPacket);
+            RADIUS radiusPayload = getRadiusPayload(stateMachine, radiusIdentifier.identifier(), eapPacket);
             radiusPayload = pktCustomizer.customizePacket(radiusPayload, inPacket);
             if (stateMachine.challengeState() != null) {
                 radiusPayload.setAttribute(RADIUSAttribute.RADIUS_ATTR_STATE,
diff --git a/app/src/main/java/org/opencord/aaa/impl/EapolPacketGenerator.java b/app/src/main/java/org/opencord/aaa/impl/EapolPacketGenerator.java
new file mode 100644
index 0000000..5a64bf0
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/EapolPacketGenerator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.EAP;
+
+/**
+ * Class that is responsible to generate fake EAPOL packets in case that
+ * FORGE_EAPOL_PACKETS is set to true in the config.
+ */
+public final class EapolPacketGenerator {
+
+    private EapolPacketGenerator() {}
+
+    public static EAP forgeEapolChallengeAuth() {
+        EAP eapPayload = new EAP(
+                new Integer(1).byteValue(), new Integer(1).byteValue(),
+                new Integer(4).byteValue(),
+                hexStringToByteArray("108b4eb55f859c501b3e14a594c4997bed"));
+        return eapPayload;
+    }
+
+    public static EAP forgeEapolSuccess() {
+        EAP eapPayload = new EAP(
+                new Integer(3).byteValue(),
+                new Integer(2).byteValue(),
+                new Integer(0).byteValue(),
+                hexStringToByteArray("")
+        );
+        return eapPayload;
+    }
+
+    public static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                    + Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+}
diff --git a/app/src/main/java/org/opencord/aaa/impl/IdentifierManager.java b/app/src/main/java/org/opencord/aaa/impl/IdentifierManager.java
index 27f824f..6b4bffb 100644
--- a/app/src/main/java/org/opencord/aaa/impl/IdentifierManager.java
+++ b/app/src/main/java/org/opencord/aaa/impl/IdentifierManager.java
@@ -16,22 +16,43 @@
 
 package org.opencord.aaa.impl;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+import org.slf4j.Logger;
 
+import java.util.Iterator;
+import java.util.Map;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import static org.onlab.util.Tools.groupedThreads;
+import static org.slf4j.LoggerFactory.getLogger;
 
 /**
  * Manages allocating request identifiers and mapping them to sessions.
  */
 public class IdentifierManager {
 
+    private final Logger log = getLogger(getClass());
+
     private static final int MAX_IDENTIFIER = 256;
 
     private BlockingQueue<Integer> freeIdNumbers;
 
-    private ConcurrentMap<RequestIdentifier, String> idToSession;
+    private ConcurrentMap<RequestIdentifier, Pair<String, Long>> idToSession;
+
+    ScheduledFuture<?> scheduledidentifierPruner;
+
+    // TODO read from component config
+    protected static int timeout = 10000;
+    protected static int pollTimeout = 2;
+    protected static int pruningInterval = 3;
 
     /**
      * Creates and initializes a new identifier manager.
@@ -44,6 +65,13 @@
         for (int i = 2; i < MAX_IDENTIFIER; i++) {
             freeIdNumbers.add(i);
         }
+
+        ScheduledExecutorService identifierPruner = Executors.newSingleThreadScheduledExecutor(
+                groupedThreads("onos/aaa", "idpruner-%d", log));
+
+        scheduledidentifierPruner = identifierPruner.scheduleAtFixedRate(
+                new IdentifierPruner(), 0,
+                pruningInterval, TimeUnit.SECONDS);
     }
 
     /**
@@ -52,19 +80,43 @@
      * @param sessionId session this identifier is associated with
      * @return identifier
      */
-    public synchronized RequestIdentifier getNewIdentifier(String sessionId) {
-        int idNum;
+    public RequestIdentifier getNewIdentifier(String sessionId) {
+        // Run this part without the lock.
+        Integer idNum;
         try {
-            idNum = freeIdNumbers.take();
+            idNum = freeIdNumbers.poll(pollTimeout, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
+            // Interrupted case
+            if (log.isTraceEnabled()) {
+                log.trace("Interrupted while waiting for an id");
+            }
             return null;
         }
+        // Timeout let's exit
+        if (idNum == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Timedout there are no available ids");
+            }
+            return null;
+        }
+        // Start of the synchronized zone. Real contention happens here.
+        // This thread wants to update the session map. The release thread
+        // wants to update the session map first and then free the id. Same
+        // for the pruner. If this thread is interrupted here is not a big issue
+        // its update is not yet visible for the remaining threads: i) the
+        // release thread cannot release an id not yet taken; ii) the pruning
+        // thread cannot prune for the same reason.
+        synchronized (this) {
+            if (log.isTraceEnabled()) {
+                log.trace("Got identifier {} for session {}", idNum, sessionId);
+            }
 
-        RequestIdentifier id = RequestIdentifier.of((byte) idNum);
+            RequestIdentifier id = RequestIdentifier.of((byte) idNum.intValue());
 
-        idToSession.put(id, sessionId);
+            idToSession.put(id, Pair.of(sessionId, System.currentTimeMillis()));
 
-        return id;
+            return id;
+        }
     }
 
     /**
@@ -73,8 +125,9 @@
      * @param id request ID
      * @return session ID
      */
-    public String getSessionId(RequestIdentifier id) {
-        return idToSession.get(id);
+    public synchronized String getSessionId(RequestIdentifier id) {
+        //TODO this has multiple accesses
+        return idToSession.get(id) == null ? null : idToSession.get(id).getKey();
     }
 
     /**
@@ -83,13 +136,76 @@
      * @param id request identifier to release
      */
     public synchronized void releaseIdentifier(RequestIdentifier id) {
-        String session = idToSession.remove(id);
+        if (log.isTraceEnabled()) {
+            log.trace("Releasing identifier {}", id.identifier() & 0xff);
+        }
+
+        Pair<String, Long> session = idToSession.remove(id);
         if (session == null) {
+            if (log.isTraceEnabled()) {
+                log.trace("Unable to released identifier {} for session null", id.identifier() & 0xff);
+            }
             // this id wasn't mapped to a session so is still free
             return;
         }
 
         // add id number back to set of free ids
-        freeIdNumbers.add((int) id.identifier());
+        freeIdNumbers.add(id.identifier() & 0xff);
+
+        if (log.isTraceEnabled()) {
+            log.trace("Released identifier {} for session {}", id.identifier() & 0xff, session.getKey());
+        }
+    }
+
+    /**
+     * Returns true if this ID is currently taken.
+     *
+     * @param id request identifier to check
+     * @return boolean
+     */
+    private boolean isIdentifierTaken(Integer id) {
+        return !freeIdNumbers.contains(id);
+    }
+
+    /**
+     * Returns the count of available identifiers in a given moment.
+     *
+     * @return boolean
+     */
+    public int getAvailableIdentifiers() {
+        return freeIdNumbers.size();
+    }
+
+    private synchronized void pruneIfNeeded() {
+        if (log.isTraceEnabled()) {
+            log.trace("Starting pruning cycle");
+        }
+        // Gets an immutable copy of the ids and release the ones that exceed the timeout
+        Map<RequestIdentifier, Pair<String, Long>> idsToCheck = ImmutableMap.copyOf(idToSession);
+        // We should not modify while iterating - this is why we get a copy
+        Iterator<Map.Entry<RequestIdentifier, Pair<String, Long>>> itr = idsToCheck.entrySet().iterator();
+        itr.forEachRemaining((entry) -> {
+            RequestIdentifier id = entry.getKey();
+            Pair<String, Long> info = entry.getValue();
+            long diff = System.currentTimeMillis() - info.getValue();
+            if (diff >= timeout) {
+                if (log.isTraceEnabled()) {
+                    log.trace("Identifier {} for session {} has exceeded timeout {}, releasing",
+                            id.identifier() & 0xff, info.getKey(), timeout);
+                }
+                releaseIdentifier(id);
+            }
+        });
+        if (log.isTraceEnabled()) {
+            log.trace("End pruning cycle");
+        }
+    }
+
+    private class IdentifierPruner implements Runnable {
+        @Override
+        public void run() {
+            pruneIfNeeded();
+        }
+
     }
 }
diff --git a/app/src/main/java/org/opencord/aaa/impl/OsgiPropertyConstants.java b/app/src/main/java/org/opencord/aaa/impl/OsgiPropertyConstants.java
index 71826c0..e6544ad 100644
--- a/app/src/main/java/org/opencord/aaa/impl/OsgiPropertyConstants.java
+++ b/app/src/main/java/org/opencord/aaa/impl/OsgiPropertyConstants.java
@@ -42,4 +42,7 @@
 
     public static final String PACKET_PROCESSOR_THREADS = "packetProcessorThreads";
     public static final int PACKET_PROCESSOR_THREADS_DEFAULT = 10;
+
+    public static final String FORGE_EAPOL_PACKETS = "forgeEapolPackets";
+    public static final boolean FORGE_EAPOL_PACKETS_DEFAULT = false;
 }
diff --git a/app/src/main/java/org/opencord/aaa/impl/RequestIdentifier.java b/app/src/main/java/org/opencord/aaa/impl/RequestIdentifier.java
index 9869af1..564c45a 100644
--- a/app/src/main/java/org/opencord/aaa/impl/RequestIdentifier.java
+++ b/app/src/main/java/org/opencord/aaa/impl/RequestIdentifier.java
@@ -18,6 +18,8 @@
 
 import java.util.Objects;
 
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * An identifier for an authentication request.
  */
@@ -30,7 +32,7 @@
      *
      * @param identifier id number
      */
-    private RequestIdentifier(byte identifier) {
+    public RequestIdentifier(byte identifier) {
         this.identifier = identifier;
     }
 
@@ -53,6 +55,7 @@
         return new RequestIdentifier(identifier);
     }
 
+    @Override
     public boolean equals(Object other) {
         if (!(other instanceof RequestIdentifier)) {
             return false;
@@ -63,7 +66,15 @@
         return identifier == that.identifier;
     }
 
+    @Override
     public int hashCode() {
         return Objects.hashCode(identifier);
     }
+
+    @Override
+    public String toString() {
+        return toStringHelper(getClass())
+                .add("identifier", Byte.toString(identifier))
+                .toString();
+    }
 }
diff --git a/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java b/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java
index 0414cac..792b7e4 100755
--- a/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java
+++ b/app/src/main/java/org/opencord/aaa/impl/SocketBasedRadiusCommunicator.java
@@ -15,7 +15,6 @@
  */
 package org.opencord.aaa.impl;
 
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import org.onlab.packet.DeserializationException;
 import org.onlab.packet.EthType;
 import org.onlab.packet.Ethernet;
@@ -35,10 +34,12 @@
 import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
 
+import static java.util.concurrent.Executors.newSingleThreadExecutor;
+import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.packet.PacketPriority.CONTROL;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -70,6 +71,12 @@
     // Executor for RADIUS communication thread
     private ExecutorService executor;
 
+    // Worker thread for RADIUS communication
+    private ExecutorService worker;
+
+    // To track the received packets
+    int packetNumber = 1;
+
     AaaManager aaaManager;
 
     SocketBasedRadiusCommunicator(ApplicationId appId, PacketService pktService,
@@ -97,10 +104,9 @@
 
         log.info("Remote RADIUS Server: {}:{}", radiusIpAddress, radiusServerPort);
 
-        executor = Executors.newSingleThreadExecutor(
-                new ThreadFactoryBuilder()
-                        .setNameFormat("AAA-radius-%d").build());
+        executor = newSingleThreadExecutor(groupedThreads("onos/aaa", "radius-%d", log));
         executor.execute(radiusListener);
+        worker = newSingleThreadExecutor(groupedThreads("onos/aaa", "radius-packet-%d", log));
     }
 
     @Override
@@ -108,6 +114,7 @@
         log.info("Closing RADIUS socket: {}:{}", radiusIpAddress, radiusServerPort);
         radiusSocket.close();
         executor.shutdownNow();
+        worker.shutdownNow();
     }
 
     @Override
@@ -155,10 +162,12 @@
                 aaaManager.radiusOperationalStatusService.setStatusServerReqSent(false);
             }
         } catch (IOException e) {
-            log.warn("Cannot send packet to RADIUS server", e);
+            log.error("Cannot send packet to RADIUS server", e);
         }
     }
 
+    // in the socket base case we don't care about packets coming from the server as nothing meaningful will be
+    // received from the southbound
     @Override
     public void handlePacketFromServer(PacketContext context) {
         InboundPacket pkt = context.inPacket();
@@ -170,43 +179,50 @@
         }
     }
 
+    // Handle radius packet for further processing
+    private void handleRadiusPacketInternal(DatagramPacket inboundBasePacket) {
+        RADIUS inboundRadiusPacket;
+        aaaManager.checkForPacketFromUnknownServer(inboundBasePacket.getAddress().getHostAddress());
+        log.debug("Packet #{} received", packetNumber++);
+        try {
+            inboundRadiusPacket = RADIUS.deserializer().deserialize(inboundBasePacket.getData(),
+                    0, inboundBasePacket.getLength());
+            if (log.isTraceEnabled()) {
+                log.trace("Handling inboundRadiusPacket {} with identifier {}", inboundRadiusPacket,
+                    inboundRadiusPacket.getIdentifier() & 0xff);
+            }
+            aaaManager.aaaStatisticsManager.handleRoundtripTime(inboundRadiusPacket.getIdentifier());
+            aaaManager.handleRadiusPacket(inboundRadiusPacket);
+        } catch (DeserializationException dex) {
+            aaaManager.aaaStatisticsManager.getAaaStats().increaseMalformedResponsesRx();
+            log.warn("Cannot deserialize packet", dex);
+        }
+    }
+
     class RadiusListener implements Runnable {
 
         @Override
         public void run() {
             boolean done = false;
-            int packetNumber = 1;
-
-            log.info("UDP listener thread starting up");
-            RADIUS inboundRadiusPacket;
+            try {
+                log.info("UDP listener thread starting up, socket buffer size {}",
+                         radiusSocket.getReceiveBufferSize());
+            } catch (SocketException e) {
+                log.error("Socket exception", e);
+            }
             while (!done) {
                 try {
                     byte[] packetBuffer = new byte[RADIUS.RADIUS_MAX_LENGTH];
-                    DatagramPacket inboundBasePacket =
-                            new DatagramPacket(packetBuffer, packetBuffer.length);
+                    DatagramPacket inboundBasePacket = new DatagramPacket(packetBuffer, packetBuffer.length);
                     DatagramSocket socket = radiusSocket;
                     socket.receive(inboundBasePacket);
-                    aaaManager.checkForPacketFromUnknownServer(inboundBasePacket.getAddress().getHostAddress());
-                    log.debug("Packet #{} received", packetNumber++);
-                    try {
-                        inboundRadiusPacket =
-                                RADIUS.deserializer()
-                                        .deserialize(inboundBasePacket.getData(),
-                                                0,
-                                                inboundBasePacket.getLength());
-                        if (log.isTraceEnabled()) {
-                            log.trace("Received RADIUS packet with Identifier {}",
-                                      inboundRadiusPacket.getIdentifier() & 0xff);
-                        }
-                        aaaManager.aaaStatisticsManager.handleRoundtripTime(inboundRadiusPacket.getIdentifier());
-                        aaaManager.handleRadiusPacket(inboundRadiusPacket);
-                    } catch (DeserializationException dex) {
-                        aaaManager.aaaStatisticsManager.getAaaStats().increaseMalformedResponsesRx();
-                        log.error("Cannot deserialize packet", dex);
-                    }
+                    worker.execute(() -> handleRadiusPacketInternal(inboundBasePacket));
+
                 } catch (IOException e) {
                     log.warn("Socket was closed, exiting listener thread");
                     done = true;
+                } catch (Exception e) {
+                    log.error("RadiusListener thread thrown an exception: {}", e.getMessage(), e);
                 }
             }
             log.info("UDP listener thread shutting down");
diff --git a/app/src/main/java/org/opencord/aaa/impl/StateMachine.java b/app/src/main/java/org/opencord/aaa/impl/StateMachine.java
index 1b5613c..8247dbf 100644
--- a/app/src/main/java/org/opencord/aaa/impl/StateMachine.java
+++ b/app/src/main/java/org/opencord/aaa/impl/StateMachine.java
@@ -738,7 +738,7 @@
             this.setSessionTerminateReason(SessionTerminationReasons.TIME_OUT.reason);
 
             delegate.notify(new AuthenticationEvent(AuthenticationEvent.Type.TIMEOUT,
-                    this.supplicantConnectpoint));
+                    this.supplicantConnectpoint, toAuthRecord()));
             // If StateMachine is not eligible for cleanup yet, reschedule cleanupTimer further.
         } else {
             this.scheduleTimeout();
diff --git a/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java b/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java
index 83d5c42..394f86d 100644
--- a/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java
+++ b/app/src/test/java/org/opencord/aaa/impl/AaaManagerTest.java
@@ -18,6 +18,8 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.onlab.junit.TestUtils;
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.DeserializationException;
@@ -58,8 +60,15 @@
 /**
  * Set of tests of the ONOS application component.
  */
+@RunWith(Parameterized.class)
 public class AaaManagerTest extends AaaTestBase {
 
+    // Change this to have more run with mvn
+    @Parameterized.Parameters
+    public static Object[][] data() {
+        return new Object[1][0];
+    }
+
     static final String BAD_IP_ADDRESS = "198.51.100.0";
 
     private final Logger log = getLogger(getClass());
diff --git a/app/src/test/java/org/opencord/aaa/impl/AaaStatisticsTest.java b/app/src/test/java/org/opencord/aaa/impl/AaaStatisticsTest.java
index 4ace459..bd68bfb 100644
--- a/app/src/test/java/org/opencord/aaa/impl/AaaStatisticsTest.java
+++ b/app/src/test/java/org/opencord/aaa/impl/AaaStatisticsTest.java
@@ -18,6 +18,8 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.onlab.junit.TestUtils;
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.DeserializationException;
@@ -62,8 +64,15 @@
 /**
  * Set of tests of the ONOS application component for AAA Statistics.
  */
+@RunWith(Parameterized.class)
 public class AaaStatisticsTest extends AaaTestBase {
 
+    // Change this to have more run with mvn
+    @Parameterized.Parameters
+    public static Object[][] data() {
+        return new Object[1][0];
+    }
+
     static final String BAD_IP_ADDRESS = "198.51.100.0";
     static final Long ZERO = (long) 0;
 
diff --git a/app/src/test/java/org/opencord/aaa/impl/IdentifierManagerTest.java b/app/src/test/java/org/opencord/aaa/impl/IdentifierManagerTest.java
index 890f2c6..afc5ec2 100644
--- a/app/src/test/java/org/opencord/aaa/impl/IdentifierManagerTest.java
+++ b/app/src/test/java/org/opencord/aaa/impl/IdentifierManagerTest.java
@@ -19,19 +19,38 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 
-import static org.junit.Assert.assertNotEquals;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
+@RunWith(Parameterized.class)
 public class IdentifierManagerTest {
 
+    // Change this to have more run with mvn
+    @Parameterized.Parameters
+    public static Object[][] data() {
+        return new Object[1][0];
+    }
+
     IdentifierManager idManager = null;
     private final Logger log = getLogger(getClass());
 
     @Before
     public void setUp() {
         System.out.print("Set up");
+        idManager.timeout = 1500;
+        idManager.pruningInterval = 1;
+        idManager.pollTimeout = 1;
         idManager = new IdentifierManager();
     }
 
@@ -54,4 +73,84 @@
             idManager.releaseIdentifier(id);
         }
     }
+
+    @Test(timeout = 3800)
+    public void testIdRelease() {
+        assertEquals(254, idManager.getAvailableIdentifiers());
+        for (int i = 0; i <= 253; i++) {
+            idManager.getNewIdentifier(Integer.toString(i));
+        }
+
+        assertEquals(0, idManager.getAvailableIdentifiers());
+
+        try {
+            TimeUnit.MILLISECONDS.sleep(3500);
+        } catch (InterruptedException e) {
+            log.error("Can't sleep");
+        }
+
+        // check that the queue has been emptied after the timeout occurred
+        assertEquals(254, idManager.getAvailableIdentifiers());
+
+        // check that I can get a new ID immediately (note the timeout in the test declaration)
+        idManager.getNewIdentifier(Integer.toString(254));
+    }
+
+    @Test(timeout = 5000)
+    public void unavailableIds() {
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+
+        Callable<Object> acquireId = () -> idManager.getNewIdentifier(Integer.toString(2));
+
+        // fill the queue
+        for (int i = 2; i <= 255; i++) {
+            idManager.getNewIdentifier(Integer.toString(i));
+        }
+
+        // try to acquire an id
+        Future<Object> futureAcquire = executor.submit(acquireId);
+
+        // wait for the threads to complete
+        RequestIdentifier id = null;
+        try {
+            id = (RequestIdentifier) futureAcquire.get();
+
+            // if we can't get the ID within 2
+            // seconds we'll drop the packet and we'll retry
+            assertNull(id);
+        } catch (InterruptedException | ExecutionException ex) {
+            log.error("Something failed");
+            assertNull(id);
+        }
+    }
+
+    @Test(timeout = 5000)
+    public void availableIds() {
+
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+
+        Callable<Object> acquireId = () -> idManager.getNewIdentifier(Integer.toString(2));
+
+        // fill the queue
+        for (int i = 2; i <= 255; i++) {
+            idManager.getNewIdentifier(Integer.toString(i));
+        }
+
+        // try to release an id
+        final RequestIdentifier id = new RequestIdentifier((byte) 2);
+        executor.submit(() -> idManager.releaseIdentifier(id));
+        // try to acquire an id
+        Future<Object> futureAcquire = executor.submit(acquireId);
+
+        // wait for the threads to complete
+        RequestIdentifier idGet = null;
+        try {
+            idGet = (RequestIdentifier) futureAcquire.get();
+            assertNotNull(idGet);
+        } catch (InterruptedException | ExecutionException ex) {
+            log.error("Something failed");
+            assertNull(idGet);
+        }
+    }
 }
diff --git a/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java b/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java
index e66a5f3..7415e2b 100644
--- a/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java
+++ b/app/src/test/java/org/opencord/aaa/impl/StateMachineTest.java
@@ -19,12 +19,22 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.concurrent.Executors;
 
 import static org.junit.Assert.assertEquals;
 
+@RunWith(Parameterized.class)
 public class StateMachineTest {
+
+    // Change this to have more run with mvn
+    @Parameterized.Parameters
+    public static Object[][] data() {
+        return new Object[1][0];
+    }
+
     StateMachine stateMachine = null;
 
     @Before