/*
 * $Id$
 *
 * 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
 *
 *
 *
 * History:
 * --------
 *  2011-02-02  initial version (jason.penton)
 */

#include "../../core/mem/shm_mem.h"
#include "../../core/parser/sdp/sdp.h"
#include "../cdp_avp/cdp_avp_mod.h"

#include "../../modules/ims_dialog/dlg_load.h"
#include "../../modules/tm/tm_load.h"
#include "../ims_usrloc_pcscf/usrloc.h"
#include "rx_authdata.h"

#include "rx_aar.h"
#include "rx_avp.h"

#include "../../lib/ims/ims_getters.h"

#include "ims_qos_mod.h"

#include "../../lib/ims/useful_defs.h"
#include "ims_qos_stats.h"


#define macro_name(_rc) #_rc

//extern struct tm_binds tmb;
extern usrloc_api_t ul;

extern struct ims_qos_counters_h ims_qos_cnts_h;

extern int authorize_video_flow;
extern int _ims_qos_suspend_transaction;

extern str af_signaling_ip;
extern str af_signaling_ip6;
extern str component_media_type;
extern str flow_protocol;
extern ims_qos_params_t _imsqos_params;

str IMS_Serv_AVP_val = {"IMS Services", 12};
str IMS_Em_Serv_AVP_val = {"Emergency IMS Call", 18};
str IMS_Reg_AVP_val = {"IMS Registration", 16};

static void free_dialog_data(void *data)
{
	str *rx_session_id = (str *)data;
	if(rx_session_id) {
		if(rx_session_id->s) {
			shm_free(rx_session_id->s);
			rx_session_id->s = 0;
		}
		shm_free(rx_session_id);
		rx_session_id = 0;
	}
}

void async_aar_callback(
		int is_timeout, void *param, AAAMessage *aaa, long elapsed_msecs)
{
	struct cell *t = 0;
	unsigned int cdp_result;
	int result = CSCF_RETURN_ERROR;
	rx_authsessiondata_t *p_session_data = 0;
	AAASession *auth = 0;

	LM_DBG("Received AAR callback\n");
	saved_transaction_t *data = (saved_transaction_t *)param;

	LM_DBG("received AAA answer");

	if(_ims_qos_suspend_transaction) {
		if(tmb.t_lookup_ident(&t, data->tindex, data->tlabel) < 0) {
			LM_ERR("t_continue: transaction not found\n");
			goto error;
		} else {
			LM_DBG("t_continue: transaction found\n");
		}
		//we have T, lets restore our state (esp. for AVPs)
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_URI, &t->uri_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_URI, &t->uri_avps_to);
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_USER, &t->user_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_USER, &t->user_avps_to);
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_DOMAIN, &t->domain_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_DOMAIN, &t->domain_avps_to);
	}

	if(is_timeout != 0) {
		LM_ERR("Error timeout when sending AAR message via CDP\n");
		counter_inc(ims_qos_cnts_h.media_aar_timeouts);
		goto error;
	}
	if(!aaa) {
		LM_ERR("Error sending message via CDP\n");
		goto error;
	}

	counter_inc(ims_qos_cnts_h.media_aars);
	counter_add(ims_qos_cnts_h.media_aar_response_time, elapsed_msecs);
	counter_inc(ims_qos_cnts_h.media_aar_replies_received);

	/* Process the response to AAR, retrieving result code and associated Rx session ID */
	if(rx_process_aaa(aaa, &cdp_result) < 0) {
		LM_ERR("Failed to process AAA from PCRF\n"); //puri.host.len, puri.host.s);
		goto error;
	}

	if(cdp_result >= 2000 && cdp_result < 3000) {
		LM_DBG("Success, received code: [%i] from PCRF for AAR request\n",
				cdp_result);
		counter_inc(ims_qos_cnts_h.successful_media_aars);

		if(!aaa->sessionId) {
			LM_ERR("NULL AAA sessionId from PCRF!\n");
			goto error;
		}

		LM_DBG("Auth session ID [%.*s]", aaa->sessionId->data.len,
				aaa->sessionId->data.s);

		if(!data->aar_update) {
			LM_DBG("This is an AAA response to an initial AAR");
			//need to set Rx auth data to say this session has been successfully opened
			//This is used elsewhere to prevent acting on termination events when the session has not been opened
			//getting auth session
			auth = cdpb.AAAGetAuthSession(aaa->sessionId->data);
			if(!auth) {
				LM_DBG("Could not get Auth Session for session id: [%.*s]\n",
						aaa->sessionId->data.len, aaa->sessionId->data.s);
				goto error;
			}
			//getting session data
			p_session_data = (rx_authsessiondata_t *)auth->u.auth.generic_data;
			if(!p_session_data) {
				LM_DBG("Could not get session data on Auth Session for session "
					   "id: [%.*s]\n",
						aaa->sessionId->data.len, aaa->sessionId->data.s);
				if(auth)
					cdpb.AAASessionsUnlock(auth->hash);
				goto error;
			}
			p_session_data->session_has_been_opened = 1;
			counter_inc(ims_qos_cnts_h.active_media_rx_sessions);
			counter_inc(ims_qos_cnts_h.media_rx_sessions);

			if(auth)
				cdpb.AAASessionsUnlock(auth->hash);

			str *passed_rx_session_id = shm_malloc(sizeof(struct _str));
			passed_rx_session_id->s = 0;
			passed_rx_session_id->len = 0;
			STR_SHM_DUP(*passed_rx_session_id, aaa->sessionId->data,
					"cb_passed_rx_session_id");
			LM_DBG("passed rx session id [%.*s]", passed_rx_session_id->len,
					passed_rx_session_id->s);
			dlgb.register_dlgcb_nodlg(data->dlg,
					DLGCB_TERMINATED | DLGCB_DESTROY | DLGCB_EXPIRED
							| DLGCB_RESPONSE_WITHIN | DLGCB_CONFIRMED
							| DLGCB_FAILED,
					callback_dialog, (void *)(passed_rx_session_id),
					free_dialog_data);
		} else {
			dlgb.release_dlg(data->dlg);
		}
		result = CSCF_RETURN_TRUE;
	} else {
		LM_DBG("Received negative reply from PCRF for AAR Request\n");
		counter_inc(ims_qos_cnts_h.failed_media_aars);
		//we don't free rx_authdata_p here - it is free-ed when the CDP session expires
		goto error; // if it is not a success then that means I want to reject this call!
	}

	//set success response code AVP
	create_return_code(result);
	goto done;

