/*
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fruanhofer Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: Alot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#include "../../core/sr_module.h"
#include "session.h"
#include "diameter.h"
#include "config.h"
#include "authstatemachine.h"
#include "acctstatemachine.h"
#include "timer.h"
#include "globals.h"

extern dp_config *config;		/**< Configuration for this diameter peer 	*/

gen_lock_t *session_lock;		/**< lock for session operation */

int sessions_hash_size = 1024;	/**< the size of the session hash table		*/
cdp_session_list_t *sessions;	/**< the session hash table					*/

unsigned int *session_id1;		/**< counter for first part of the session id */
unsigned int *session_id2;		/**< counter for second part of the session id */

#define GRACE_DISCON_TIMEOUT	60	/**< 60 seconds for a DISCON acct session to hang around for before being cleaned up */


/**
 * Lock a hash table row
 */
inline void AAASessionsLock(unsigned int hash)
{
	if(destroy_modules_phase()) return;
	if ( hash < sessions_hash_size ){
		lock_get(sessions[hash].lock);
	}
	else {
		LM_ERR("AAASessionsLock: hash :%d out of range of sessions_hash_size: %d !\n", hash, sessions_hash_size);
	}
}

/**
 * Unlock a hash table row
 */
inline void AAASessionsUnlock(unsigned int hash)
{
	if(destroy_modules_phase()) return;

	if ( hash < sessions_hash_size ){
		lock_release(sessions[hash].lock);
	}
	else {
		LM_ERR("AAASessionsLock: hash :%d out of range of sessions_hash_size: %d !\n", hash, sessions_hash_size);
	}
}



/**
 * Free a session structure
 */
void free_session(cdp_session_t *x)
{
	if (x){
		if (x->id.s) shm_free(x->id.s);
		switch(x->type){
			case UNKNOWN_SESSION:
				if (x->u.generic_data){
					LM_ERR("free_session(): The session->u.generic_data should be freed and reset before dropping the session!"
							"Possible memory leak!\n");
				}
				break;
			case AUTH_CLIENT_STATEFULL:
				break;
			case AUTH_SERVER_STATEFULL:
				break;
			case ACCT_CC_CLIENT:
				break;
			default:
				LM_ERR("free_session(): Unknown session type %d!\n",x->type);
		}

		if(x->dest_host.s) shm_free(x->dest_host.s);
		if(x->dest_realm.s) shm_free(x->dest_realm.s);
		if (x->sticky_peer_fqdn_buflen && x->sticky_peer_fqdn.s) {
			shm_free(x->sticky_peer_fqdn.s);
		}
		shm_free(x);
	}
}


/**
 * Initializes the session related structures.
 */
int cdp_sessions_init(int hash_size)
{
	int i;
	session_lock = lock_alloc();
	if (!session_lock){
		LOG_NO_MEM("lock",sizeof(gen_lock_t));
		goto error;
	}
	session_lock = lock_init(session_lock);
	sessions_hash_size=hash_size;

	sessions = shm_malloc(sizeof(cdp_session_list_t)*hash_size);
	if (!sessions){
		LOG_NO_MEM("shm",sizeof(cdp_session_list_t)*hash_size);
		goto error;
	}
	memset(sessions,0,sizeof(cdp_session_list_t)*hash_size);

	for(i=0;i<hash_size;i++){
		sessions[i].lock = lock_alloc();
		if (!sessions[i].lock){
			LOG_NO_MEM("lock",sizeof(gen_lock_t));
			goto error;
		}
		sessions[i].lock = lock_init(sessions[i].lock);
	}

	session_id1 = shm_malloc(sizeof(unsigned int));
	if (!session_id1){
		LOG_NO_MEM("shm",sizeof(unsigned int));
		goto error;
	}
	session_id2 = shm_malloc(sizeof(unsigned int));
	if (!session_id2){
		LOG_NO_MEM("shm",sizeof(unsigned int));
		goto error;
	}
	*session_id1 = kam_rand();
	*session_id1 <<= 16;
	*session_id1 += time(0)&0xFFFF;
	*session_id2 = 0;

	add_timer(1,0,cdp_sessions_timer,0);
	return 1;
error:
	return 0;
}

/**
 * Destroys the session related structures.
 */
