SEBA-75: AAA app exposes authentication state machine events.

Change-Id: I9a1fbc0c28579f6b347322d6f7fc58635c1a9c8a
diff --git a/src/main/java/org/opencord/aaa/AaaManager.java b/src/main/java/org/opencord/aaa/AaaManager.java
index c9fddc0..120f14f 100755
--- a/src/main/java/org/opencord/aaa/AaaManager.java
+++ b/src/main/java/org/opencord/aaa/AaaManager.java
@@ -20,6 +20,7 @@
 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;
@@ -30,6 +31,7 @@
 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;
@@ -63,20 +65,19 @@
 /**
  * AAA application for ONOS.
  */
+@Service
 @Component(immediate = true)
-public class
-AaaManager {
+public class AaaManager
+        extends AbstractListenerManager<AuthenticationEvent, AuthenticationEventListener>
+        implements AuthenticationService {
+
     private static final String APP_NAME = "org.opencord.aaa";
 
-    // for verbose output
     private final Logger log = getLogger(getClass());
 
-    // a list of our dependencies :
-    // to register with ONOS as an application - described next
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
 
-    // to receive Packet-in events that we'll respond to
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected PacketService packetService;
 
@@ -98,7 +99,7 @@
     protected InetAddress nasIpAddress;
 
     // self MAC address
-    protected static String nasMacAddress;
+    protected String nasMacAddress;
 
     // Parsed RADIUS server addresses
     protected InetAddress radiusIpAddress;
@@ -109,9 +110,6 @@
     // RADIUS server secret
     protected String radiusSecret;
 
-    // NAS Identifier
-    protected String nasId;
-
     // bindings
     protected CustomizationInfo customInfo;
 
@@ -131,7 +129,7 @@
     // "socket" or "packet_out"
     private String radiusConnectionType;
 
-    // Object for the spcific type of communication with the RADIUS
+    // Object for the specific type of communication with the RADIUS
     // server, socket based or packet_out based
     RadiusCommunicator impl = null;
 
@@ -152,6 +150,8 @@
     // Listener for config changes
     private final InternalConfigListener cfgListener = new InternalConfigListener();
 
+    private StateMachineDelegate delegate = new InternalStateMachineDelegate();
+
     /**
      * Builds an EAPOL packet based on the given parameters.
      *
@@ -189,6 +189,7 @@
     @Activate
     public void activate() {
         appId = coreService.registerApplication(APP_NAME);
+        eventDispatcher.addSink(AuthenticationEvent.class, listenerRegistry);
         netCfgService.addListener(cfgListener);
         netCfgService.registerConfigFactory(factory);
         customInfo = new CustomizationInfo(subsService, deviceService);
@@ -202,6 +203,7 @@
 
 
         StateMachine.initializeMaps();
+        StateMachine.setDelegate(delegate);
 
         impl.initializeLocalState(newCfg);
 
@@ -216,13 +218,13 @@
     @Deactivate
     public void deactivate() {
         impl.withdrawIntercepts();
-        // de-register and null our handler
         packetService.removeProcessor(processor);
-        processor = null;
         netCfgService.removeListener(cfgListener);
+        StateMachine.unsetDelegate(delegate);
         StateMachine.destroyMaps();
         impl.deactivate();
         deviceService.removeListener(deviceListener);
+        eventDispatcher.removeSink(AuthenticationEvent.class);
         log.info("Stopped");
     }
 
@@ -441,8 +443,8 @@
             switch (eapol.getEapolType()) {
                 case EAPOL.EAPOL_START:
                     log.debug("EAP packet: EAPOL_START");
-                    stateMachine.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);
@@ -542,6 +544,19 @@
     }
 
     /**
+     * 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 {
diff --git a/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java b/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java
index f5b4e43..6655fec 100644
--- a/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java
+++ b/src/main/java/org/opencord/aaa/AaaResetDeviceCommand.java
@@ -20,6 +20,9 @@
 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 {
diff --git a/src/main/java/org/opencord/aaa/AuthenticationEvent.java b/src/main/java/org/opencord/aaa/AuthenticationEvent.java
new file mode 100644
index 0000000..da02ec2
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AuthenticationEvent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018-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;
+
+import org.onosproject.event.AbstractEvent;
+import org.onosproject.net.ConnectPoint;
+
+/**
+ * Event indicating the authentication state of a port has changed.
+ */
+public class AuthenticationEvent extends
+        AbstractEvent<AuthenticationEvent.Type, ConnectPoint> {
+
+    /**
+     * Authentication event type.
+     */
+    public enum Type {
+        /**
+         * Supplicant has started authentication on a port.
+         */
+        STARTED,
+
+        /**
+         * Supplicant has requested authentication on a port.
+         */
+        REQUESTED,
+
+        /**
+         * Authentication request was approved.
+         */
+        APPROVED,
+
+        /**
+         * Authentication request was denied.
+         */
+        DENIED
+    }
+
+    /**
+     * Creates a new authentication event.
+     *
+     * @param type event type
+     * @param connectPoint port
+     */
+    public AuthenticationEvent(Type type, ConnectPoint connectPoint) {
+        super(type, connectPoint);
+    }
+
+}
diff --git a/src/main/java/org/opencord/aaa/AuthenticationEventListener.java b/src/main/java/org/opencord/aaa/AuthenticationEventListener.java
new file mode 100644
index 0000000..87d2e4a
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AuthenticationEventListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018-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;
+
+import org.onosproject.event.EventListener;
+
+/**
+ * Listener for authentication events.
+ */
+public interface AuthenticationEventListener extends
+        EventListener<AuthenticationEvent> {
+}
diff --git a/src/main/java/org/opencord/aaa/AuthenticationService.java b/src/main/java/org/opencord/aaa/AuthenticationService.java
new file mode 100644
index 0000000..82aabbd
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/AuthenticationService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2018-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;
+
+import org.onosproject.event.ListenerService;
+
+/**
+ * Service for interacting with authentication module.
+ */
+public interface AuthenticationService extends
+        ListenerService<AuthenticationEvent, AuthenticationEventListener> {
+}
diff --git a/src/main/java/org/opencord/aaa/StateMachine.java b/src/main/java/org/opencord/aaa/StateMachine.java
index e2a24bc..d888c98 100644
--- a/src/main/java/org/opencord/aaa/StateMachine.java
+++ b/src/main/java/org/opencord/aaa/StateMachine.java
@@ -109,6 +109,8 @@
     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();
@@ -120,6 +122,16 @@
         identifierMap = null;
     }
 
+    public static void setDelegate(StateMachineDelegate delagate) {
+        StateMachine.delegate = delagate;
+    }
+
+    public static void unsetDelegate(StateMachineDelegate delegate) {
+        if (StateMachine.delegate == delegate) {
+            StateMachine.delegate = null;
+        }
+    }
+
     public static Map<String, StateMachine> sessionIdMap() {
         return sessionIdMap;
     }
@@ -366,6 +378,10 @@
      */
     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();
@@ -379,6 +395,10 @@
      */
     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);
     }
