/*
 * 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 <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#include "peerstatemachine.h"
#include "diameter_api.h"
#include "diameter_ims.h"

#include "utils.h"
#include "receiver.h"
#include "peermanager.h"
#include "config.h"
#include "worker.h"
#include "authstatemachine.h"
#include "acctstatemachine.h"

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

/** Strings for the peer states */
char *dp_states[]={"Closed","Wait_Conn_Ack","Wait_I_CEA","Wait_Conn_Ack_Elect","Wait_Returns","R_Open","I_Open","Closing"};
/** Strings for the peer events */
char *dp_events[]={"Start","Stop","Timeout","Win_Election","R_Conn_CER","I_Rcv_Conn_Ack","I_Rcv_Conn_NAck",
	"I_Rcv_CER","I_Rcv_CEA","R_Rcv_CER","R_Rcv_CEA","I_Rcv_Non_CEA",
	"I_Rcv_DPR","I_Rcv_DPA","R_Rcv_DPR","R_Rcv_DPA",
	"I_Rcv_DWR","I_Rcv_DWA","R_Rcv_DWR","R_Rcv_DWA",
	"Send_Message","I_Rcv_Message","R_Rcv_Message","I_Peer_Disc","R_Peer_Disc"};

/**
 * Diameter base protocol state-machine processing.
 * This function get's called for every event. It updates the states and can trigger
 * other events.
 * @param p - the peer for which the event happened
 * @param event - the event that happened
 * @param msg - if a Diameter message was received this is it, or NULL if not
 * @param peer_locked - if the peer lock is already aquired
 * @param sock - socket that this event happened on, or NULL if unrelated
 * @returns 1 on success, 0 on error. Also the peer states are updated
 */