int cdp_sessions_destroy()
{
	int i;
	cdp_session_t *n,*x;

	if (session_lock){
		lock_get(session_lock);
		lock_destroy(session_lock);
		lock_dealloc((void*)session_lock);
		session_lock=0;
	}
	for(i=0;i<sessions_hash_size;i++){
		AAASessionsLock(i);
		for(x = sessions[i].head; x; x = n){
			n = x->next;
			free_session(x);
		}
		lock_destroy(sessions[i].lock);
		lock_dealloc((void*)sessions[i].lock);
	}
	shm_free(sessions);

	shm_free(session_id1);
	shm_free(session_id2);
	return 1;
}




/**
 * Computes the hash for a string.
 * @param aor - the aor to compute the hash on
 * @param hash_size - value to % with
 * @returns the hash % hash_size
 */
unsigned int get_str_hash(str x,int hash_size)
{
#define h_inc h+=v^(v>>3)
	char* p;
	register unsigned v;
	register unsigned h;

	h=0;
	for (p=x.s; p<=(x.s+x.len-4); p+=4){
		v=(*p<<24)+(p[1]<<16)+(p[2]<<8)+p[3];
		h_inc;
	}
	v=0;
	for (;p<(x.s+x.len); p++) {
		v<<=8;
		v+=*p;
	}
	h_inc;

	h=((h)+(h>>11))+((h>>13)+(h>>23));
	return (h)%hash_size;
#undef h_inc
}

/**
 * Create a new session structure
 * @param id - the session id string, already allocated in shm
 * @param type - the session type
 * @returns the new cdp_session_t on success or 0 on failure
 */
cdp_session_t* cdp_new_session(str id,cdp_session_type_t type)
{
	cdp_session_t *x=0;

	x = shm_malloc(sizeof(cdp_session_t));
	if (!x){
		LOG_NO_MEM("shm",sizeof(cdp_session_t));
		goto error;
	}
	memset(x,0,sizeof(cdp_session_t));
	x->id = id;
	x->type = type;
	x->hash = get_str_hash(x->id,sessions_hash_size);
	return x;
error:
	return 0;
}

/**
 * Adds the session to the session list.
 * \note This returns with a lock, so unlock when done
 * @param x - the session to add
 */
void cdp_add_session(cdp_session_t *x)
{
	//	unsigned int hash;
	if (!x) return;
	//	hash = get_str_hash(x->id,sessions_hash_size);
	//	x->hash = hash;
	LM_DBG("adding a session with id %.*s\n",x->id.len,x->id.s);
	AAASessionsLock(x->hash);
	x->next = 0;
	x->prev = sessions[x->hash].tail;
	if (sessions[x->hash].tail) sessions[x->hash].tail->next = x;
	sessions[x->hash].tail = x;
	if (!sessions[x->hash].head) sessions[x->hash].head = x;
}

/**
 * Finds a session in the session hash table.
 * \note Returns with a lock on the sessions[x->hash].lock!!!
 * @param id - the id of the session
 * @returns the session if found or 0 if not
 */
cdp_session_t* cdp_get_session(str id)
{
	unsigned int hash;
	cdp_session_t *x;
	if (!id.len) return 0;
	hash = get_str_hash(id,sessions_hash_size);
	LM_DBG("called get session with id %.*s and hash %u\n",id.len,id.s,hash);
	AAASessionsLock(hash);
	for(x = sessions[hash].head;x;x=x->next){
		LM_DBG("looking for |%.*s| in |%.*s|\n",id.len,id.s,x->id.len,x->id.s);
		if (x->id.len == id.len &&
				strncasecmp(x->id.s,id.s,id.len)==0)
			return x;
	}
	AAASessionsUnlock(hash);
	LM_DBG("no session found\n");
	return 0;
}


/**
 * Removes and frees a session.
 * \note must be called with a lock on the x->hash and it will unlock on exit. Do not use x after calling this
 *
 * @param x - the session to remove
 */
void del_session(cdp_session_t *x)
{
	unsigned int hash;

	if (!x) return;

	hash = x->hash;
	if (hash >= sessions_hash_size) {
		LM_ERR("del_session: x->hash :%d out of range of sessions_hash_size: %d !\n",hash, sessions_hash_size);
		return;
	}

	if (sessions[x->hash].head == x) sessions[x->hash].head = x->next;
	else if (x->prev) x->prev->next = x->next;
	if (sessions[x->hash].tail == x) sessions[x->hash].tail = x->prev;
	else if (x->next) x->next->prev = x->prev;

	AAASessionsUnlock(hash);

	free_session(x);
}