@@ -394,7 +414,8 @@
         //move to the next state
         next(TRANSITION_AUTHORIZE_ACCESS);
 
-        // TODO send state machine change event
+        delegate.notify(new AuthenticationEvent(
+                AuthenticationEvent.Type.APPROVED, supplicantConnectpoint));
 
         // Clear mapping
         deleteStateMachineMapping(this);
@@ -410,6 +431,10 @@
         states[currentState].radiusDenied();
         //move to the next state
         next(TRANSITION_DENY_ACCESS);
+
+        delegate.notify(new AuthenticationEvent(
+                AuthenticationEvent.Type.DENIED, supplicantConnectpoint));
+
         // Clear mappings
         deleteStateMachineMapping(this);
     }
diff --git a/src/main/java/org/opencord/aaa/StateMachineDelegate.java b/src/main/java/org/opencord/aaa/StateMachineDelegate.java
new file mode 100644
index 0000000..6546d74
--- /dev/null
+++ b/src/main/java/org/opencord/aaa/StateMachineDelegate.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2018-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;
+
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * Delegate for authentication state machine.
+ */
+public interface StateMachineDelegate extends StoreDelegate<AuthenticationEvent> {
+}
diff --git a/src/test/java/org/opencord/aaa/AaaManagerTest.java b/src/test/java/org/opencord/aaa/AaaManagerTest.java
index 456c7b6..d615aee 100644
--- a/src/test/java/org/opencord/aaa/AaaManagerTest.java
+++ b/src/test/java/org/opencord/aaa/AaaManagerTest.java
@@ -19,6 +19,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.junit.TestUtils;
 import org.onlab.packet.BasePacket;
 import org.onlab.packet.DeserializationException;
 import org.onlab.packet.EAP;