int sm_process(peer *p,peer_event_t event,AAAMessage *msg,int peer_locked,int sock)
{
	int result_code;
	peer_event_t next_event;
	int msg_received=0;

	if (!peer_locked) lock_get(p->lock);
	LM_DBG("sm_process(): Peer %.*s State %s Event %s\n",
			p->fqdn.len,p->fqdn.s,dp_states[p->state],dp_events[event-101]);

	switch (p->state){
		case Closed:
			switch (event){
				case Start:
					p->state = Wait_Conn_Ack;
					next_event = I_Snd_Conn_Req(p);
					if (next_event==I_Rcv_Conn_NAck)
						sm_process(p,next_event,0,1,p->I_sock);
					else{
						/* wait for fd to be transmitted to the respective receiver,
						 * in order to get a send pipe opened */
					}
					break;
				case R_Conn_CER:
					R_Accept(p,sock);
					result_code = Process_CER(p,msg);
					Snd_CEA(p,msg,result_code,p->R_sock);
					msg=0;
					if (result_code>=2000 && result_code<3000)
						p->state = R_Open;
					else {
						R_Disc(p);
						p->state = Closed;
					}
					log_peer_list(L_INFO);
					break;
				case Stop:
					/* just ignore this state */
					p->state = Closed;
					break;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;
		case Wait_Conn_Ack:
			switch(event){
				case I_Rcv_Conn_Ack:
					I_Snd_CER(p);
					p->state = Wait_I_CEA;
					break;
				case I_Rcv_Conn_NAck:
					Cleanup(p,p->I_sock);
					p->state = Closed;
					break;
				case R_Conn_CER:
					if (p->r_cer) AAAFreeMessage(&(p->r_cer));
					R_Accept(p,sock);
					result_code = Process_CER(p,msg);
					if (result_code>=2000 && result_code<3000){
						p->state = Wait_Conn_Ack_Elect;
						p->r_cer = msg;
					}
					else {
						p->state = Closed;
						AAAFreeMessage(&msg);
						R_Disc(p);
						I_Disc(p);
					}
					break;
				case Timeout:
					Error(p,p->I_sock);
					p->state = Closed;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;

		case Wait_I_CEA:
			switch(event){
				case I_Rcv_CEA:
					result_code = Process_CEA(p,msg);
					if (result_code>=2000 && result_code<3000)
						p->state = I_Open;
					else {
						Cleanup(p,p->I_sock);
						p->state = Closed;
					}
					log_peer_list(L_INFO);
					break;
				case R_Conn_CER:
					if (p->r_cer) AAAFreeMessage(&(p->r_cer));
					R_Accept(p,sock);
					result_code = Process_CER(p,msg);
					if (result_code>=2000 && result_code<3000){
						p->state = Wait_Returns;
						if (Elect(p,msg)){
							// won the election = > I_Disc(), R_Send_CEA()
							LM_INFO("sm_process():Wait_I_CEA Win Elect \n");
							sm_process(p,Win_Election,msg,1,sock);
						} else {
							// lost the election => wait for I_Recv_CEA, then R_Disc()
							LM_INFO("sm_process():Wait_I_CEA Lose Elect \n");
							p->r_cer = msg;
							sm_process(p,I_Peer_Disc,0,1,p->I_sock);
						}
					}
					else{
						Snd_CEA(p,msg,result_code,p->R_sock);
						R_Disc(p);
						I_Disc(p);
						p->state=Closed;
						break;
					}
					break;
				case I_Peer_Disc:
					I_Disc(p);
					p->state = Closed;
					break;
				case I_Rcv_Non_CEA:
					Error(p,p->I_sock);
					p->state = Closed;
					break;
				case Timeout:
					Error(p,p->I_sock);
					p->state = Closed;
					break;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;

		case Wait_Conn_Ack_Elect:
			switch(event){
				case I_Rcv_Conn_Ack:
					I_Snd_CER(p);
					if (p->r_cer){
						p->state = Wait_Returns;
						if (Elect(p,p->r_cer)){
							// won the election = > I_Disc(), R_Send_CEA()
							LM_INFO("sm_process():Wait_Conn_Ack_Elect Win Elect \n");
							sm_process(p,Win_Election,p->r_cer,1,sock);
							p->r_cer = 0;
						} else {
							// lost the election => wait for I_Recv_CEA, then R_Disc()
							LM_INFO("sm_process():Wait_Conn_Ack_Elect Lose Elect \n");
							AAAFreeMessage(&p->r_cer);
						}
					} else {
						LM_ERR("sm_process():Wait_Conn_Ack_Elect, I_Rcv_Conn_Ack, No R-CER ! \n");
						p->state = Wait_I_CEA;
					}
					break;
				case I_Rcv_Conn_NAck:
					Cleanup(p,p->I_sock);
					if (p->r_cer){
						result_code = Process_CER(p,p->r_cer);
						Snd_CEA(p,p->r_cer,result_code,p->R_sock);
						p->r_cer=0;
						if (result_code>=2000 && result_code<3000)
							p->state = R_Open;
						else {
							R_Disc(p);
							p->state = Closed;
							//	p->state = R_Open; /* Or maybe I should disconnect it?*/
						}
					}else{
						LM_ERR("sm_process():Wait_Conn_Ack_Elect, I_Rcv_Conn_NAck No R-CER ! \n");
					}
					break;
				case R_Peer_Disc:
					R_Disc(p);
					p->state = Wait_Conn_Ack;
					break;
				case R_Conn_CER:
					R_Reject(p,sock);
					AAAFreeMessage(&msg);
					p->state = Wait_Conn_Ack_Elect;
					break;
				case Timeout:
					if (p->I_sock>=0) Error(p,p->I_sock);
					if (p->R_sock>=0) Error(p,p->R_sock);
					p->state = Closed;
					break;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;

		case Wait_Returns:
			switch(event){
				case Win_Election:
					/* this is the Win Election -> I is dropped, R is kept */
					LM_INFO("sm_process():Wait_Returns Win Elect \n");
					I_Disc(p);
					result_code = Process_CER(p,msg);
					Snd_CEA(p,msg,result_code,p->R_sock);
					if (result_code>=2000 && result_code<3000){
						p->state = R_Open;
					}else{
						R_Disc(p);
						p->state = Closed;
					}
					break;
				case I_Peer_Disc:
					I_Disc(p);
					if (p->r_cer){
						result_code = Process_CER(p,p->r_cer);
						Snd_CEA(p,p->r_cer,result_code,p->R_sock);
						p->r_cer=0;
						if (result_code>=2000 && result_code<3000){
							p->state = R_Open;
						}else{
							R_Disc(p);
							p->state = Closed;
						}
					}else {
						LM_ERR("sm_process():Wait_Returns, I_Peer_Disc No R-CER ! \n");
					}
					break;
				case I_Rcv_CEA:
					/* this is the Lost Election -> I is kept, R dropped */
					LM_INFO("sm_process():Wait_Returns Lost Elect \n");
					R_Disc(p);
					result_code = Process_CEA(p,msg);
					if (result_code>=2000 && result_code<3000)
						p->state = I_Open;
					else {
						Cleanup(p,p->I_sock);
						p->state = Closed;
					}
					break;
				case R_Peer_Disc:
					R_Disc(p);
					p->state = Wait_I_CEA;
					break;
				case R_Conn_CER:
					R_Reject(p,p->R_sock);
					AAAFreeMessage(&msg);
					p->state = Wait_Returns;
					break;
				case Timeout:
					if (p->I_sock>=0) Error(p,p->I_sock);
					if (p->R_sock>=0) Error(p,p->R_sock);
					p->state = Closed;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;
		case R_Open:
			switch (event){
				case Send_Message:
					Snd_Message(p,msg);
					p->state = R_Open;
					break;
				case R_Rcv_Message:
					// delayed processing until out of the critical zone
					//Rcv_Process(p,msg);
					msg_received = 1;
					p->state = R_Open;
					break;
				case R_Rcv_DWR:
					result_code = Process_DWR(p,msg);
					Snd_DWA(p,msg,result_code,p->R_sock);
					p->state = R_Open;
					break;
				case R_Rcv_DWA:
					Process_DWA(p,msg);
					p->state = R_Open;
					break;
				case R_Conn_CER:
					R_Reject(p,sock);
					AAAFreeMessage(&msg);
					p->state = R_Open;
					break;
				case Stop:
					Snd_DPR(p);
					p->state = Closing;
					break;
				case R_Rcv_DPR:
					Snd_DPA(p,msg,AAA_SUCCESS,p->R_sock);
					R_Disc(p);
					p->state = Closed;
					log_peer_list(L_INFO);
					break;
				case R_Peer_Disc:
					R_Disc(p);
					p->state = Closed;
					log_peer_list(L_INFO);
					break;
				case R_Rcv_CER:
					result_code = Process_CER(p,msg);
					Snd_CEA(p,msg,result_code,p->R_sock);
					if (result_code>=2000 && result_code<3000)
						p->state = R_Open;
					else {
						/*R_Disc(p);p.state = Closed;*/
						p->state = R_Open; /* Or maybe I should disconnect it?*/
					}
					break;
				case R_Rcv_CEA:
					result_code = Process_CEA(p,msg);
					if (result_code>=2000 && result_code<3000)
						p->state = R_Open;
					else {
						/*R_Disc(p);p.state = Closed;*/
						p->state = R_Open; /* Or maybe I should disconnect it?*/
					}
					log_peer_list(L_INFO);
					break;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;
		case I_Open:
			switch (event){
				case Send_Message:
					Snd_Message(p,msg);
					p->state = I_Open;
					break;
				case I_Rcv_Message:
					// delayed processing until out of the critical zone
					//Rcv_Process(p,msg);
					msg_received = 1;
					p->state = I_Open;
					break;
				case I_Rcv_DWR:
					result_code = Process_DWR(p,msg);
					Snd_DWA(p,msg,result_code,p->I_sock);
					p->state =I_Open;
					break;
				case I_Rcv_DWA:
					Process_DWA(p,msg);
					p->state =I_Open;
					break;
				case R_Conn_CER:
					R_Reject(p,sock);
					AAAFreeMessage(&msg);
					p->state = I_Open;
					break;
				case Stop:
					Snd_DPR(p);
					p->state = Closing;
					break;
				case I_Rcv_DPR:
					Snd_DPA(p,msg,2001,p->I_sock);
					I_Disc(p);
					p->state = Closed;
					log_peer_list(L_INFO);
					break;
				case I_Peer_Disc:
					I_Disc(p);
					p->state = Closed;
					log_peer_list(L_INFO);
					break;
				case I_Rcv_CER:
					result_code = Process_CER(p,msg);
					Snd_CEA(p,msg,result_code,p->I_sock);
					if (result_code>=2000 && result_code<3000)
						p->state = I_Open;
					else {
						/*I_Disc(p);p.state = Closed;*/
						p->state = I_Open; /* Or maybe I should disconnect it?*/
					}
					break;
				case I_Rcv_CEA:
					result_code = Process_CEA(p,msg);
					if (result_code>=2000 && result_code<3000)
						p->state = I_Open;
					else {
						/*I_Disc(p);p.state = Closed;*/
						p->state = I_Open; /* Or maybe I should disconnect it?*/
					}
					break;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;
		case Closing:
			switch(event){
				case I_Rcv_DPA:
					I_Disc(p);
					p->state = Closed;
					break;
				case R_Rcv_DPA:
					R_Disc(p);
					p->state = Closed;
					break;
				case Timeout:
					if (p->I_sock>=0) Error(p,p->I_sock);
					if (p->R_sock>=0) Error(p,p->R_sock);
					p->state = Closed;
					break;
				case I_Peer_Disc:
					I_Disc(p);
					p->state = Closed;
					break;
				case R_Peer_Disc:
					R_Disc(p);
					p->state = Closed;
					break;
				default:
					LM_ERR("sm_process(): In state %s invalid event %s\n",
							dp_states[p->state],dp_events[event-101]);
					goto error;
			}
			break;
	}
	if (!peer_locked) lock_release(p->lock);

	if (msg_received)
		Rcv_Process(p,msg);

	return 1;
error:
	if (!peer_locked) lock_release(p->lock);
	return 0;
}

/**
 * Initiator - Send Connection Request.
 * Tries to connect to the remote peer's socket. If the connection is refused, the new
 * state is I_Rcv_Conn_NAck, else I_Rcv_Conn_Ack.
 * \note Must be called with a lock on the peer.
 * @param p - peer to send to
 * @returns the new state for the peer
 */
peer_event_t I_Snd_Conn_Req(peer *p)
{
	LM_INFO("I_Snd_Conn_Req(): Peer %.*s \n",
			p->fqdn.len,p->fqdn.s);

	if (p->I_sock>0) close(p->I_sock);
	p->I_sock = -1;
	p->I_sock = peer_connect(p);
	if (p->I_sock<0){
		return I_Rcv_Conn_NAck;
	}

	return I_Rcv_Conn_Ack;
}

/**
 * Cleans a socket by closing it and removing the reference
 * \note Must be called with a lock on the peer.
 * @param p - the peer
 * @param sock - socket to close
 */
void Cleanup(peer *p,int sock)
{
	if (sock<0) return;
	close(sock);
	if (p->I_sock == sock) p->I_sock = -1;
	if (p->R_sock == sock) p->R_sock = -1;
}

/**
 * Error action for a peer, triggers a Cleanup action.
 * \note Must be called with a lock on the peer.
 * @param p - the peer
 * @param sock - socket to close
 */
void Error(peer *p, int sock)
{
	Cleanup(p,sock);
}

/**
 * Adds the Applications to a Capability Exchange message.
 * \note Must be called with a lock on the peer.
 * @param msg - the message to add to (request/answer)
 * @param p - the peer to add applications from
 */
static inline void Snd_CE_add_applications(AAAMessage *msg,peer *p)
{
	int i;
	app_config *app;
	char x[4];
	AAA_AVP *avp1,*avp2;
	AAA_AVP_LIST list;
	str group;
	list.head=0;list.tail=0;

	for(i=0;i<config->applications_cnt;i++){
		app = config->applications+i;
		if (app->vendor==0){
			set_4bytes(x,app->id);
			AAACreateAndAddAVPToMessage(msg,
					(app->type==DP_AUTHORIZATION?AVP_Auth_Application_Id:AVP_Acct_Application_Id),
					AAA_AVP_FLAG_MANDATORY,0,x,4);
		}else{
			set_4bytes(x,app->vendor);
			avp1 = AAACreateAVP(AVP_Vendor_Id,AAA_AVP_FLAG_MANDATORY,0,x,4, AVP_DUPLICATE_DATA);
			AAAAddAVPToList(&list,avp1);

			set_4bytes(x,app->id);
			avp2 = AAACreateAVP((app->type==DP_AUTHORIZATION?AVP_Auth_Application_Id:AVP_Acct_Application_Id),
					AAA_AVP_FLAG_MANDATORY,0,x,4,AVP_DUPLICATE_DATA);
			AAAAddAVPToList(&list,avp2);

			group = AAAGroupAVPS(list);
			AAAFreeAVPList(&list);

			AAACreateAndAddAVPToMessage(msg,
					AVP_Vendor_Specific_Application_Id,
					AAA_AVP_FLAG_MANDATORY,0,group.s,group.len);
			shm_free(group.s);
		}
	}
	for(i=0;i<config->supported_vendors_cnt;i++){
		set_4bytes(x,config->supported_vendors[i]);
		AAACreateAndAddAVPToMessage(msg,AVP_Supported_Vendor_Id,AAA_AVP_FLAG_MANDATORY,0,x,4);
	}
}

/**
 * Send a Capability Exchange Request.
 * \note Must be called with a lock on the peer.
 * @param p - the peer to send to
 */
void I_Snd_CER(peer *p)
{
	AAAMessage *cer=0;
	//	AAA_AVP *avp;
	unsigned long ip;
	union {
		struct sockaddr addr;
		struct sockaddr_in in;
		struct sockaddr_in6 in6;
	} addr_u ;
	socklen_t addrlen;
	char x[18];

	cer = AAANewMessage(Code_CE,0,0,0);
	if (!cer) return;
	cer->hopbyhopId = next_hopbyhop();
	cer->endtoendId = next_endtoend();
	addrlen = sizeof(addr_u);
	if (getsockname(p->I_sock,&(addr_u.addr), &addrlen) == -1) {
		LM_ERR("I_Snd_CER(): Error on finding local host address > %s\n",strerror(errno));
	}else{
		switch(addr_u.addr.sa_family){
			case AF_INET:
				set_2bytes(x,1);
				ip = htonl(addr_u.in.sin_addr.s_addr);
				set_4bytes(x+2,ip);
				AAACreateAndAddAVPToMessage(cer,AVP_Host_IP_Address,AAA_AVP_FLAG_MANDATORY,0,x,6);
				break;
			case AF_INET6:
				set_2bytes(x,2);
				memcpy(x+2,addr_u.in6.sin6_addr.s6_addr,16);
				AAACreateAndAddAVPToMessage(cer,AVP_Host_IP_Address,AAA_AVP_FLAG_MANDATORY,0,x,18);
				break;
			default:
				LM_ERR("I_Snd_CER(): unknown address type with family %d\n",addr_u.addr.sa_family);
		}
	}

	set_4bytes(x,config->vendor_id);
	AAACreateAndAddAVPToMessage(cer,AVP_Vendor_Id,AAA_AVP_FLAG_MANDATORY,0,x,4);

	AAACreateAndAddAVPToMessage(cer,AVP_Product_Name,AAA_AVP_FLAG_MANDATORY,0,config->product_name.s,config->product_name.len);

	Snd_CE_add_applications(cer,p);
	//	peer_send(p,p->I_sock,cer,1);
	peer_send_msg(p,cer);
}


void add_peer_application(peer *p, int id, int vendor, app_type type)
{
	int i;
	if (!p->applications) return;
	for(i=0;i<p->applications_cnt;i++)
		if (p->applications[i].id == id &&
				p->applications[i].vendor == vendor &&
				p->applications[i].type == type) return;

  if (p->applications_cnt >= p->applications_max) {
		LM_ERR("Too many applications for this peer (max %i), not adding Application %i:%i.\n", p->applications_max, id, vendor);
		return;
	}

	p->applications[p->applications_cnt].id = id;
	p->applications[p->applications_cnt].vendor = vendor;
	p->applications[p->applications_cnt].type = type;
	LM_DBG("Application number %i with id %d and vendor %d added, maximum %i\n",
			p->applications_cnt, id, vendor, p->applications_max);
	p->applications_cnt++;
}

int count_Supported_Vendor_Id_AVPS(AAAMessage *msg)
{
	AAA_AVP* avp_vendor;
	int avp_vendor_cnt;

	avp_vendor = AAAFindMatchingAVP(msg,0,AVP_Supported_Vendor_Id,0,0);
	avp_vendor_cnt = 0;
	while (avp_vendor) {
		avp_vendor_cnt++;
		if (!avp_vendor->next)
			break;
		avp_vendor = AAAFindMatchingAVP(msg,avp_vendor->next,AVP_Supported_Vendor_Id,0,0);
	}
	LM_DBG("Found %i Supported_Vendor AVPS\n", avp_vendor_cnt);
	return avp_vendor_cnt;
}

void save_peer_applications(peer *p,AAAMessage *msg)
{
	int total_cnt=0;
	int supported_vendor_id_avp_cnt = 0;
	AAA_AVP *avp,*avp_vendor,*avp2;
	AAA_AVP_LIST group;
	int id,vendor;

	if (p->applications) {
		shm_free(p->applications);
		p->applications = 0;
		p->applications_cnt = 0;
	}

	supported_vendor_id_avp_cnt = count_Supported_Vendor_Id_AVPS(msg);

	if (supported_vendor_id_avp_cnt == 0) {
		LM_INFO("No Supported-Vendor-Id AVP found, assuming compability with %d vendor(s) from our CER msg\n",
				config->supported_vendors_cnt);
				supported_vendor_id_avp_cnt = 1;
	}

	for(avp=msg->avpList.head;avp;avp = avp->next)

		switch (avp->code){
			case AVP_Auth_Application_Id:
				total_cnt += supported_vendor_id_avp_cnt;
				break;
			case AVP_Acct_Application_Id:
				total_cnt += supported_vendor_id_avp_cnt;
				break;
			case AVP_Vendor_Specific_Application_Id:
				total_cnt+=6;/* wasteful, but let's skip decoding */
				break;
		}
	LM_DBG("Total count of applications is %d\n", total_cnt);

	p->applications_cnt = 0;
	p->applications = shm_malloc(sizeof(app_config)*total_cnt);
	p->applications_max = total_cnt;
	if (!p->applications){
		LM_ERR("save_peer_applications(): Error allocating %ld bytes! No applications saved...\n",
				(long int)(sizeof(app_config)*total_cnt));
		return;
	}
	for(avp=msg->avpList.head;avp;avp = avp->next)
	{

		switch (avp->code){
			case AVP_Auth_Application_Id:
				id = get_4bytes(avp->data.s);
				add_peer_application(p,id,0,DP_AUTHORIZATION);
				avp_vendor = AAAFindMatchingAVP(msg,0,AVP_Supported_Vendor_Id,0,0);
				while (avp_vendor) {
					vendor = get_4bytes(avp_vendor->data.s);
					LM_DBG("Found Supported Vendor for Application %i: %i\n", DP_AUTHORIZATION, vendor);
					add_peer_application(p,id,vendor,DP_AUTHORIZATION);
					if (!avp_vendor->next)
						break;
					avp_vendor = AAAFindMatchingAVP(msg,avp_vendor->next,AVP_Supported_Vendor_Id,0,AAA_FORWARD_SEARCH);
				}
				break;
			case AVP_Acct_Application_Id:
				id = get_4bytes(avp->data.s);
				add_peer_application(p,id,0,DP_ACCOUNTING);
				avp_vendor = AAAFindMatchingAVP(msg,0,AVP_Supported_Vendor_Id,0,0);
				while (avp_vendor) {
					vendor = get_4bytes(avp_vendor->data.s);
					LM_DBG("Found Supported Vendor for Application %i: %i\n", DP_ACCOUNTING, vendor);
					add_peer_application(p,id,vendor,DP_ACCOUNTING);
					if (!avp_vendor->next)
						break;
					avp_vendor = AAAFindMatchingAVP(msg,avp_vendor->next,AVP_Supported_Vendor_Id,0,AAA_FORWARD_SEARCH);
				}
				break;
			case AVP_Vendor_Specific_Application_Id:
				group = AAAUngroupAVPS(avp->data);
				avp_vendor = AAAFindMatchingAVPList(group,group.head,AVP_Vendor_Id,0,0);
				avp2 = AAAFindMatchingAVPList(group,group.head,AVP_Auth_Application_Id,0,AAA_FORWARD_SEARCH);
				if (avp_vendor&&avp2){
					vendor = get_4bytes(avp_vendor->data.s);
					id = get_4bytes(avp2->data.s);
					add_peer_application(p,id,vendor,DP_AUTHORIZATION);
				}
				avp2 = AAAFindMatchingAVPList(group,group.head,AVP_Acct_Application_Id,0,AAA_FORWARD_SEARCH);
				if (avp_vendor&&avp2){
					vendor = get_4bytes(avp_vendor->data.s);
					id = get_4bytes(avp2->data.s);
					add_peer_application(p,id,vendor,DP_ACCOUNTING);
				}
				AAAFreeAVPList(&group);
				break;

		}
	}
}

/**
 * Process a Capability Exchange Answer.
 * \note Must be called with a lock on the peer.
 * @param p - the peer that the CEA was received from
 * @param cea - the CEA message
 * @returns the result-code from CEA or AAA_UNABLE_TO_COMPLY if no result-code found
 */
int Process_CEA(peer *p,AAAMessage *cea)
{
	AAA_AVP *avp;
	avp = AAAFindMatchingAVP(cea,cea->avpList.head,AVP_Result_Code,0,0);
	save_peer_applications(p,cea);
	AAAFreeMessage(&cea);
	if (!avp) return AAA_UNABLE_TO_COMPLY;
	else return get_4bytes(avp->data.s);
}

/**
 * Initiator - disconnect peer.
 * \note Must be called with a lock on the peer.
 * @param p - the peer to disconnect
 */
void I_Disc(peer *p)
{
	if (p->I_sock>=0){
		close(p->I_sock);
		p->I_sock = -1;
	}
}

/**
 * Receiver - disconnect peer.
 * \note Must be called with a lock on the peer.
 * @param p - the peer to disconnect
 */
void R_Disc(peer *p)
{
	if (p->R_sock>=0){
		close(p->R_sock);
		p->R_sock = -1;
	}
}

/**
 * Process a Diameter Watch-dog Request.
 * The last activity timer is updated in the sm_process(), on any message received.
 * \note Must be called with a lock on the peer.
 * @param p - the peer that the DWR was received from
 * @param dwr - the DWR message
 * @returns AAA_SUCCESS
 */
int Process_DWR(peer *p,AAAMessage *dwr)
{
	return AAA_SUCCESS;
}

/**
 * Process a Diameter Watch-dog Answer.
 * The flag for waiting a DWA is reseted.
 * \note Must be called with a lock on the peer.
 * @param p - the peer that the DWR was received from
 * @param dwa - the DWA message
 */
void Process_DWA(peer *p,AAAMessage *dwa)
{
	p->waitingDWA = 0;
	AAAFreeMessage(&dwa);
}

/**
 * Sends a Diameter Watch-dog Request.
 * The flag for waiting a DWA is set.
 * \note Must be called with a lock on the peer.
 * @param p - the peer that the DWR was received from
 */
void Snd_DWR(peer *p)
{
	AAAMessage *dwr=0;

	dwr = AAANewMessage(Code_DW,0,0,0);
	if (!dwr) return;
	dwr->hopbyhopId = next_hopbyhop();
	dwr->endtoendId = next_endtoend();
	if (p->state==I_Open)
		peer_send_msg(p,dwr);
	else
		peer_send_msg(p,dwr);
}

/**
 * Sends a Diameter Watch-dog Answer.
 * \note Must be called with a lock on the peer.
 * @param p - the peer that the DWR was received from
 * @param dwr - the DWR message
 * @param result_code - the Result-Code to attach to DWA
 * @param sock - socket to send on
 */
void Snd_DWA(peer *p,AAAMessage *dwr,int result_code,int sock)
{
	AAAMessage *dwa;
	char x[4];

	dwa = AAANewMessage(Code_DW,0,0,dwr);
	if (!dwa) goto done;

	set_4bytes(x,result_code);
	AAACreateAndAddAVPToMessage(dwa,AVP_Result_Code,AAA_AVP_FLAG_MANDATORY,0,x,4);

	peer_send_msg(p,dwa);
done:
	AAAFreeMessage(&dwr);
}

/**
 * Sends a Disconnect Peer Request.
 * \note Must be called with a lock on the peer.
 * @param p - the peer to send to
 */
void Snd_DPR(peer *p)
{
	AAAMessage *dpr=0;
	char x[4];

	dpr = AAANewMessage(Code_DP,0,0,0);
	if (!dpr) return;
	dpr->hopbyhopId = next_hopbyhop();
	dpr->endtoendId = next_endtoend();

	set_4bytes(x,0/*busy*/);
	AAACreateAndAddAVPToMessage(dpr,AVP_Disconnect_Cause,AAA_AVP_FLAG_MANDATORY,0,x,4);

	if (p->state==I_Open)
		peer_send_msg(p,dpr);
	else
		peer_send_msg(p,dpr);
}

/**
 * Sends a Disconnect Peer Answer.
 * \note Must be called with a lock on the peer.
 * @param p - the peer to send to
 * @param dpr - the DPR message
 * @param result_code - the Result-Code to attach to DPA
 * @param sock - socket to send on
 */
void Snd_DPA(peer *p,AAAMessage *dpr,int result_code,int sock)
{
	AAAMessage *dpa;

	dpa = AAANewMessage(Code_DP,0,0,dpr);
	if (dpa) peer_send_msg(p,dpa);
	AAAFreeMessage(&dpr);
}

/**
 * Receiver - Accept a connection.
 * \note Must be called with a lock on the peer.
 * @param p - peer identification
 * @param sock - socket to communicate through
 */
void R_Accept(peer *p,int sock)
{
	p->R_sock = sock;
	touch_peer(p);
}

/**
 * Receiver - Reject a connection.
 * \note Must be called with a lock on the peer.
 * @param p - peer identification
 * @param sock - socket to communicate through
 */
void R_Reject(peer *p,int sock)
{
	close(sock);
}

/**
 * Process a Capabilities Exchange Request.
 * Checks whether there are common applications.
 * \note Must be called with a lock on the peer.
 * @param p - peer identification
 * @param cer - the CER message
 * @returns the Result-Code of the operation, AAA_SUCCESS or AAA_NO_COMMON_APPLICATION
 */
int Process_CER(peer *p,AAAMessage *cer)
{
	int common_app=0;
	AAA_AVP *avp,*avp_vendor,*avp2;
	AAA_AVP_LIST group;
	int i,id,vendor;
	for(avp=cer->avpList.head;avp;avp = avp->next)
	{
		switch (avp->code){
			case AVP_Auth_Application_Id:
				id = get_4bytes(avp->data.s);
				for(i=0;i<config->applications_cnt;i++)
					if (id == config->applications[i].id &&
							config->applications[i].vendor==0 &&
							config->applications[i].type==DP_AUTHORIZATION) common_app++;
				break;
			case AVP_Acct_Application_Id:
				id = get_4bytes(avp->data.s);
				for(i=0;i<config->applications_cnt;i++)
					if (id == config->applications[i].id &&
							config->applications[i].vendor==0 &&
							config->applications[i].type==DP_ACCOUNTING) common_app++;
				break;
			case AVP_Vendor_Specific_Application_Id:
				group = AAAUngroupAVPS(avp->data);
				avp_vendor = AAAFindMatchingAVPList(group,group.head,AVP_Vendor_Id,0,0);
				avp2 = AAAFindMatchingAVPList(group,group.head,AVP_Auth_Application_Id,0,0);
				if (avp_vendor&&avp2){
					vendor = get_4bytes(avp_vendor->data.s);
					id = get_4bytes(avp2->data.s);
					for(i=0;i<config->applications_cnt;i++)
						if (id == config->applications[i].id &&
								config->applications[i].vendor==vendor &&
								config->applications[i].type==DP_AUTHORIZATION) common_app++;

				}
				avp2 = AAAFindMatchingAVPList(group,group.head,AVP_Acct_Application_Id,0,0);
				if (avp_vendor&&avp2){
					vendor = get_4bytes(avp_vendor->data.s);
					id = get_4bytes(avp2->data.s);
					for(i=0;i<config->applications_cnt;i++)
						if (id == config->applications[i].id &&
								config->applications[i].vendor==vendor &&
								config->applications[i].type==DP_ACCOUNTING) common_app++;

				}
				AAAFreeAVPList(&group);
				break;

		}
	}

	if (common_app!=0){
		save_peer_applications(p,cer);
		return AAA_SUCCESS;
	}else
		return AAA_NO_COMMON_APPLICATION;
}

/**
 * Send a Capabilities Exchange Answer.
 * Checks whether there are common applications.
 * \note Must be called with a lock on the peer.
 * @param p - peer identification
 * @param cer - the CER message
 * @param result_code - the Result-Code to send
 * @param sock - socket to send through
 */
void Snd_CEA(peer *p,AAAMessage *cer,int result_code,int sock)
{
	AAAMessage *cea;
	unsigned int ip;
	union {
		struct sockaddr addr;
		struct sockaddr_in in;
		struct sockaddr_in6 in6;
	} addr_u ;
	socklen_t addrlen;
	char x[18];

	cea = AAANewMessage(Code_CE,0,0,cer);
	if (!cea) goto done;

	addrlen = sizeof(addr_u);
	if (getsockname(sock, &(addr_u.addr), &addrlen) == -1) {
		LM_ERR("Snd_CEA(): Error on finding local host address > %s\n",strerror(errno));
	}else{
		switch(addr_u.addr.sa_family){
			case AF_INET:
				set_2bytes(x,1);
				ip = htonl(addr_u.in.sin_addr.s_addr);
				set_4bytes(x+2,ip);
				AAACreateAndAddAVPToMessage(cea,AVP_Host_IP_Address,AAA_AVP_FLAG_MANDATORY,0,x,6);
				break;
			case AF_INET6:
				set_2bytes(x,2);
				memcpy(x+2,addr_u.in6.sin6_addr.s6_addr,16);
				AAACreateAndAddAVPToMessage(cea,AVP_Host_IP_Address,AAA_AVP_FLAG_MANDATORY,0,x,18);
				break;
			default:
				LM_ERR("Snd_CEA(): unknown address type with family %d\n",addr_u.addr.sa_family);
		}
	}

	set_4bytes(x,config->vendor_id);
	AAACreateAndAddAVPToMessage(cea,AVP_Vendor_Id,AAA_AVP_FLAG_MANDATORY,0,x,4);

	AAACreateAndAddAVPToMessage(cea,AVP_Product_Name,AAA_AVP_FLAG_MANDATORY,0,config->product_name.s,config->product_name.len);

	set_4bytes(x,result_code);
	AAACreateAndAddAVPToMessage(cea,AVP_Result_Code,AAA_AVP_FLAG_MANDATORY,0,x,4);

	Snd_CE_add_applications(cea,p);

	peer_send(p,sock,cea,1);
done:
	AAAFreeMessage(&cer);
}

/**
 * Perform the Election mechanism.
 * When 2 peers connect to each-other at the same moment, an election is triggered.
 * That means that based on the alphabetical relation between the FQDNs, one peer is
 * kept as winner on Initiator and that connection is kept, while the other keeps the same
 * connection as Receiver and drops its Initiator connection.
 * \note Must be called with a lock on the peer.
 * @param p - peer identification
 * @param cer - the CER message
 * @returns 1 if winning, 0 if loosing
 */
int Elect(peer *p,AAAMessage *cer)
{
	/* returns if we win the election */
	AAA_AVP *avp;
	str remote,local;
	int i,d;

	if(!cer){
		LM_ERR("Elect cer is NULL \n");
		// return lose
		return 0;
	}

	local = config->fqdn;

	avp = AAAFindMatchingAVP(cer,cer->avpList.head,AVP_Origin_Host,0,0);
	if (!avp) {
		return 1;
	}else{
		remote = avp->data;
		for(i=0;i<remote.len&&i<local.len;i++){
			d = ((unsigned char) local.s[i])-((unsigned char) remote.s[i]);
			if (d>0) return 1;
			if (d<0) return 0;
		}
		if (local.len>remote.len) return 1;
		return 0;
	}
}

/**
 * Sends a message to the peer.
 * \note Must be called with a lock on the peer.
 * @param p - peer to send to
 * @param msg - message to send
 */
void Snd_Message(peer *p, AAAMessage *msg)
{
	AAASession *session=0;
	int rcode;
	int send_message_before_session_sm=0;
	LM_DBG("Snd_Message called to peer [%.*s] for %s with code %d \n",
			p->fqdn.len,p->fqdn.s,is_req(msg)?"request":"response",msg->commandCode);
	if (msg->sessionId) session = cdp_get_session(msg->sessionId->data);

	if (session){
		LM_DBG("There is a session of type %d\n",session->type);
		switch (session->type){
			case ACCT_CC_CLIENT:
				if (is_req(msg)) {
					LM_DBG("this is a request for CC app\n");
					cc_acc_client_stateful_sm_process(session, ACC_CC_EV_SEND_REQ, msg);
					session = 0;
				} else {
					LM_DBG("this is a response to CC app\n");
				}
				break;
			case AUTH_CLIENT_STATEFULL:
				if (is_req(msg)) {
					auth_client_statefull_sm_process(session,AUTH_EV_SEND_REQ,msg);
					session = 0;
				}
				else {
					if (msg->commandCode == IMS_ASA){
						if (!msg->res_code){
							msg->res_code = AAAFindMatchingAVP(msg,0,AVP_Result_Code,0,0);
						}
						if (!msg->res_code) {
							auth_client_statefull_sm_process(session,AUTH_EV_SEND_ASA_UNSUCCESS,msg);
							session = 0;
						}
						else {
							rcode = get_4bytes(msg->res_code->data.s);
							if (rcode>=2000 && rcode<3000) {
								peer_send_msg(p,msg);
								send_message_before_session_sm=1;
								auth_client_statefull_sm_process(session,AUTH_EV_SEND_ASA_SUCCESS,msg);
								session = 0;
							}
							else {
								auth_client_statefull_sm_process(session,AUTH_EV_SEND_ASA_UNSUCCESS,msg);
								session = 0;
							}
						}

					}else {
						auth_client_statefull_sm_process(session,AUTH_EV_SEND_ANS,msg);
						session = 0;
					}
				}
				break;
			case AUTH_SERVER_STATEFULL:
				LM_DBG("this message is matched here to see what request or reply it is\n");
				if (is_req(msg))
				{
					if (msg->commandCode== IMS_ASR)
					{
						LM_DBG("ASR\n");
						auth_server_statefull_sm_process(session,AUTH_EV_SEND_ASR,msg);
						session = 0;
					} else {
						//would be a RAR but ok!
						LM_DBG("other request\n");
						auth_server_statefull_sm_process(session,AUTH_EV_SEND_REQ,msg);
						session = 0;
					}
				} else {
					if (msg->commandCode == IMS_STR)
					{
						LM_DBG("STA\n");
						auth_server_statefull_sm_process(session,AUTH_EV_SEND_STA,msg);
						session = 0;
					} else {
						LM_DBG("other reply\n");
						auth_server_statefull_sm_process(session,AUTH_EV_SEND_ANS,msg);
						session = 0;
					}
				}
				break;
			default:
				break;
		}
		if (session) AAASessionsUnlock(session->hash);
	}
	if (!send_message_before_session_sm) peer_send_msg(p,msg);

}

/**
 * Processes an incoming message.
 * This actually just puts the message into a message queue. One worker will pick-it-up
 * and do the actual processing.
 * \note Must be called with a lock on the peer.
 * @param p - peer received from
 * @param msg - the message received
 */
void Rcv_Process(peer *p, AAAMessage *msg)
{
	AAASession *session=0;
	int nput=0;
	if (msg->sessionId) session = cdp_get_session(msg->sessionId->data);

	if (session){
		switch (session->type){
			case ACCT_CC_CLIENT:
				if (is_req(msg)){
					LM_WARN("unhandled receive request on Credit Control Acct session\n");
					AAASessionsUnlock(session->hash);	//must be called because we dont call state machine here
					session = 0; //we dont call SM here so we mustnt set to 0
				} else {
					cc_acc_client_stateful_sm_process(session, ACC_CC_EV_RECV_ANS, msg);
					session = 0;
				}
				break;
			case AUTH_CLIENT_STATEFULL:
				if (is_req(msg)){
					if (msg->commandCode==IMS_ASR)
						auth_client_statefull_sm_process(session,AUTH_EV_RECV_ASR,msg);
					else
						auth_client_statefull_sm_process(session,AUTH_EV_RECV_REQ,msg);
					session = 0;
				}else {
					if (msg->commandCode==IMS_STA)
						nput=auth_client_statefull_sm_process(session,AUTH_EV_RECV_STA,msg);
					else
						auth_client_statefull_sm_process(session,AUTH_EV_RECV_ANS,msg);
					session = 0;
				}
				break;
			case AUTH_SERVER_STATEFULL:
				if (is_req(msg))
				{
					if (msg->commandCode==IMS_STR)
					{
						auth_server_statefull_sm_process(session,AUTH_EV_RECV_STR,msg);
					} else {
						auth_server_statefull_sm_process(session,AUTH_EV_RECV_REQ,msg);
					}
					session = 0;
				}else{
					if (msg->commandCode==IMS_ASA)
						auth_server_statefull_sm_process(session,AUTH_EV_RECV_ASA,msg);
					else
						auth_server_statefull_sm_process(session,AUTH_EV_RECV_ANS,msg);
					session = 0;
				}
				break;
			default:
				AAASessionsUnlock(session->hash);
				session =0;
				break;
		}
	}else{
		if (msg->sessionId){
			if (msg->commandCode == IMS_ASR)
				auth_client_statefull_sm_process(0,AUTH_EV_RECV_ASR,msg);
		}

	}
	if (!nput && !put_task(p,msg)){
		LM_ERR("Rcv_Process(): Queue refused task\n");
		if (msg) AAAFreeMessage(&msg);
	}
	//if (msg) LM_ERR("Rcv_Process(): task added to queue command %d, flags %#1x endtoend %u hopbyhop %u\n",msg->commandCode,msg->flags,msg->endtoendId,msg->hopbyhopId);

	//	AAAPrintMessage(msg);

}
