package org.jdiameter.client.impl.app.auth;
import static org.jdiameter.api.Message.SESSION_TERMINATION_REQUEST;
import static org.jdiameter.common.api.app.auth.ClientAuthSessionState.DISCONNECTED;
import static org.jdiameter.common.api.app.auth.ClientAuthSessionState.IDLE;
import static org.jdiameter.common.api.app.auth.ClientAuthSessionState.OPEN;
import static org.jdiameter.common.api.app.auth.ClientAuthSessionState.PENDING;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jdiameter.api.Answer;
import org.jdiameter.api.Avp;
import org.jdiameter.api.EventListener;
import org.jdiameter.api.IllegalDiameterStateException;
import org.jdiameter.api.InternalException;
import org.jdiameter.api.NetworkReqListener;
import org.jdiameter.api.OverloadException;
import org.jdiameter.api.Request;
import org.jdiameter.api.RouteException;
import org.jdiameter.api.SessionFactory;
import org.jdiameter.api.app.AppAnswerEvent;
import org.jdiameter.api.app.AppEvent;
import org.jdiameter.api.app.AppRequestEvent;
import org.jdiameter.api.app.StateChangeListener;
import org.jdiameter.api.app.StateEvent;
import org.jdiameter.api.auth.ClientAuthSession;
import org.jdiameter.api.auth.ClientAuthSessionListener;
import org.jdiameter.api.auth.events.AbortSessionAnswer;
import org.jdiameter.api.auth.events.AbortSessionRequest;
import org.jdiameter.api.auth.events.ReAuthAnswer;
import org.jdiameter.api.auth.events.ReAuthRequest;
import org.jdiameter.api.auth.events.SessionTermAnswer;
import org.jdiameter.api.auth.events.SessionTermRequest;
import org.jdiameter.common.api.app.IAppSessionState;
import org.jdiameter.common.api.app.auth.ClientAuthSessionState;
import org.jdiameter.common.api.app.auth.IAuthMessageFactory;
import org.jdiameter.common.api.app.auth.IClientAuthActionContext;
import org.jdiameter.common.impl.app.AppAnswerEventImpl;
import org.jdiameter.common.impl.app.AppRequestEventImpl;
import org.jdiameter.common.impl.app.auth.AbortSessionAnswerImpl;
import org.jdiameter.common.impl.app.auth.AbortSessionRequestImpl;
import org.jdiameter.common.impl.app.auth.AppAuthSessionImpl;
import org.jdiameter.common.impl.app.auth.ReAuthAnswerImpl;
import org.jdiameter.common.impl.app.auth.ReAuthRequestImpl;
import org.jdiameter.common.impl.app.auth.SessionTermAnswerImpl;
import org.jdiameter.common.impl.app.auth.SessionTermRequestImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClientAuthSessionImpl extends AppAuthSessionImpl implements ClientAuthSession, EventListener<Request, Answer>, NetworkReqListener {
private static final long serialVersionUID = 1L;
protected static final Logger logger = LoggerFactory.getLogger(ClientAuthSessionImpl.class);
protected ClientAuthSessionState state = IDLE;
protected boolean stateless;
protected IAuthMessageFactory factory;
protected String destHost, destRealm;
protected IClientAuthActionContext context;
protected AppEvent buffer;
protected ClientAuthSessionListener listener;
protected ScheduledFuture fsf;
private Lock sendAndStateLock = new ReentrantLock();
// =================== CONSTRUCTORS
public ClientAuthSessionImpl(boolean stl, IAuthMessageFactory fct, SessionFactory sf, ClientAuthSessionListener lst) {
this(stl, null, fct, sf, lst);
}
public ClientAuthSessionImpl(boolean stl, String sessionId, IAuthMessageFactory fct, SessionFactory sf, ClientAuthSessionListener lst) {
if (lst == null) {
throw new IllegalArgumentException("Listener can not be null");
}
if (fct.getApplicationId() == null) {
throw new IllegalArgumentException("ApplicationId can not be null");
}
appId = fct.getApplicationId();
listener = lst;
factory = fct;
stateless = stl;
try {
if (sessionId == null) {
session = sf.getNewSession();
}
else {
session = sf.getNewSession(sessionId);
}
session.setRequestListener(this);
}
catch (InternalException e) {
throw new IllegalArgumentException(e);
}
if (listener instanceof IClientAuthActionContext) {
context = (IClientAuthActionContext) listener;
}
}
public void sendAbortSessionAnswer(AbortSessionAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_SESSION_ABORT_ANSWER, answer);
}
public void sendAuthRequest(AppRequestEvent request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_AUTH_REQUEST, request);
}
public void sendReAuthAnswer(ReAuthAnswer answer) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_AUTH_ANSWER, answer);
}
public void sendSessionTerminationRequest(SessionTermRequest request) throws InternalException, IllegalDiameterStateException, RouteException, OverloadException {
send(Event.Type.SEND_SESSION_TERINATION_REQUEST, request);
}
protected void send(Event.Type type, AppEvent event) throws InternalException {
try {
sendAndStateLock.lock();
if (type != null) {
handleEvent(new Event(type, event));
}
session.send(event.getMessage(), this);
// Store last destinmation information
destRealm = event.getMessage().getAvps().getAvp(Avp.DESTINATION_REALM).getOctetString();
destHost = event.getMessage().getAvps().getAvp(Avp.DESTINATION_HOST).getOctetString();
}
catch (Exception e) {
throw new InternalException(e);
}
finally {
sendAndStateLock.unlock();
}
}
public boolean isStateless() {
return stateless;
}
protected void setState(ClientAuthSessionState newState) {
IAppSessionState oldState = state;
state = newState;
for (StateChangeListener i : stateListeners) {
i.stateChanged((Enum) oldState, (Enum) newState);
}
}
public <E> E getState(Class<E> eClass) {
return eClass == ClientAuthSessionState.class ? (E) state : null;
}
public boolean handleEvent(StateEvent event) throws InternalException, OverloadException {
return stateless ? handleEventForStatelessSession(event) : handleEventForStatefullSession(event);
}
public boolean handleEventForStatelessSession(StateEvent event) throws InternalException, OverloadException {
try {
ClientAuthSessionState oldState = state;
switch (state) {
case IDLE:
switch ((Event.Type) event.getType()) {
case SEND_AUTH_REQUEST:
setState(PENDING);
break;
default:
logger.debug("Unknown event {}", event.getType());
break;
}
break;
case PENDING:
switch ((Event.Type) event.getType()) {
case RECEIVE_AUTH_ANSWER:
try {
/**
* Successful Service-specific authorization answer
* received with Auth-Session- State set to NO_STATE_MAINTAINED
*/
listener.doAuthAnswerEvent(this, null, (AppAnswerEvent) event.getData());
setState(OPEN);
}
catch (Exception e) {
// Failed Service-specific authorization answer received
setState(IDLE);
}
break;
default:
logger.debug("Unknown event {}", event.getType());
break;
}
break;
case OPEN:
switch ((Event.Type) event.getType()) {
case SEND_SESSION_ABORT_ANSWER:
case SEND_SESSION_TERINATION_REQUEST:
setState(IDLE);
break;
case TIMEOUT_EXPIRES:
if (context != null) {
context.accessTimeoutElapses();
Request str = createSessionTermRequest();
context.disconnectUserOrDev(this, str);
session.send(str, this);
}
setState(IDLE);
break;
default:
logger.debug("Unknown event {}", event.getType());
break;
}
break;
}
// post processing
if (oldState != state) {
if (DISCONNECTED.equals(state) || IDLE.equals(state)) {
if (fsf != null) {
fsf.cancel(false);
}
fsf = null;
}
else if (OPEN.equals(state) && context != null && context.createAccessTimer() > 0) {
fsf = scheduler.schedule(new Runnable() {
public void run() {
if (context != null) {
try {
handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, null));
}
catch (Exception e) {
logger.debug("Can not handle event", e);
}
}
}
}, context.createAccessTimer(), TimeUnit.MILLISECONDS);
}
}
} catch (Throwable t) {
throw new InternalException(t);
}
return true;
}
public boolean handleEventForStatefullSession(StateEvent event) throws InternalException, OverloadException {
ClientAuthSessionState oldState = state;
try {
switch (state) {
case IDLE: {
switch ((Event.Type) event.getType()) {
case SEND_AUTH_REQUEST:
setState(PENDING);
break;
case RECEIVE_ABORT_SESSION_REQUEST:
listener.doAbortSessionRequestEvent(this, (AbortSessionRequest) event.getData());
break;
default:
logger.debug("Unknown event {}", event.getType());
break;
}
break;
}
case PENDING: {
switch ((Event.Type) event.getType()) {
case RECEIVE_AUTH_ANSWER:
try {
/**
* Listener processed following actions: Grant access / Send STR / Cleanup
*/
listener.doAuthAnswerEvent(this, null, (AppAnswerEvent) event.getData());
setState(OPEN);
}
catch (InternalException e) {
// Successful Service-specific authorization answer received but service not provided
// Error processing successful Service-specific authorization answer
setState(DISCONNECTED);
}
catch (Exception e) {
// Failed Service-specific authorization answer received
setState(IDLE);
}
break;
default:
logger.debug("Unknown event {}", event.getType());
break;
}
break;
}
case OPEN: {
switch ((Event.Type) event.getType()) {
case SEND_AUTH_REQUEST:
// User or client device equests access to service
break;
case RECEIVE_AUTH_ANSWER:
try {
// Successful Service-specific authorization answer received
listener.doAuthAnswerEvent(this, null, (AppAnswerEvent) event.getData());
}
catch (Exception e) {
// ASR Received, client will comply with request to end the session
setState(DISCONNECTED);
}
break;
case RECEIVE_FAILED_AUTH_ANSWER:
// Failed Service-specific authorization answer received
if (context != null) {
Request str = createSessionTermRequest();
context.disconnectUserOrDev(this, str);
session.send(str, this);
}
setState(IDLE);
break;
case RECEIVE_ABORT_SESSION_REQUEST:
listener.doAbortSessionRequestEvent(this, (AbortSessionRequestImpl) event.getData());
break;
case SEND_SESSION_TERINATION_REQUEST:
setState(DISCONNECTED);
break;
case TIMEOUT_EXPIRES:
// Session-Timeout Expires on Access Device Authorization-Lifetime +
// Auth-Grace-Period expires on access device
if (context != null) {
context.accessTimeoutElapses();
Request str = createSessionTermRequest();
context.disconnectUserOrDev(this, str);
session.send(str, this);
}
setState(DISCONNECTED);
break;
}
break;
}
case DISCONNECTED: {
switch ((Event.Type) event.getType()) {
case RECEIVE_ABORT_SESSION_REQUEST:
listener.doAbortSessionRequestEvent(this, (AbortSessionRequest) event.getData());
break;
case RECEIVE_SESSION_TERINATION_ANSWER:
listener.doSessionTerminationAnswerEvent(this, ((SessionTermAnswerImpl) event.getData()));
setState(IDLE);
break;
default:
logger.debug("Unknown event {}", event.getType());
break;
}
break;
}
default: {
logger.debug("Unknown state {}", state);
break;
}
}
// post processing
if (oldState != state) {
if (OPEN.equals(state) && context != null && context.createAccessTimer() > 0) {
scheduler.schedule(new Runnable() {
public void run() {
if (context != null) {
try {
handleEvent(new Event(Event.Type.TIMEOUT_EXPIRES, null));
}
catch (Exception e) {
logger.debug("Can not handle event", e);
}
}
}
}, context.createAccessTimer(), TimeUnit.MILLISECONDS);
}
}
}
catch (Throwable t) {
throw new InternalException(t);
}
return true;
}
public void receivedSuccessMessage(Request request, Answer answer) {
try {
sendAndStateLock.lock();
//FIXME: baranowb: this shouldnt be like that
if (request.getCommandCode() == factory.getAuthMessageCommandCode()) {
handleEvent(new Event(Event.Type.RECEIVE_AUTH_ANSWER, factory.createAuthAnswer(answer)));
}
else if (request.getCommandCode() == AbortSessionRequestImpl.code) {
handleEvent(new Event(Event.Type.RECEIVE_ABORT_SESSION_REQUEST, createAbortSessionRequest(request)));
}
else if (request.getCommandCode() == ReAuthRequestImpl.code) {
listener.doReAuthRequestEvent(this, createReAuthRequest(request));
}
else if (request.getCommandCode() == SessionTermAnswerImpl.code) {
listener.doSessionTerminationAnswerEvent(this, new SessionTermAnswerImpl(answer));
handleEvent(new Event(Event.Type.RECEIVE_SESSION_TERINATION_ANSWER, createSessionTermAnswer(answer)));
}
else {
listener.doOtherEvent(this, factory.createAuthRequest(request), new AppAnswerEventImpl(answer));
}
}
catch (Exception e) {
logger.debug("Can not process received message", e);
}
finally {
sendAndStateLock.unlock();
}
}
public void timeoutExpired(Request request) {
try {
handleEvent(new Event(Event.Type.RECEIVE_FAILED_AUTH_ANSWER, new AppRequestEventImpl(request)));
}
catch (Exception e) {
logger.debug("Can not handle timeout event", e);
}
}
public Answer processRequest(Request request) {
try {
if (request.getCommandCode() == AbortSessionRequestImpl.code) {
handleEvent(new Event(Event.Type.RECEIVE_ABORT_SESSION_REQUEST, createAbortSessionRequest(request)));
}
else if (request.getCommandCode() == ReAuthRequestImpl.code) {
listener.doReAuthRequestEvent(this, createReAuthRequest(request));
}
else {
//FIXME: baranowb : should it be like that?
listener.doOtherEvent(this, factory.createAuthRequest(request), null);
}
}
catch (Exception e) {
logger.debug("Can not process received request", e);
}
return null;
}
protected AbortSessionAnswer createAbortSessionAnswer(Answer answer) {
return new AbortSessionAnswerImpl(answer);
}
protected AbortSessionRequest createAbortSessionRequest(Request request) {
return new AbortSessionRequestImpl(request);
}
protected ReAuthAnswer createReAuthAnswer(Answer answer) {
return new ReAuthAnswerImpl(answer);
}
protected ReAuthRequest createReAuthRequest(Request request) {
return new ReAuthRequestImpl(request);
}
protected SessionTermAnswer createSessionTermAnswer(Answer answer) {
return new SessionTermAnswerImpl(answer);
}
protected SessionTermRequest createSessionTermRequest(Request request) {
return new SessionTermRequestImpl(request);
}
protected Request createSessionTermRequest() {
return session.createRequest(SESSION_TERMINATION_REQUEST, appId, destRealm, destHost);
}
}