/**
 * Generates a new session_ID (conforming with draft-ietf-aaa-diameter-17).
 * This function is thread safe.
 * @returns an 1 if success or -1 if error.
 */
static int generate_session_id(str *id, unsigned int end_pad_len)
{
	unsigned int s2;

	/* some checks */
	if (!id)
		goto error;

	/* compute id's len */
	id->len = config->identity.len +
		1/*;*/ + 10/*high 32 bits*/ +
		1/*;*/ + 10/*low 32 bits*/ +
		//		1/*;*/ + 8/*optional value*/ +
		1 /* terminating \0 */ +
		end_pad_len;

	/* get some memory for it */
	id->s = (char*)shm_malloc( id->len );
	if (id->s==0) {
		LM_ERR("generate_session_id: no more free memory!\n");
		goto error;
	}

	lock_get(session_lock);
	s2 = *session_id2 +1;
	*session_id2 = s2;
	lock_release(session_lock);

	/* build the sessionID */
	sprintf(id->s,"%.*s;%u;%u",config->identity.len,config->identity.s,*session_id1,s2);
	id->len = strlen(id->s);
	return 1;
error:
	return -1;
}

void cdp_sessions_log()
{
	if (debug_heavy) {
		int hash;
		cdp_session_t *x;

		LM_DBG("------- CDP Sessions ----------------\n");
		for(hash=0;hash<sessions_hash_size;hash++){
			AAASessionsLock(hash);
			for(x = sessions[hash].head;x;x=x->next) {
				LM_DBG(" %3u. [%.*s] AppId [%d] Type [%d]\n",
						hash,
						x->id.len,x->id.s,
						x->application_id,
						x->type);
				switch (x->type){
					case AUTH_CLIENT_STATEFULL:
					case AUTH_SERVER_STATEFULL:
						LM_DBG("Auth State [%d] Timeout [%d] Lifetime [%d] Grace [%d] Generic [%p] Class [%d]\n",
								x->u.auth.state,
								(int)(x->u.auth.timeout-time(0)),
								x->u.auth.lifetime?(int)(x->u.auth.lifetime-time(0)):-1,
								(int)(x->u.auth.grace_period),
								x->u.auth.generic_data,
								x->u.auth.class);
						break;
					case ACCT_CC_CLIENT:
						LM_DBG("CCAcct State [%d] Charging Active [%c (%d)s] Reserved Units(valid=%ds) [%d] Generic [%p]\n",
								x->u.cc_acc.state,
								(x->u.cc_acc.charging_start_time&&x->u.cc_acc.state!=ACC_CC_ST_DISCON)?'Y':'N',
								x->u.cc_acc.charging_start_time?(int)((int)time(0) - (int)x->u.cc_acc.charging_start_time):-1,
								x->u.cc_acc.reserved_units?(int)((int)x->u.cc_acc.last_reservation_request_time + x->u.cc_acc.reserved_units_validity_time) - (int)time(0):-1,
								x->u.cc_acc.reserved_units,
								x->u.cc_acc.generic_data);
						break;
					default:
						break;
				}
			}
			AAASessionsUnlock(hash);
		}
		LM_DBG("-------------------------------------\n");
	}
}