out_of_memory:
error:
	//set failure response code
	create_return_code(result);

done:
	if(t)
		tmb.unref_cell(t);
	//free memory
	if(aaa)
		cdpb.AAAFreeMessage(&aaa);

	if(_ims_qos_suspend_transaction) {
		tmb.t_continue(data->tindex, data->tlabel, data->act);
	}
	free_saved_transaction_global_data(data);
}

void async_aar_reg_callback(
		int is_timeout, void *param, AAAMessage *aaa, long elapsed_msecs)
{
	struct cell *t = 0;
	pcontact_t *pcontact;
	unsigned int cdp_result;
	struct pcontact_info ci;
	udomain_t *domain_t;
	int finalReply = 0;
	AAASession *auth = 0;
	rx_authsessiondata_t *p_session_data = 0;
	int result = CSCF_RETURN_ERROR;
	pcontact_info_t contact_info;

	LM_DBG("Received AAR callback\n");
	saved_transaction_local_t *local_data = (saved_transaction_local_t *)param;
	saved_transaction_t *data = local_data->global_data;
	domain_t = data->domain;

	int is_rereg = local_data->is_rereg;

	//before we do anything else, lets decrement the reference counter on replies
	lock_get(data->lock);
	data->answers_not_received--;
	if(data->answers_not_received <= 0) {
		finalReply = 1;
	}
	if(data->ignore_replies) { //there was obv. a subsequent error AFTER we had sent one/more AAR's - so we can ignore these replies and just free memory
		free_saved_transaction_data(local_data);
		if(finalReply) {
			free_saved_transaction_global_data(data);
		}
		return;
	}
	lock_release(data->lock);

	LM_DBG("received answer and we are waiting for [%d] answers so far "
		   "failures flag is [%d]\n",
			data->answers_not_received, data->failed);

	if(_ims_qos_suspend_transaction) {
		if(tmb.t_lookup_ident(&t, data->tindex, data->tlabel) < 0) {
			LM_ERR("t_continue: transaction not found\n");
			goto error;
		}
		//we have T, lets restore our state (esp. for AVPs)
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_URI, &t->uri_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_URI, &t->uri_avps_to);
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_USER, &t->user_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_USER, &t->user_avps_to);
		set_avp_list(AVP_TRACK_FROM | AVP_CLASS_DOMAIN, &t->domain_avps_from);
		set_avp_list(AVP_TRACK_TO | AVP_CLASS_DOMAIN, &t->domain_avps_to);
	}

	if(is_timeout != 0) {
		LM_ERR("Error timeout when sending AAR message via CDP\n");
		counter_inc(ims_qos_cnts_h.registration_aar_timeouts);
		goto error;
	}
	if(!aaa) {
		LM_ERR("Error sending message via CDP\n");
		goto error;
	}

	counter_inc(ims_qos_cnts_h.registration_aars);
	counter_add(ims_qos_cnts_h.registration_aar_response_time, elapsed_msecs);
	counter_inc(ims_qos_cnts_h.registration_aar_replies_received);

	/* Process the response to AAR, retrieving result code and associated Rx session ID */
	if(rx_process_aaa(aaa, &cdp_result) < 0) {
		LM_ERR("Failed to process AAA from PCRF\n"); //puri.host.len, puri.host.s);
		goto error;
	}

	if(cdp_result >= 2000 && cdp_result < 3000) {
		counter_inc(ims_qos_cnts_h.successful_registration_aars);
		if(is_rereg) {
			LM_DBG("this is a re-registration, therefore we don't need to do "
				   "anything except know that the subscription was "
				   "successful\n");
			result = CSCF_RETURN_TRUE;
			create_return_code(result);
			goto done;
		}

		if(!aaa->sessionId) {
			LM_ERR("NULL AAA sessionId from PCRF!\n");
			goto error;
		}

		//need to set Rx auth data to say this session has been successfully opened
		//This is used elsewhere to prevent acting on termination events when the session has not been opened
		//getting auth session
		auth = cdpb.AAAGetAuthSession(aaa->sessionId->data);
		if(!auth) {
			LM_DBG("Could not get Auth Session for session id: [%.*s]\n",
					aaa->sessionId->data.len, aaa->sessionId->data.s);
			goto error;
		}
		//getting session data
		p_session_data = (rx_authsessiondata_t *)auth->u.auth.generic_data;
		if(!p_session_data) {
			LM_DBG("Could not get session data on Auth Session for session id: "
				   "[%.*s]\n",
					aaa->sessionId->data.len, aaa->sessionId->data.s);
			if(auth)
				cdpb.AAASessionsUnlock(auth->hash);
			goto error;
		}
		p_session_data->session_has_been_opened = 1;
		counter_inc(ims_qos_cnts_h.active_registration_rx_sessions);

		if(auth)
			cdpb.AAASessionsUnlock(auth->hash);


		LM_DBG("Success, received code: [%i] from PCRF for AAR request "
			   "(contact: [%.*s]), (auth session id: %.*s)\n",
				cdp_result, local_data->contact.len, local_data->contact.s,
				local_data->auth_session_id.len, local_data->auth_session_id.s);
		LM_DBG("Registering for Usrloc callbacks on DELETE\n");

		ul.lock_udomain(domain_t, &local_data->via_host, local_data->via_port,
				local_data->via_proto);
		memset(&contact_info, 0, sizeof(struct pcontact_info));
		contact_info.received_host = local_data->recv_host;
		contact_info.received_port = local_data->recv_port;
		contact_info.received_proto = local_data->recv_proto;
		contact_info.searchflag = (1 << SEARCH_RECEIVED);


		contact_info.aor = local_data->contact;
		contact_info.via_host = local_data->via_host;
		contact_info.via_port = local_data->via_port;
		contact_info.via_prot = local_data->via_proto;
		contact_info.reg_state = PCONTACT_ANY;

		if(ul.get_pcontact(domain_t, &contact_info, &pcontact, 0) != 0) {
			LM_ERR("Shouldn't get here, can't find contact....\n");
			ul.unlock_udomain(domain_t, &local_data->via_host,
					local_data->via_port, local_data->via_proto);
			goto error;
		}

		//at this point we have the contact
		/*set the contact state to say we have successfully done ARR for register
         * and that we don't need to do it again
         * for the duration of the registration.
         * */
		if(ul.update_rx_regsession(
				   domain_t, &local_data->auth_session_id, pcontact)
				!= 0) {
			LM_ERR("unable to update pcontact......\n");
			ul.unlock_udomain(domain_t, &local_data->via_host,
					local_data->via_port, local_data->via_proto);
			goto error;
		}
		memset(&ci, 0, sizeof(struct pcontact_info));
		ci.reg_state = PCONTACT_REG_PENDING_AAR;
		ci.num_service_routes = 0;
		ci.num_public_ids = 0;
		LM_DBG("impu: [%.*s] updating status to PCONTACT_REG_PENDING\n",
				pcontact->aor.len, pcontact->aor.s);
		ul.update_pcontact(domain_t, &ci, pcontact);
		//register for callbacks on contact
		ul.register_ulcb(pcontact, PCSCF_CONTACT_DELETE | PCSCF_CONTACT_EXPIRE,
				callback_pcscf_contact_cb, NULL);
		ul.unlock_udomain(domain_t, &local_data->via_host, local_data->via_port,
				local_data->via_proto);
		result = CSCF_RETURN_TRUE;
	} else {
		LM_DBG("Received negative reply from PCRF for AAR Request\n");
		counter_inc(ims_qos_cnts_h.failed_registration_aars);
		result = CSCF_RETURN_FALSE;
		goto error;
	}

	//set success response code AVP
	create_return_code(result);
	goto done;