@@ -27,13 +28,19 @@
 import org.onlab.packet.RADIUS;
 import org.onlab.packet.RADIUSAttribute;
 import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.event.DefaultEventSinkRegistry;
+import org.onosproject.event.Event;
+import org.onosproject.event.EventDeliveryService;
+import org.onosproject.event.EventSink;
 import org.onosproject.net.config.Config;
 import org.onosproject.net.config.NetworkConfigRegistryAdapter;
 import org.onosproject.net.packet.InboundPacket;
 
+import java.lang.reflect.Field;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
+import static com.google.common.base.Preconditions.checkState;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.junit.Assert.assertThat;
@@ -82,6 +89,27 @@
         }
     }
 
+    public static class TestEventDispatcher extends DefaultEventSinkRegistry
+            implements EventDeliveryService {
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public synchronized void post(Event event) {
+            EventSink sink = getSink(event.getClass());
+            checkState(sink != null, "No sink for event %s", event);
+            sink.process(event);
+        }
+
+        @Override
+        public void setDispatchTimeLimit(long millis) {
+        }
+
+        @Override
+        public long getDispatchTimeLimit() {
+            return 0;
+        }
+    }
+
     /**
      * Constructs an Ethernet packet containing a RADIUS challenge
      * packet.
@@ -111,6 +139,20 @@
         return radius;
     }
 
+    public static void injectEventDispatcher(Object manager, EventDeliveryService svc) {
+        Class mc = manager.getClass();
+        for (Field f : mc.getSuperclass().getDeclaredFields()) {
+            if (f.getType().equals(EventDeliveryService.class)) {
+                try {
+                    TestUtils.setField(manager, f.getName(), svc);
+                } catch (TestUtils.TestUtilsException e) {
+                    throw new IllegalArgumentException("Unable to inject reference", e);
+                }
+                break;
+            }
+        }
+    }
+
     /**
      * Sets up the services required by the AAA application.
      */
@@ -122,6 +164,7 @@
         aaaManager.packetService = new MockPacketService();
         aaaManager.deviceService = new TestDeviceService();
         aaaManager.subsService = new MockSubService();
+        TestUtils.setField(aaaManager, "eventDispatcher", new TestEventDispatcher());
         aaaManager.activate();
     }
 
diff --git a/src/test/java/org/opencord/aaa/StateMachineTest.java b/src/test/java/org/opencord/aaa/StateMachineTest.java
index f12a95f..5b0a62d 100644
--- a/src/test/java/org/opencord/aaa/StateMachineTest.java
+++ b/src/test/java/org/opencord/aaa/StateMachineTest.java
@@ -32,6 +32,7 @@
     public void setUp() {
         System.out.println("Set Up.");
         StateMachine.initializeMaps();
+        StateMachine.setDelegate(e -> { });
         stateMachine = new StateMachine("session0");
     }