int cdp_sessions_timer(time_t now, void* ptr)
{
	int hash;
	cdp_session_t *x,*n;
	for(hash=0;hash<sessions_hash_size;hash++){
		AAASessionsLock(hash);
		for(x = sessions[hash].head;x;x=n) {
			n = x->next;
			switch (x->type){
				case ACCT_CC_CLIENT:
					if (x->u.cc_acc.type == ACC_CC_TYPE_SESSION) {
						//check for old, stale sessions, we need to do something more elegant
						//here to ensure that if a CCR start record is sent and the client never sends anything
						//else that we catch it and clean up the session from within CDP, calling all callbacks, etc
						if ((time(0) > (x->u.cc_acc.discon_time + GRACE_DISCON_TIMEOUT)) && (x->u.cc_acc.state==ACC_CC_ST_DISCON)) {
							cc_acc_client_stateful_sm_process(x, ACC_CC_EV_SESSION_STALE, 0);
						}
						//check reservation timers - again here we are assuming CC-Time applications
						int last_res_timestamp = x->u.cc_acc.last_reservation_request_time;
						int res_valid_for = x->u.cc_acc.reserved_units_validity_time;
						int last_reservation = x->u.cc_acc.reserved_units;
						int buffer_time = 15; //15 seconds - TODO: add as config parameter
						//we should check for reservation expiries if the state is open
						if(x->u.cc_acc.state==ACC_CC_ST_OPEN){
							if (last_res_timestamp) {
								//we have obv already started reservations
								if ((last_res_timestamp + res_valid_for) < (time(0) + last_reservation + buffer_time)) {
									LM_DBG("reservation about to expire, sending callback\n");
									cc_acc_client_stateful_sm_process(x, ACC_CC_EV_RSVN_WARNING, 0);
								}

							}
						}
						/* TODO: if reservation has expired we need to tear down the session. Ideally
						 * the client application (module) should do this but for completeness we should
						 * put a failsafe here too.
						 */
					}
					break;
				case AUTH_CLIENT_STATEFULL:
					if (x->u.auth.timeout>=0 && x->u.auth.timeout<=now){
						//Session timeout
						LM_CRIT("session TIMEOUT\n");
						auth_client_statefull_sm_process(x,AUTH_EV_SESSION_TIMEOUT,0);
					} else if (x->u.auth.lifetime>0 && x->u.auth.lifetime+x->u.auth.grace_period<=now){
						//lifetime + grace timeout
						LM_CRIT("lifetime+grace TIMEOUT\n");
						auth_client_statefull_sm_process(x,AUTH_EV_SESSION_GRACE_TIMEOUT,0);
					}else if (x->u.auth.lifetime>0 && x->u.auth.lifetime<=now){
						//lifetime timeout
						LM_CRIT("lifetime+grace TIMEOUT\n");
						auth_client_statefull_sm_process(x,AUTH_EV_SESSION_LIFETIME_TIMEOUT,0);
					}
					break;
				case AUTH_SERVER_STATEFULL:
					if (x->u.auth.timeout>=0 && x->u.auth.timeout<=now){
						//Session timeout
						LM_CRIT("session TIMEOUT\n");
						auth_server_statefull_sm_process(x,AUTH_EV_SESSION_TIMEOUT,0);
					}else if (x->u.auth.lifetime>0 && x->u.auth.lifetime+x->u.auth.grace_period<=now){
						//lifetime + grace timeout
						LM_CRIT("lifetime+grace TIMEOUT\n");
						auth_server_statefull_sm_process(x,AUTH_EV_SESSION_GRACE_TIMEOUT,0);
					}else if (x->u.auth.lifetime>0 && x->u.auth.lifetime<=now){
						//lifetime timeout
						LM_CRIT("lifetime+grace TIMEOUT\n");
						auth_server_statefull_sm_process(x,AUTH_EV_SESSION_LIFETIME_TIMEOUT,0);
					}
					break;
				default:
					break;

			}
		}
		AAASessionsUnlock(hash);
	}
	if (now%5==0)cdp_sessions_log();
	return 1;
}



/****************************** API FUNCTIONS ********************************/

/**
 * Creates a Generic Session.
 */
AAASession* AAACreateSession(void *generic_data)
{
	AAASession *s;
	str id;

	generate_session_id(&id,0);
	s = cdp_new_session(id,UNKNOWN_SESSION);
	if (s) {
		s->u.generic_data = generic_data;
		cdp_add_session(s);
	}
	return s;
}

/**
 * Make a session based on already known parameters.
 * \Note This should be used to recover saved sessions after a restart.
 * @param app_id
 * @param type
 * @param session_id - will be duplicated to shm
 * @return
 */
AAASession* AAAMakeSession(int app_id,int type,str session_id)
{
	AAASession *s;
	str id;

	id.s = shm_malloc(session_id.len);
	if (!id.s){
		LM_ERR("Error allocating %d bytes!\n",session_id.len);
		return 0;
	}
	memcpy(id.s,session_id.s,session_id.len);
	id.len = session_id.len;
	s = cdp_new_session(id,type);
	s->application_id = app_id;
	if (s) {
		cdp_add_session(s);
	}
	return s;
}
/**
 * Deallocates the memory taken by a Generic Session
 */