error:
	//set failure response code
	create_return_code(result);

done:
	if(t)
		tmb.unref_cell(t);
	//free memory
	if(aaa)
		cdpb.AAAFreeMessage(&aaa);

	if(finalReply) {
		if(_ims_qos_suspend_transaction) {
			tmb.t_continue(data->tindex, data->tlabel, data->act);
		}
		free_saved_transaction_global_data(data);
	}
	free_saved_transaction_data(local_data);
}

/* handle an AAA response to an AAR for resource reservation for a successful registration or initiated/updated dialog
 * @param aaa - the diameter reply
 * @return -  1 if result code found and processing ok, -1 if error
 */
int rx_process_aaa(AAAMessage *aaa, unsigned int *rc)
{
	int ret = 1;

	ret = rx_get_result_code(aaa, rc);

	if(ret == 0) {
		LM_DBG("AAA message without result code\n");
		return ret;
	}

	return ret;
}

/** Helper function for adding media component AVPs - uses previously stored flow descriptions not SDP from messages*/
int add_media_components_using_current_flow_description(
		AAAMessage *aar, rx_authsessiondata_t *p_session_data)
{

	flow_description_t *flow_description;
	int add_flow = 1;

	flow_description = p_session_data->first_current_flow_description;
	if(!flow_description) {
		return -1;
	}
	while(flow_description) {

		if(!authorize_video_flow) {
			if(strncmp(flow_description->media.s, "video", 5) == 0) {
				add_flow = 0;
			}
		}

		if(add_flow) {
			rx_add_media_component_description_avp(aar,
					flow_description->stream_num, &flow_description->media,
					&flow_description->req_sdp_ip_addr,
					&flow_description->req_sdp_port,
					&flow_description->rpl_sdp_ip_addr,
					&flow_description->rpl_sdp_port,
					&flow_description->rpl_sdp_transport,
					&flow_description->req_sdp_raw_stream,
					&flow_description->rpl_sdp_raw_stream,
					flow_description->direction,
					AVP_EPC_Flow_Usage_No_Information);
		}

		flow_description = flow_description->next;
		add_flow = 1;
	}
	return 0;
}


