[SEBA-593] Splitting aaa app in implementation and api bundles
Change-Id: Ib81650be0695258bdd1f4cd6131f2206760e0da6
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.");
+ }
+ }
+
+
+}