void AAADropSession(AAASession *s)
{
	// first give a chance to the cb to free the generic param
	if (s&&s->cb)
		(s->cb)(AUTH_EV_SESSION_DROP,s);
	del_session(s);
}


AAASession* cdp_new_auth_session(str id,int is_client,int is_statefull)
{
	AAASession *s;
	cdp_session_type_t type;

	if (is_client){
		if (is_statefull) type = AUTH_CLIENT_STATEFULL;
		else type = AUTH_CLIENT_STATELESS;
	}else{
		if (is_statefull) type = AUTH_SERVER_STATEFULL;
		else type = AUTH_SERVER_STATELESS;
	}
	s = cdp_new_session(id,type);
	if (s) {
		s->u.auth.timeout=time(0)+config->default_auth_session_timeout;
		s->u.auth.lifetime=0;
		s->u.auth.grace_period=0;
		s->u.auth.class = AUTH_CLASS_UNKNOWN;
		s->u.auth.last_requested_grace = s->u.auth.last_requested_lifetime = s->u.auth.last_requested_timeout = 0;
		cdp_add_session(s);
	}
	return s;
}

/**
 * Creates a Credit Control Accounting session for Client.
 * It generates a new id and adds the session to the cdp list of sessions
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* cdp_new_cc_acc_session(str id, int is_statefull)
{
	AAASession *s;
	cdp_session_type_t type;

	//for now everything will be supported through this SM (until we add IEC)
	type = ACCT_CC_CLIENT;

	s = cdp_new_session(id,type);
	if (s) {
		if (is_statefull)
			s->u.cc_acc.type = ACC_CC_TYPE_SESSION;
		else
			s->u.cc_acc.type = ACC_CC_TYPE_EVENT;

		//		s->u.cc_acc.timeout=time(0)+config->default_cc_acct_session_timeout;
		cdp_add_session(s);
	}
	return s;
}

/**
 * Creates an Authorization Session for the Client.
 * It generates a new id and adds the session to the cdp list of sessions
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* AAACreateClientAuthSession(int is_statefull,AAASessionCallback_f *cb,void *generic_data)
{
	AAASession *s;
	str id;

	generate_session_id(&id,0);

	s = cdp_new_auth_session(id,1,is_statefull);
	if (s) {
		s->u.auth.generic_data = generic_data;
		s->cb = cb;
		if (s->cb)
			(s->cb)(AUTH_EV_SESSION_CREATED,s);
	}
	return s;
}
/**
 * Creates an Authorization Session for the Server, from the application specific Session starting request
 * It generates a new id and adds the session to the cdp list of sessions
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* AAACreateServerAuthSession(AAAMessage *msg,int is_statefull,AAASessionCallback_f *cb,void *generic_data)
{
	AAASession *s;
	str id;

	if (!msg||!msg->sessionId||!msg->sessionId->data.len){
		LM_ERR("Error retrieving the Session-Id from the message.\n");
		return 0;
	}
	id.s = shm_malloc(msg->sessionId->data.len);
	if (!id.s){
		LM_ERR("Error allocating %d bytes of shm!\n",msg->sessionId->data.len);
		return 0;
	}else{
		id.len = msg->sessionId->data.len;
		memcpy(id.s,msg->sessionId->data.s,id.len);
		s=cdp_new_auth_session(id,0,is_statefull);
		if (s) {
			s->u.auth.generic_data = generic_data;
			s->cb = cb;
			if (s->cb)
				(s->cb)(AUTH_EV_SESSION_CREATED,s);
			update_auth_session_timers(&(s->u.auth),msg);
			auth_server_statefull_sm_process(s,AUTH_EV_RECV_REQ,msg);
			// this is a special exception where the session lock is not released
			//s=0;
		}
	}
	return s;
}

/**
 * Looks for a session with a given id and returns it if found
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* AAAGetSession(str id)
{
	return cdp_get_session(id);
}

/**
 * Looks for an Auth session with a given id and returns it if found
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* AAAGetAuthSession(str id)
{
	AAASession *x=cdp_get_session(id);
	if (x){
		switch (x->type){
			case AUTH_CLIENT_STATEFULL:
			case AUTH_CLIENT_STATELESS:
			case AUTH_SERVER_STATEFULL:
			case AUTH_SERVER_STATELESS:
				return x;
			default:
				AAASessionsUnlock(x->hash);
				return 0;
		}
	}
	return 0;
}

/**
 * Sends a Service terminated event to the session
 */