/** Helper function for adding media component AVPs for each SDP stream*/
int add_media_components(AAAMessage *aar, struct sip_msg *req,
		struct sip_msg *rpl, enum dialog_direction direction, AAASession *auth)
{
	int sdp_session_num;
	int sdp_stream_num;
	sdp_session_cell_t *req_sdp_session, *rpl_sdp_session;
	sdp_stream_cell_t *req_sdp_stream, *rpl_sdp_stream;
	int add_flow = 1;
	str ttag = {0, 0};
	str ftag = {0, 0};
	int request_originated_from_callee = 0;
	str ipA, ipB, portA, portB;

	rx_authsessiondata_t *p_session_data = 0;
	p_session_data = (rx_authsessiondata_t *)auth->u.auth.generic_data;

	if(!req || !rpl) {
		goto error;
	}

	if(parse_sdp(req) < 0) {
		LM_ERR("Unable to parse req SDP\n");
		goto error;
	}

	if(parse_sdp(rpl) < 0) {
		LM_ERR("Unable to parse res SDP\n");
		goto error;
	}

	sdp_session_num = 0;

	//Loop through req sessions and streams and get corresponding rpl sessions and streams and populate avps
	for(;;) {
		//we only cater for one session at the moment: TDOD: extend
		if(sdp_session_num > 0) {
			break;
		}

		req_sdp_session = get_sdp_session(req, sdp_session_num);
		rpl_sdp_session = get_sdp_session(rpl, sdp_session_num);
		if(!req_sdp_session || !rpl_sdp_session) {
			if(!req_sdp_session)
				LM_ERR("Missing SDP session information from req\n");

			if(!rpl_sdp_session)
				LM_ERR("Missing SDP session information from rpl\n");

			goto error;
		}

		sdp_stream_num = 0;
		for(;;) {
			req_sdp_stream =
					get_sdp_stream(req, sdp_session_num, sdp_stream_num);
			rpl_sdp_stream =
					get_sdp_stream(rpl, sdp_session_num, sdp_stream_num);
			if(!req_sdp_stream || !rpl_sdp_stream) {
				//LM_ERR("Missing SDP stream information\n");
				break;
			}
			//is this a stream to add to AAR.
			if(req_sdp_stream->is_rtp) {

				//check if the src or dst port is 0 and if so then don't add to rx
				int intportA = atoi(req_sdp_stream->port.s);
				int intportB = atoi(rpl_sdp_stream->port.s);
				if(intportA != 0 && intportB != 0) {
					if(!authorize_video_flow) {
						if(strncmp(req_sdp_stream->media.s, "video", 5) == 0) {
							add_flow = 0;
						}
					}

					if(add_flow) {


						if(cscf_get_to_tag(rpl, &ttag)
								&& cscf_get_from_tag(rpl, &ftag)) {
							LM_DBG("Original ftag [%.*s] ttag [%.*s].  Current "
								   "ftag [%.*s] ttag [%.*s]\n",
									p_session_data->ftag.len,
									p_session_data->ftag.s,
									p_session_data->ttag.len,
									p_session_data->ttag.s, ftag.len, ftag.s,
									ttag.len, ttag.s);
							if(!(strncmp(p_session_data->ttag.s, ttag.s,
										 p_session_data->ttag.len)
											   == 0
									   && strncmp(p_session_data->ftag.s,
												  ftag.s,
												  p_session_data->ftag.len)
												  == 0)) {
								LM_DBG("ftag and ttag of this response do not "
									   "match initial response so this request "
									   "came from callee\n");
								request_originated_from_callee = 1;
							}
						} else {
							LM_ERR("Couldn't retrieve ftag so assume this "
								   "request originated from caller\n");
						}

						if(request_originated_from_callee) {
							LM_DBG("Request originated from callee so IPs are "
								   "reversed\n");
							ipA = rpl_sdp_session->ip_addr;
							portA = rpl_sdp_stream->port;

							ipB = req_sdp_session->ip_addr;
							portB = req_sdp_stream->port;
						} else {
							ipA = req_sdp_session->ip_addr;
							portA = req_sdp_stream->port;

							ipB = rpl_sdp_session->ip_addr;
							portB = rpl_sdp_stream->port;
						}


						if(ipA.len <= 0) {
							LM_DBG("Request SDP connection IP could not be "
								   "retrieved, so we use SDP 1st stream IP\n");
							if(request_originated_from_callee) {
								LM_DBG("Request originated from callee so IPs "
									   "are reversed\n");
								ipA = rpl_sdp_stream->ip_addr;
								portA = rpl_sdp_stream->port;
							} else {
								ipA = req_sdp_stream->ip_addr;
								portA = req_sdp_stream->port;
							}


							if(ipA.len <= 0) {
								LM_ERR("Requested SDP IP information could not "
									   "be retrieved\n");
								goto error;
							}
						}

						if(ipB.len <= 0) {
							LM_DBG("Reply SDP connection IP could not be "
								   "retrieved, so we use SDP 1st stream IP\n");
							if(request_originated_from_callee) {
								LM_DBG("Request originated from callee so IPs "
									   "are reversed\n");
								ipB = req_sdp_stream->ip_addr;
								portB = req_sdp_stream->port;
							} else {
								ipB = rpl_sdp_stream->ip_addr;
								portB = rpl_sdp_stream->port;
							}


							if(ipB.len <= 0) {
								LM_ERR("Request SDP IP information could not "
									   "be retrieved\n");
								goto error;
							}
						}

						//add this to auth session data
						add_flow_description((rx_authsessiondata_t *)
													 auth->u.auth.generic_data,
								sdp_stream_num + 1, &req_sdp_stream->media,
								&ipA, &portA, &ipB, &portB,
								&rpl_sdp_stream->transport,
								&req_sdp_stream->raw_stream,
								&rpl_sdp_stream->raw_stream, direction,
								0 /*This is a new mcd, we are not setting it as active*/);

						rx_add_media_component_description_avp(aar,
								sdp_stream_num + 1, &req_sdp_stream->media,
								&ipA, &portA, &ipB, &portB,
								&rpl_sdp_stream->transport,
								&req_sdp_stream->raw_stream,
								&rpl_sdp_stream->raw_stream, direction,
								AVP_EPC_Flow_Usage_No_Information);
					}
					add_flow = 1;
				}
			}
			sdp_stream_num++;
		}
		sdp_session_num++;
	}

	free_sdp((sdp_info_t **)(void *)&req->body);
	free_sdp((sdp_info_t **)(void *)&rpl->body);

	return 1;

error:

	return 0;
}

/**
 * Sends the Authorization Authentication Request - specifically this is an asynchronous AAR sent if another update adding video has failed so we need to remove video
 */

int rx_send_aar_update_no_video(AAASession *auth)
{

	AAAMessage *aar = 0;

	str identifier;
	int identifier_type;


	AAA_AVP *avp = 0;
	char x[4];
	int ret = 0;

	str recv_ip;
	uint16_t ip_version;

	//we get ip and identifier for the auth session data
	rx_authsessiondata_t *p_session_data = 0;
	p_session_data = (rx_authsessiondata_t *)auth->u.auth.generic_data;
	identifier = p_session_data->identifier;
	identifier_type = p_session_data->identifier_type;
	recv_ip = p_session_data->ip;
	ip_version = p_session_data->ip_version;

	aar = cdpb.AAACreateRequest(IMS_Rx, IMS_AAR, Flag_Proxyable, auth);

	LM_DBG("Sending AAR update to remove a video bearer\n");
	show_callsessiondata(p_session_data);

	if(!aar)
		goto error;

	/*Adding AVPs*/

	LM_DBG("Adding auth app id AVP...\n");
	/* Add Auth-Application-Id AVP */
	if(!rx_add_auth_application_id_avp(aar, IMS_Rx))
		goto error;
	if(!rx_add_vendor_specific_application_id_group(
			   aar, IMS_vendor_id_3GPP, IMS_Rx))
		goto error;

	LM_DBG("Adding dest realm if not there already...\n");
	/* Add Destination-Realm AVP, if not already there */
	avp = cdpb.AAAFindMatchingAVP(aar, aar->avpList.head, AVP_Destination_Realm,
			0, AAA_FORWARD_SEARCH);
	if(!avp) {
		str realm = rx_dest_realm;
		if(realm.len && !rx_add_destination_realm_avp(aar, realm))
			goto error;
	}

	LM_DBG("Adding AF App identifier...\n");
	/* Add AF-Application-Identifier AVP */
	str af_id = {0, 0};
	af_id = IMS_Serv_AVP_val;
	if(!rx_add_avp(aar, af_id.s, af_id.len, AVP_IMS_AF_Application_Identifier,
			   AAA_AVP_FLAG_MANDATORY, IMS_vendor_id_3GPP, AVP_DUPLICATE_DATA,
			   __FUNCTION__))
		goto error;

	LM_DBG("Adding service info status...\n");
	/* Add Service-Info-Status AVP, if prelimiary
     * by default(when absent): final status is considered*/

	set_4bytes(x, AVP_EPC_Service_Info_Status_Preliminary_Service_Information);
	if(!rx_add_avp(aar, x, 4, AVP_IMS_Service_Info_Status,
			   AAA_AVP_FLAG_MANDATORY, IMS_vendor_id_3GPP, AVP_DUPLICATE_DATA,
			   __FUNCTION__))
		goto error;

	/* Add Auth lifetime AVP */ LM_DBG("auth_lifetime %u\n",
			rx_auth_expiry); //TODO check why this is 0 all the time
	if(rx_auth_expiry) {
		set_4bytes(x, rx_auth_expiry);
		if(!rx_add_avp(aar, x, 4, AVP_Authorization_Lifetime,
				   AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__))
			goto error;
	}

	LM_DBG("Adding subscription id...\n");

	rx_add_subscription_id_avp(aar, identifier, identifier_type);


	LM_DBG("Adding reservation priority...\n");
	/* Add Reservation Priority AVP*/
	set_4bytes(x, 0);
	if(!rx_add_avp(aar, x, 4, AVP_ETSI_Reservation_Priority,
			   AAA_AVP_FLAG_VENDOR_SPECIFIC, IMS_vendor_id_ETSI,
			   AVP_DUPLICATE_DATA, __FUNCTION__))
		goto error;

	LM_DBG("Adding media component...\n");
	//Note we add this AVP first as it gets the IP address which we need to create the auth session
	//Could and maybe should have a separate method that retrieves the IP from SDP - TODO

	/*---------- 2. Create and add Media-Component-Description AVP ----------*/

	/*
     *  See 3GPP TS29214
     *
     *  <Media-Component-Description> = {Media-Component-Number}
     * 								 	[Media-Sub-Component]
     * 								 	[AF-Application-Identifier]
     * 								 	[Media-Type]
     * 								 	[Max-Requested-Bandwidth-UL]
     * 									[Max-Requested-Bandwidth-DL]
     * 									[Flow-Status]
     * 									[Reservation-Priority] (Not used yet)
     * 								 	[RS-Bandwidth]
     * 									[RR-Bandwidth]
     * 									*[Codec-Data]
     */

	add_media_components_using_current_flow_description(aar, p_session_data);

	LM_DBG("Adding framed ip address [%.*s]\n", recv_ip.len, recv_ip.s);
	/* Add Framed IP address AVP*/
	if(!rx_add_framed_ip_avp(&aar->avpList, recv_ip, ip_version)) {
		LM_ERR("Unable to add framed IP AVP\n");
		goto error;
	}
	LM_DBG("Unlocking AAA session...\n");

	if(auth)
		cdpb.AAASessionsUnlock(auth->hash);

	LM_DBG("sending AAR to PCRF\n");
	if(rx_forced_peer.len)
		ret = cdpb.AAASendMessageToPeer(aar, &rx_forced_peer, NULL, NULL);
	else
		ret = cdpb.AAASendMessage(aar, NULL, NULL);

	return ret;

error:
	LM_ERR("unexpected error\n");
	if(aar)
		cdpb.AAAFreeMessage(&aar);
	if(auth) {
		cdpb.AAASessionsUnlock(auth->hash);
		cdpb.AAADropAuthSession(auth);
		auth = 0;
	}
	return ret;
}