void AAATerminateAuthSession(AAASession *s)
{
	if (s->type==AUTH_CLIENT_STATEFULL) {
		auth_client_statefull_sm_process(s,AUTH_EV_SERVICE_TERMINATED,0);
	}
}

/**
 * Deallocates the memory taken by an Authorization Session
 * \note Must be called with a lock on the s->hash - will unlock it, so don't use the session after this
 */
void AAADropAuthSession(AAASession *s)
{
	AAADropSession(s);
}

/**
 * Creates an Accounting Session.
 */
AAASession* AAACreateAccSession(void *generic_data)
{
	return 0;
}

/**
 * Deallocates the memory taken by an Accounting Session
 */
void AAADropAccSession(AAASession *s)
{
	AAADropSession(s);
}

/**
 * Creates an Accounting Session (Credit control - RFC 4006) for the client
 * It generates a new id and adds the session to the cdp list of sessions
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* AAACreateCCAccSession(AAASessionCallback_f *cb, int is_session, void *generic_data)
{
	AAASession *s;
	str id;

	generate_session_id(&id, 0);

	s = cdp_new_cc_acc_session(id, is_session);
	if (s) {
		if (generic_data)
			s->u.auth.generic_data = generic_data;
		s->cb = cb;
		if (s->cb)
			(s->cb)(ACC_CC_EV_SESSION_CREATED, s);
	}
	return s;
}

/**
 * Starts accounting on time-based CC App session (Credit control - RFC 4006) for the client
 * @returns 0 on success, anything else on failure
 */
int AAAStartChargingCCAccSession(AAASession *s)
{
	if (s->type != ACCT_CC_CLIENT && s->u.cc_acc.type != ACC_CC_TYPE_SESSION) {
		LM_ERR("Can't start charging on a credit-control session that is not session based\n");
		return -1;
	}

	s->u.cc_acc.charging_start_time = time(0);
	return 0;
}
/**
 * Deallocates the memory taken by an Accounting Session (Credit Control - RFC 4006)
 */
void AAADropCCAccSession(AAASession *s)
{
	AAADropSession(s);
}

/**
 * Looks for a CC Acc dession with a given id and returns it if found
 * \note Returns with a lock on AAASession->hash. Unlock when done working with the result
 * @returns the new AAASession or null on error
 */
AAASession* AAAGetCCAccSession(str id)
{
	AAASession *x=cdp_get_session(id);
	if (x){
		switch (x->type){
			case ACCT_CC_CLIENT:
				return x;
			default:
				AAASessionsUnlock(x->hash);
				return 0;
		}
	}
	return 0;
}

/**
 * Sends a Service terminated event to the session
 */
void AAATerminateCCAccSession(AAASession *s)
{
	if (s->type==ACCT_CC_CLIENT) {
		//TODO: run state machine for terminate event
	}
}

void cdp_session_cleanup(cdp_session_t* s, AAAMessage* msg) {
	// Here we should drop the session ! and free everything related to it
	// but the generic_data thing should be freed by the callback function registered
	// when the auth session was created
	AAASessionCallback_f *cb;

	LM_DBG("cleaning up session %.*s\n", s->id.len, s->id.s);
	switch (s->type) {
		case ACCT_CC_CLIENT:
			if (s->cb) {
				cb = s->cb;
				(cb)(ACC_CC_EV_SESSION_TERMINATED, s);
			}
			AAADropCCAccSession(s);
			break;
		case AUTH_CLIENT_STATEFULL:
		case AUTH_CLIENT_STATELESS:
			if (s->cb) {
				cb = s->cb;
				(cb)(AUTH_EV_SERVICE_TERMINATED, s);
			}
			AAADropAuthSession(s);
			break;
		default:
			LM_WARN("asked to cleanup unknown/unhandled session type [%d]\n", s->type);
			break;
	}

}