/**
 * Sends the Authorization Authentication Request.
 * @param req - SIP request
 * @param res - SIP response
 * @param direction - 0/o/orig for originating side, 1/t/term for terminating side
 * @param rx_auth_data - the returned rx auth data
 * @returns AAA message or NULL on error
 */

int rx_send_aar(struct sip_msg *req, struct sip_msg *res, AAASession *auth,
		char *direction, saved_transaction_t *saved_t_data)
{

	AAAMessage *aar = 0;

	str identifier;
	int identifier_type;


	AAA_AVP *avp = 0;
	char x[4];
	int ret = 0;

	str ip;
	uint16_t ip_version;

	//we get ip and identifier for the auth session data
	rx_authsessiondata_t *p_session_data = 0;
	p_session_data = (rx_authsessiondata_t *)auth->u.auth.generic_data;
	identifier = p_session_data->identifier;
	identifier_type = p_session_data->identifier_type;
	ip = p_session_data->ip;
	ip_version = p_session_data->ip_version;

	/* find direction for AAR (orig/term) */
	//need this to add the media component details
	enum dialog_direction dlg_direction = get_dialog_direction(direction);
	if(dlg_direction == DLG_MOBILE_UNKNOWN) {
		LM_DBG("Asked to send AAR for unknown direction.....Aborting...\n");
		goto error;
	}

	aar = cdpb.AAACreateRequest(IMS_Rx, IMS_AAR, Flag_Proxyable, auth);

	LM_DBG("Created aar request...\n");

	if(!aar)
		goto error;

	/*Adding AVPs*/

	LM_DBG("Adding auth app id AVP...\n");
	/* Add Auth-Application-Id AVP */
	if(!rx_add_auth_application_id_avp(aar, IMS_Rx))
		goto error;
	if(!rx_add_vendor_specific_application_id_group(
			   aar, IMS_vendor_id_3GPP, IMS_Rx))
		goto error;

	LM_DBG("Adding dest realm if not there already...\n");
	/* Add Destination-Realm AVP, if not already there */
	avp = cdpb.AAAFindMatchingAVP(aar, aar->avpList.head, AVP_Destination_Realm,
			0, AAA_FORWARD_SEARCH);
	if(!avp) {
		str realm = rx_dest_realm;
		if(realm.len && !rx_add_destination_realm_avp(aar, realm))
			goto error;
	}

	LM_DBG("Adding AF App identifier...\n");
	/* Add AF-Application-Identifier AVP */
	str af_id = {0, 0};
	af_id = IMS_Serv_AVP_val;
	if(!rx_add_avp(aar, af_id.s, af_id.len, AVP_IMS_AF_Application_Identifier,
			   AAA_AVP_FLAG_MANDATORY, IMS_vendor_id_3GPP, AVP_DUPLICATE_DATA,
			   __FUNCTION__))
		goto error;

	LM_DBG("Adding service info status...\n");
	/* Add Service-Info-Status AVP, if prelimiary
     * by default(when absent): final status is considered*/
	if(!res) {
		set_4bytes(
				x, AVP_EPC_Service_Info_Status_Preliminary_Service_Information);
		if(!rx_add_avp(aar, x, 4, AVP_IMS_Service_Info_Status,
				   AAA_AVP_FLAG_MANDATORY, IMS_vendor_id_3GPP,
				   AVP_DUPLICATE_DATA, __FUNCTION__))
			goto error;
	}

	/* Add Auth lifetime AVP */ LM_DBG("auth_lifetime %u\n",
			rx_auth_expiry); //TODO check why this is 0 all the time
	if(rx_auth_expiry) {
		set_4bytes(x, rx_auth_expiry);
		if(!rx_add_avp(aar, x, 4, AVP_Authorization_Lifetime,
				   AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__))
			goto error;
	}

	LM_DBG("Adding subscription id...\n");

	rx_add_subscription_id_avp(aar, identifier, identifier_type);


	LM_DBG("Adding reservation priority...\n");
	/* Add Reservation Priority AVP*/
	set_4bytes(x, 0);
	if(!rx_add_avp(aar, x, 4, AVP_ETSI_Reservation_Priority,
			   AAA_AVP_FLAG_VENDOR_SPECIFIC, IMS_vendor_id_ETSI,
			   AVP_DUPLICATE_DATA, __FUNCTION__))
		goto error;

	LM_DBG("Adding media component...\n");
	//Note we add this AVP first as it gets the IP address which we need to create the auth session
	//Could and maybe should have a separate method that retrieves the IP from SDP - TODO

	/*---------- 2. Create and add Media-Component-Description AVP ----------*/

	/*
     *  See 3GPP TS29214
     *
     *  <Media-Component-Description> = {Media-Component-Number}
     * 								 	[Media-Sub-Component]
     * 								 	[AF-Application-Identifier]
     * 								 	[Media-Type]
     * 								 	[Max-Requested-Bandwidth-UL]
     * 									[Max-Requested-Bandwidth-DL]
     * 									[Flow-Status]
     * 									[Reservation-Priority] (Not used yet)
     * 								 	[RS-Bandwidth]
     * 									[RR-Bandwidth]
     * 									*[Codec-Data]
     */

	add_media_components(aar, req, res, dlg_direction, auth);

	LM_DBG("Adding framed ip address [%.*s]\n", ip.len, ip.s);
	/* Add Framed IP address AVP*/
	if(!rx_add_framed_ip_avp(&aar->avpList, ip, ip_version)) {
		LM_ERR("Unable to add framed IP AVP\n");
		goto error;
	}

	/* Add specific action AVP's */
	rx_add_specific_action_avp(aar, 1); // CHARGING_CORRELATION_EXCHANGE
	rx_add_specific_action_avp(aar, 2); // INDICATION_OF_LOSS_OF_BEARER
	rx_add_specific_action_avp(aar, 3); // INDICATION_RECOVERY_OF_BEARER
	rx_add_specific_action_avp(aar, 4); // INDICATION_RELEASE_OF_BEARER
	rx_add_specific_action_avp(
			aar, 5); // INDICATION_ESTABLISHMENT_OF_BEARER (now void)
	rx_add_specific_action_avp(aar, 6);	 // IP-CAN_CHANGE
	rx_add_specific_action_avp(aar, 12); // ACCESS_NETWORK_INFO_REPORT

	show_callsessiondata(p_session_data);

	LM_DBG("Unlocking AAA session...\n");

	if(auth)
		cdpb.AAASessionsUnlock(auth->hash);

	LM_DBG("sending AAR to PCRF\n");
	if(rx_forced_peer.len)
		ret = cdpb.AAASendMessageToPeer(aar, &rx_forced_peer,
				(void *)async_aar_callback, (void *)saved_t_data);
	else
		ret = cdpb.AAASendMessage(
				aar, (void *)async_aar_callback, (void *)saved_t_data);

	return ret;

error:
	LM_ERR("unexpected error\n");
	if(aar)
		cdpb.AAAFreeMessage(&aar);
	if(auth) {
		cdpb.AAASessionsUnlock(auth->hash);
		cdpb.AAADropAuthSession(auth);
		auth = 0;
	}
	return ret;
}

/**
 * Sends the Authorization Authentication Request for Register messages
 * @param req - SIP Register msg
 * @param rx_auth_data - the returned rx auth data
 * @param ip - ip address extracted from contact to register
 * @param ip_version - AF_INET or AF_INET6
 * @returns int >0 if sent AAR successfully, otherwise 0
 */

int rx_send_aar_register(struct sip_msg *msg, AAASession *auth,
		saved_transaction_local_t *saved_t_data)
{
	AAAMessage *aar = 0;
	int ret = 0;
	AAA_AVP *avp = 0;
	char x[4];
	str identifier;

	str ip;
	uint16_t ip_version;
	str via_host;

	//we get ip and identifier for the auth session data
	rx_authsessiondata_t *p_session_data = 0;
	p_session_data = (rx_authsessiondata_t *)auth->u.auth.generic_data;
	identifier = p_session_data->identifier;
	ip = p_session_data->ip;
	ip_version = p_session_data->ip_version;

	LM_DBG("Send AAR register\n");

	aar = cdpb.AAACreateRequest(IMS_Rx, IMS_AAR, Flag_Proxyable, auth);

	if(!aar)
		goto error;

	/*Add AVPs*/

	/* Add Auth-Application-Id AVP */
	if(!rx_add_auth_application_id_avp(aar, IMS_Rx))
		goto error;
	if(!rx_add_vendor_specific_application_id_group(
			   aar, IMS_vendor_id_3GPP, IMS_Rx))
		goto error;

	/* Add Destination-Realm AVP, if not already there */
	avp = cdpb.AAAFindMatchingAVP(aar, aar->avpList.head, AVP_Destination_Realm,
			0, AAA_FORWARD_SEARCH);
	if(!avp) {
		str realm = rx_dest_realm;
		if(realm.len && !rx_add_destination_realm_avp(aar, realm))
			goto error;
	}

	/* Add Subscription ID AVP*/

	identifier = cscf_get_public_identity(msg);

	int identifier_type =
			AVP_Subscription_Id_Type_SIP_URI; //we only do IMPU now
	rx_add_subscription_id_avp(aar, identifier, identifier_type);

	/* Create flow description for AF-Signaling */
	//add this to auth session data
	str raw_stream;
	raw_stream.s = 0;
	raw_stream.len = 0;

	char c_port_from[10];
	str port_from;
	port_from.len = snprintf(c_port_from, 10, "%u", saved_t_data->via_port);
	port_from.s = c_port_from;

	char c_port_to[10];
	str port_to;
	port_to.len = snprintf(c_port_to, 10, "%u", saved_t_data->recv_port);
	port_to.s = c_port_to;

	via_host.len = saved_t_data->via_host.len;
	via_host.s = saved_t_data->via_host.s;

	if(ip_version == AF_INET6 && via_host.len && via_host.s[0] == '[') {
		/* skip over [ ] */
		if(via_host.s[via_host.len - 1] != ']') {
			LM_ERR("Invalid IPv6 format %.*s\n", via_host.len, via_host.s);
			goto error;
		}

		via_host.s++;
		via_host.len -= 2;
	}

	//rx_add_media_component_description_avp_register(aar);
	/* Add media component description avp for register*/
	rx_add_media_component_description_avp(aar, 1, &component_media_type,
			&via_host, &port_from,
			ip_version == AF_INET ? &af_signaling_ip : &af_signaling_ip6,
			&port_to, &flow_protocol, &raw_stream, &raw_stream,
			_imsqos_params.dlg_direction, AVP_EPC_Flow_Usage_AF_Signaling);

	/* Add specific action AVP's */
	rx_add_specific_action_avp(aar, 1); // CHARGING_CORRELATION_EXCHANGE
	rx_add_specific_action_avp(aar, 2); // INDICATION_OF_LOSS_OF_BEARER
	rx_add_specific_action_avp(aar, 3); // INDICATION_RECOVERY_OF_BEARER
	rx_add_specific_action_avp(aar, 4); // INDICATION_RELEASE_OF_BEARER
	rx_add_specific_action_avp(
			aar, 5); // INDICATION_ESTABLISHMENT_OF_BEARER (now void)
	rx_add_specific_action_avp(aar, 6);	 // IP-CAN_CHANGE
	rx_add_specific_action_avp(aar, 12); // ACCESS_NETWORK_INFO_REPORT

	/* Add Framed IP address AVP*/
	if(!rx_add_framed_ip_avp(&aar->avpList, ip, ip_version)) {
		LM_ERR("Unable to add framed IP AVP\n");
		goto error;
	}

	/* Add Auth lifetime AVP */ LM_DBG("auth_lifetime %u\n",
			rx_auth_expiry); //TODO check why this is 0 all the time
	if(rx_auth_expiry) {
		set_4bytes(x, rx_auth_expiry);
		if(!rx_add_avp(aar, x, 4, AVP_Authorization_Lifetime,
				   AAA_AVP_FLAG_MANDATORY, 0, AVP_DUPLICATE_DATA, __FUNCTION__))
			goto error;
	}

	if(auth)
		cdpb.AAASessionsUnlock(auth->hash);

	LM_DBG("sending AAR to PCRF\n");
	if(rx_forced_peer.len)
		ret = cdpb.AAASendMessageToPeer(aar, &rx_forced_peer,
				(void *)async_aar_reg_callback, (void *)saved_t_data);
	else
		ret = cdpb.AAASendMessage(
				aar, (void *)async_aar_reg_callback, (void *)saved_t_data);

	return ret;

error:
	LM_ERR("unexpected error\n");
	if(aar)
		cdpb.AAAFreeMessage(&aar);
	if(auth) {
		cdpb.AAASessionsUnlock(auth->hash);
		cdpb.AAADropAuthSession(auth);
		auth = 0;
	}
	return ret;
}

enum dialog_direction get_dialog_direction(char *direction)
{
	if(!direction) {
		LM_CRIT("Unknown direction NULL");
		return DLG_MOBILE_UNKNOWN;
	}
	switch(direction[0]) {
		case 'o':
		case 'O':
		case '0':
			return DLG_MOBILE_ORIGINATING;
		case 't':
		case 'T':
		case '1':
			return DLG_MOBILE_TERMINATING;
		default:
			LM_CRIT("Unknown direction %s", direction);
			return DLG_MOBILE_UNKNOWN;
	}
}

void free_saved_transaction_global_data(saved_transaction_t *data)
{
	if(!data)
		return;
	if(data->callid.s && data->callid.len) {
		shm_free(data->callid.s);
		data->callid.len = 0;
	}
	if(data->ftag.s && data->ftag.len) {
		shm_free(data->ftag.s);
		data->ftag.len = 0;
	}
	if(data->ttag.s && data->ttag.len) {
		shm_free(data->ttag.s);
		data->ttag.len = 0;
	}
	if(data->lock) {
		lock_dealloc(data->lock);
		lock_destroy(data->lock);
	}
	shm_free(data);
}

void free_saved_transaction_data(saved_transaction_local_t *data)
{
	if(!data)
		return;
	shm_free(data);
}
