/*
 * 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 "../../core/str.h"
#include "../../core/socket_info.h"
#include "../../core/parser/parse_allow.h"
#include "../../core/parser/parse_methods.h"
#include "../../core/parser/msg_parser.h"
#include "../../core/parser/parse_to.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/dprint.h"
#include "../../core/trim.h"
#include "../../core/ut.h"
#include "../../core/qvalue.h"
#include "../../core/dset.h"
#include "../../core/mod_fix.h"
#include "../../core/strutils.h"
#include "../../core/counters.h"

#ifdef USE_TCP
#include "../../core/tcp_server.h"
#endif

#include "../ims_usrloc_scscf/usrloc.h"
#include "common.h"
#include "sip_msg.h"
#include "rerrno.h"
#include "reply.h"
#include "ims_registrar_scscf_mod.h"
#include "regtime.h"
#include "path.h"
#include "save.h"
#include "config.h"
#include "server_assignment.h"
#include "userdata_parser.h"
#include "../../lib/ims/ims_getters.h"
#include "registrar_notify.h"

#include "cxdx_sar.h"

extern struct tm_binds tmb;
extern int
		store_data_on_dereg; /**< should we store SAR user data on de-registration  */

extern int ue_unsubscribe_on_dereg;
extern int user_data_always;

#define DO_NOT_USE_REALM_FOR_PRIVATE_IDENTITY 0x01

/* \brief
 * Return randomized expires between expires-range% and expires.
 * RFC allows only value less or equal to the one provided by UAC.
 */
static inline int randomize_expires(int expires, int range)
{
	/* if no range is given just return expires */
	if(range == 0)
		return expires;

	int range_min = expires - (float)range / 100 * expires;

	return range_min + (float)(kam_rand() % 100) / 100 * (expires - range_min);
}

/*! \brief
 * Calculate absolute expires value per contact as follows:
 * 1) If the contact has expires value, use the value. If it
 *    is not zero, add actual time to it
 * 2) If the contact has no expires parameter, use expires
 *    header field in the same way
 * 3) If the message contained no expires header field, use
 *    the default value
 */
static inline int calc_contact_expires(
		contact_t *c, int expires_hdr, int sos_reg)
{
	int r = 0;
	if(c && c->expires)
		str2int(&(c->expires->body), (unsigned int *)&r);
	else if(expires_hdr >= 0)
		r = expires_hdr;
	else {
		r = (sos_reg > 0) ? default_registrar_cfg.em_default_expires
						  : default_registrar_cfg.default_expires;
		goto end;
	}
	if(!sos_reg && r < default_registrar_cfg.min_expires) {
		r = default_registrar_cfg.min_expires;
		goto end;
	}
	if(sos_reg && r < default_registrar_cfg.em_min_expires) {
		r = default_registrar_cfg.em_min_expires;
		goto end;
	}
	if(!sos_reg && r > default_registrar_cfg.max_expires) {
		r = default_registrar_cfg.max_expires;
		goto end;
	}
	if(sos_reg && r > default_registrar_cfg.em_max_expires) {
		r = default_registrar_cfg.em_min_expires;
		goto end;
	}
end:
	r = randomize_expires(r, default_registrar_cfg.default_expires_range);
	LM_DBG("Calculated expires for contact is %d\n", r);
	return time(NULL) + r;
}

/*! \brief
 * Process request that contained a star, in that case,
 * we will remove all bindings with the given impu
 * from the usrloc and return 200 OK response
 */
static inline int star(udomain_t *_d, str *_a)
{
	impurecord_t *r;

	ul.lock_udomain(_d, _a);

	if(ul.delete_impurecord(_d, _a, 0) != 0) {
		LM_ERR("failed to remove record from usrloc\n");

		/* Delete failed, try to get corresponding
         * record structure and send back all existing
         * contacts
         */
		rerrno = R_UL_DEL_R;

		if(ul.get_impurecord(_d, _a, &r) == 0) {
			contact_for_header_t **contact_header = 0;
			build_contact(r, contact_header, 0);
			free_contact_buf(*contact_header);
			ul.unlock_udomain(_d, _a);
		}
		return -1;
	}
	ul.unlock_udomain(_d, _a);
	return 0;
}

/*! \brief
 */
static struct socket_info *get_sock_hdr(struct sip_msg *msg)
{
	struct socket_info *sock;
	struct hdr_field *hf;
	str socks;
	str hosts;
	int port;
	int proto;
	char c;

	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
		LM_ERR("failed to parse message\n");
		return 0;
	}

	for(hf = msg->headers; hf; hf = hf->next) {
		if(cmp_hdrname_str(&hf->name, &sock_hdr_name) == 0)
			break;
	}

	/* hdr found? */
	if(hf == 0)
		return 0;

	trim_len(socks.len, socks.s, hf->body);
	if(socks.len == 0)
		return 0;

	/*FIXME: This is a hack */
	c = socks.s[socks.len];
	socks.s[socks.len] = '\0';
	if(parse_phostport(socks.s, &hosts.s, &hosts.len, &port, &proto) != 0) {
		socks.s[socks.len] = c;
		LM_ERR("bad socket <%.*s> in \n", socks.len, socks.s);
		return 0;
	}
	socks.s[socks.len] = c;
	sock = grep_sock_info(&hosts, (unsigned short)port, (unsigned short)proto);
	if(sock == 0) {
		LM_ERR("non-local socket <%.*s>\n", socks.len, socks.s);
		return 0;
	}

	LM_DBG("%d:<%.*s>:%d -> p=%p\n", proto, socks.len, socks.s, port_no, sock);

	return sock;
}

/*! \brief
 * Fills the common part (for all contacts) of the info structure
 */
static inline ucontact_info_t *pack_ci(
		struct sip_msg *_m, contact_t *_c, unsigned int _e, unsigned int _f)
{
	static ucontact_info_t ci;
	static str no_ua = str_init("n/a");
	static str callid;
	static str path_received = {0, 0};
	static str path;
	static str received = {0, 0};
	static int received_found;
	static unsigned int allowed, allow_parsed;
	static struct sip_msg *m = 0;
	int_str val;

	if(_m != 0) {
		memset(&ci, 0, sizeof(ucontact_info_t));

		/* Get callid of the message */
		callid = _m->callid->body;
		trim_trailing(&callid);
		if(callid.len > CALLID_MAX_SIZE) {
			rerrno = R_CALLID_LEN;
			LM_ERR("callid too long\n");
			goto error;
		}
		ci.callid = &callid;

		/* Get CSeq number of the message */
		if(str2int(&get_cseq(_m)->number, (unsigned int *)&ci.cseq) < 0) {
			rerrno = R_INV_CSEQ;
			LM_ERR("failed to convert cseq number\n");
			goto error;
		}

		/* set received socket */
		if(_m->flags & sock_flag) {
			ci.sock = get_sock_hdr(_m);
			if(ci.sock == 0)
				ci.sock = _m->rcv.bind_address;
		} else {
			ci.sock = _m->rcv.bind_address;
		}

		/* additional info from message */
		if(parse_headers(_m, HDR_USERAGENT_F, 0) != -1 && _m->user_agent
				&& _m->user_agent->body.len > 0
				&& _m->user_agent->body.len < MAX_UA_SIZE) {
			ci.user_agent = &_m->user_agent->body;
		} else {
			ci.user_agent = &no_ua;
		}

		/* extract Path headers */
		if(path_enabled) {
			if(build_path_vector(_m, &path, &path_received) < 0) {
				rerrno = R_PARSE_PATH;
				goto error;
			}
			if(path.len && path.s) {
				ci.path = &path;
				if(path_mode != PATH_MODE_OFF) {
					/* save in msg too for reply */
					if(set_path_vector(_m, &path) < 0) {
						rerrno = R_PARSE_PATH;
						goto error;
					}
				}
			}
		}

		if(_c->params) {
			ci.params = _c->params;
		}

		/* set flags */
		ci.flags = _f;
		getbflagsval(0, &ci.cflags);

		/* get received */
		if(path_received.len && path_received.s) {
			ci.cflags |= ul.nat_flag;
			ci.received = path_received;
		}

		allow_parsed = 0;	/* not parsed yet */
		received_found = 0; /* not found yet */
		m = _m;				/* remember the message */
	}

	if(_c != 0) {
		/* Calculate q value of the contact */
		if(calc_contact_q(_c->q, &ci.q) < 0) {
			rerrno = R_INV_Q;
			LM_ERR("failed to calculate q\n");
			goto error;
		}

		/* set expire time */
		ci.expires = _e;

		/* Get methods of contact */
		if(_c->methods) {
			if(parse_methods(&(_c->methods->body), &ci.methods) < 0) {
				rerrno = R_PARSE;
				LM_ERR("failed to parse contact methods\n");
				goto error;
			}
		} else {
			/* check on Allow hdr */
			if(allow_parsed == 0) {
				if(m && parse_allow(m) != -1) {
					allowed = get_allow_methods(m);
				} else {
					allowed = ALL_METHODS;
				}
				allow_parsed = 1;
			}
			ci.methods = allowed;
		}

		/* get received */
		if(ci.received.len == 0) {
			if(_c->received) {
				ci.received = _c->received->body;
			} else {
				if(received_found == 0) {
					memset(&val, 0, sizeof(int_str));
					if(rcv_avp_name.n != 0
							&& search_first_avp(
									rcv_avp_type, rcv_avp_name, &val, 0)
							&& val.s.len > 0) {
						if(val.s.len > RECEIVED_MAX_SIZE) {
							rerrno = R_CONTACT_LEN;
							LM_ERR("received too long\n");
							goto error;
						}
						received = val.s;
					} else {
						received.s = 0;
						received.len = 0;
					}
					received_found = 1;
				}
				ci.received = received;
			}
		}
	}

	return &ci;
error:
	return 0;
}

/**
 * Deallocates memory used by a subscription.
 * \note Must be called with the lock got to avoid races
 * @param s - the ims_subscription to free
 */
void free_ims_subscription_data(ims_subscription *s)
{
	int i, j, k;
	if(!s)
		return;
	/*	lock_get(s->lock); - must be called with the lock got */
	for(i = 0; i < s->service_profiles_cnt; i++) {
		for(j = 0; j < s->service_profiles[i].public_identities_cnt; j++) {
			if(s->service_profiles[i].public_identities[j].public_identity.s)
				shm_free(s->service_profiles[i]
								 .public_identities[j]
								 .public_identity.s);
			if(s->service_profiles[i].public_identities[j].wildcarded_psi.s)
				shm_free(s->service_profiles[i]
								 .public_identities[j]
								 .wildcarded_psi.s);
		}
		if(s->service_profiles[i].public_identities)
			shm_free(s->service_profiles[i].public_identities);

		for(j = 0; j < s->service_profiles[i].filter_criteria_cnt; j++) {
			if(s->service_profiles[i].filter_criteria[j].trigger_point) {
				for(k = 0; k < s->service_profiles[i]
									   .filter_criteria[j]
									   .trigger_point->spt_cnt;
						k++) {
					switch(s->service_profiles[i]
									.filter_criteria[j]
									.trigger_point->spt[k]
									.type) {
						case IFC_REQUEST_URI:
							if(s->service_profiles[i]
											.filter_criteria[j]
											.trigger_point->spt[k]
											.request_uri.s)
								shm_free(s->service_profiles[i]
												 .filter_criteria[j]
												 .trigger_point->spt[k]
												 .request_uri.s);
							break;
						case IFC_METHOD:
							if(s->service_profiles[i]
											.filter_criteria[j]
											.trigger_point->spt[k]
											.method.s)
								shm_free(s->service_profiles[i]
												 .filter_criteria[j]
												 .trigger_point->spt[k]
												 .method.s);
							break;
						case IFC_SIP_HEADER:
							if(s->service_profiles[i]
											.filter_criteria[j]
											.trigger_point->spt[k]
											.sip_header.header.s)
								shm_free(s->service_profiles[i]
												 .filter_criteria[j]
												 .trigger_point->spt[k]
												 .sip_header.header.s);
							if(s->service_profiles[i]
											.filter_criteria[j]
											.trigger_point->spt[k]
											.sip_header.content.s)
								shm_free(s->service_profiles[i]
												 .filter_criteria[j]
												 .trigger_point->spt[k]
												 .sip_header.content.s);
							break;
						case IFC_SESSION_CASE:
							break;
						case IFC_SESSION_DESC:
							if(s->service_profiles[i]
											.filter_criteria[j]
											.trigger_point->spt[k]
											.session_desc.line.s)
								shm_free(s->service_profiles[i]
												 .filter_criteria[j]
												 .trigger_point->spt[k]
												 .session_desc.line.s);
							if(s->service_profiles[i]
											.filter_criteria[j]
											.trigger_point->spt[k]
											.session_desc.content.s)
								shm_free(s->service_profiles[i]
												 .filter_criteria[j]
												 .trigger_point->spt[k]
												 .session_desc.content.s);
							break;
					}
				}
				if(s->service_profiles[i].filter_criteria[j].trigger_point->spt)
					shm_free(s->service_profiles[i]
									 .filter_criteria[j]
									 .trigger_point->spt);
				shm_free(s->service_profiles[i]
								 .filter_criteria[j]
								 .trigger_point);
			}
			if(s->service_profiles[i]
							.filter_criteria[j]
							.application_server.server_name.s)
				shm_free(s->service_profiles[i]
								 .filter_criteria[j]
								 .application_server.server_name.s);
			if(s->service_profiles[i]
							.filter_criteria[j]
							.application_server.service_info.s)
				shm_free(s->service_profiles[i]
								 .filter_criteria[j]
								 .application_server.service_info.s);
			if(s->service_profiles[i].filter_criteria[j].profile_part_indicator)
				shm_free(s->service_profiles[i]
								 .filter_criteria[j]
								 .profile_part_indicator);
		}
		if(s->service_profiles[i].filter_criteria)
			shm_free(s->service_profiles[i].filter_criteria);

		if(s->service_profiles[i].cn_service_auth)
			shm_free(s->service_profiles[i].cn_service_auth);

		if(s->service_profiles[i].shared_ifc_set)
			shm_free(s->service_profiles[i].shared_ifc_set);
	}
	if(s->service_profiles)
		shm_free(s->service_profiles);
	if(s->private_identity.s)
		shm_free(s->private_identity.s);
	ul.unlock_subscription(s);
#ifdef EXTRA_DEBUG
	LM_DBG("SUBSCRIPTION LOCK %p destroyed\n", s->lock);
#endif
	lock_destroy(s->lock);
	lock_dealloc(s->lock);
	shm_free(s);
}

/** Check if an impu record exists.
 * 1. must be in registered state (impurecord)
 * 2. must have at least one valid contact
 */
static inline int is_impu_registered(udomain_t *_d, str *public_identity)
{
	int res, ret = 1;
	impurecord_t *impu;

	ul.lock_udomain(_d, public_identity);
	res = ul.get_impurecord(_d, public_identity, &impu);
	if(res != 0) {
		ul.unlock_udomain(_d, public_identity);
		return 0;
	} else {
		//check reg status
		if(impu->reg_state != IMPU_REGISTERED) {
			LM_DBG("IMPU <%.*s> is not currently registered\n",
					public_identity->len, public_identity->s);
			ret = 0;
		}

		//check valid contacts
		if((impu->linked_contacts.numcontacts <= 0)
				|| (impu->linked_contacts.head == 0)) {
			LM_DBG("IMPU <%.*s> has no valid contacts\n", public_identity->len,
					public_identity->s);
			ret = 0;
		}
		ul.unlock_udomain(_d, public_identity);
	}
	return ret;
}

/**
 * update the contacts for a public identity. Make sure you have the lock on the domain before calling this
 * returns 0 on success, -1 on failure
 */
static inline int update_contacts_helper(struct sip_msg *msg,
		impurecord_t *impu_rec, int assignment_type, int expires_hdr)
{
	struct hdr_field *h;
	contact_t *chi;		 //contact header information
	ucontact_info_t *ci; //ucontact info
	qvalue_t qvalue;
	int sos = 0, expires;
	struct ucontact *ucontact;
	int result, sl;

	LM_DBG("updating the contacts for IMPU <%.*s>\n",
			impu_rec->public_identity.len, impu_rec->public_identity.s);

	switch(assignment_type) {

		case AVP_IMS_SAR_USER_DEREGISTRATION:
			LM_DBG("update_contacts_helper: doing de-reg\n");
			break;

		case AVP_IMS_SAR_REGISTRATION:
		case AVP_IMS_SAR_RE_REGISTRATION:
			for(h = msg->contact; h; h = h->next) {
				if(h->type == HDR_CONTACT_T && h->parsed) {

					for(chi = ((contact_body_t *)h->parsed)->contacts; chi;
							chi = chi->next) {
						if(calc_contact_q(chi->q, &qvalue) != 0) {
							LM_ERR("error on <%.*s>\n", chi->uri.len,
									chi->uri.s);
							goto error;
						}
						sos = cscf_get_sos_uri_param(chi->uri);
						if(sos < 0) {
							LM_ERR("Error trying to determine if this is a sos "
								   "contact <%.*s>\n",
									chi->uri.len, chi->uri.s);
							goto error;
						}
						expires = calc_contact_expires(chi, expires_hdr, sos);
						//TODO: this next line will fail if the expires is in the main body and not the contact body //FIXED
						LM_DBG("Need to update contact: <%.*s>: "
							   "q_value [%d],"
							   "sos: [%d],"
							   "expires [%" TIME_T_FMT "]\n",
								chi->uri.len, chi->uri.s, qvalue, sos,
								TIME_T_CAST(expires - time(NULL)));

						LM_DBG("packing contact information\n");
						if((ci = pack_ci(msg, chi, expires, 0)) == 0) {
							LM_ERR("Failed to extract contact info\n");
							goto error;
						}

						LM_DBG("adding/updating contact based on prior "
							   "existence\n");
						//stick the contacts into usrloc
						//ul.lock_contact_slot(&chi->uri);
						result = ul.get_ucontact(&chi->uri, ci->callid,
								ci->path, ci->cseq, &ucontact);
						if(result != 0) { //get_contact returns with lock
							LM_DBG("inserting new contact\n");
							if(ul.insert_ucontact(
									   impu_rec, &chi->uri, ci, &ucontact)
									!= 0) {
								LM_ERR("Error inserting contact <%.*s>\n",
										chi->uri.len, chi->uri.s);
								//				ul.unlock_contact_slot(&chi->uri);
								goto error;
							}
						} else {
							LM_DBG("Contact already exists - updating - it's "
								   "currently in state [%d]\n",
									ucontact->state);
							sl = ucontact->sl;
							ul.lock_contact_slot_i(sl);
							if(ucontact->state != CONTACT_VALID) {
								LM_WARN("contact is not in state valid - this "
										"is a race between dereg and "
										"reg/re-reg");
								ucontact->state =
										CONTACT_VALID; //TODO this should prob move into the contact info structure - ie pass state into update
							}
							if(ul.update_ucontact(impu_rec, ucontact, ci)
									!= 0) {
								LM_ERR("Error updating contact <%.*s>\n",
										chi->uri.len, chi->uri.s);
								ul.unlock_contact_slot_i(sl);
								ul.release_ucontact(ucontact);
								//				ul.unlock_contact_slot(&chi->uri);
								goto error;
							}
							ul.unlock_contact_slot_i(sl);
							ul.release_ucontact(ucontact);
						}
						//			ul.unlock_contact_slot(&chi->uri);
					}
				}
			}
			break;
	}

	return 0;

error:
	return -1;
}

/*NB remember to lock udomain prior to calling this*/
static inline int unregister_contact(contact_t *chi, contact_state_t state)
{
	struct ucontact *ucontact;
	str callid = {0, 0};
	str path = {0, 0};


	//    if (_impu_rec) {
	//        LM_DBG("already have impurecord....\n");
	//        impu_rec = _impu_rec;
	//    } else {
	//        if (ul.get_impurecord(_d, public_identity, &impu_rec) != 0) {
	//            LM_ERR("Error, no public identity exists for <%.*s>\n", public_identity->len, public_identity->s);
	//            goto error;
	//        }
	//    }

	if(ul.get_ucontact(&chi->uri, &callid, &path, 0 /*cseq*/, &ucontact) != 0) {
		LM_DBG("Can't unregister contact that does not exist <%.*s>\n",
				chi->uri.len, chi->uri.s);
		//        ul.unlock_udomain(_d, public_identity);
		goto error;
	}

	get_act_time();
	if(ucontact->state == CONTACT_DELETED) {
		LM_DBG("Contact is not valid (expired/deleted).... ignoring\n");
		ul.release_ucontact(ucontact);
		return 0;
	}

	//Richard added this  - fix to remove subscribes that have presentity and watcher uri same as a contact aor that is being removed
	//When UEs explicitly dereg - they don't unsubscribe, so we remove subscriptions for them
	//only do this if ue_unsubscribe_on_dereg is set to 0
	//    if (!ue_unsubscribe_on_dereg) {
	//        s = impu_rec->shead;
	//        LM_DBG("Checking if there is a subscription to this IMPU that has same watcher contact as this contact");
	//        while (s) {
	//
	//            LM_DBG("Subscription for this impurecord: watcher uri [%.*s] presentity uri [%.*s] watcher contact [%.*s] ", s->watcher_uri.len, s->watcher_uri.s,
	//                    s->presentity_uri.len, s->presentity_uri.s, s->watcher_contact.len, s->watcher_contact.s);
	//            LM_DBG("Contact to be removed [%.*s] ", ucontact->c.len, ucontact->c.s);
	//            if (contact_port_ip_match(&s->watcher_contact, &ucontact->c)) {
	//                //if ((s->watcher_contact.len == ucontact->c.len) && (strncasecmp(s->watcher_contact.s, ucontact->c.s, ucontact->c.len) == 0)) {
	//                LM_DBG("This contact has a subscription to its own status - so going to delete the subscription");
	//                ul.external_delete_subscriber(s, _d, 0 /*domain is locked*/);
	//            }
	//            s = s->next;
	//        }
	//    }

	//    if (ul.delete_ucontact(impu_rec, ucontact) != 0) {
	ul.lock_contact_slot_i(ucontact->sl);
	ucontact->state = state;
	//    notify_subscribers(impu_rec);
	//    ucontact->state = CONTACT_DELETED;
	//    if (ul.unlink_contact_from_impu(impu_rec, ucontact, 1, 1/*explicit dereg of contact*/) != 0) {
	//        LM_ERR("Failed to delete ucontact <%.*s>\n", chi->uri.len, chi->uri.s);
	//    }
	ul.unlock_contact_slot_i(ucontact->sl);
	ul.release_ucontact(ucontact);
	//    LM_DBG("Contact unlinked successfully <%.*s>\n", chi->uri.len, chi->uri.s);
	//    ul.unlock_udomain(_d, public_identity);
	return 0;

error:
	return -1;
}

/**
 * Get the number of valid contacts for an impu. Ie contacts not expired and not in deleted or delete_pending state
 * @param impu
 * @return
 */
int get_number_of_valid_contacts(impurecord_t *impu)
{
	int ret = 0;
	impu_contact_t *impucontact;
	get_act_time();

	impucontact = impu->linked_contacts.head;
	while(impucontact) {
		if(impucontact->contact) {
			if VALID_CONTACT(impucontact->contact, act_time)
				ret++;
		} else {
			//if we hit a null ref then we are at the end of the list.
			return ret;
		}
		impucontact = impucontact->next;
	}

	return ret;
}

int store_explicit_dereg_contact(struct sip_msg *msg,
		str **explicit_dereg_contact, int *num_explicit_dereg_contact)
{
	int bytes_needed_explicit_dereg_contact = 0;
	int len_num_explicit_dereg_contact = 0;
	struct hdr_field *h;
	contact_t *chi; //contact header information

	LM_DBG("This is an explicit deregistration so we need to store the contact "
		   "URI to send out NOTIFY\n");

	//get lengths
	for(h = msg->contact; h; h = h->next) {
		if(h->type == HDR_CONTACT_T && h->parsed) {
			for(chi = ((contact_body_t *)h->parsed)->contacts; chi;
					chi = chi->next) {
				LM_DBG("URI [%.*s] len [%d]\n", chi->uri.len, chi->uri.s,
						chi->uri.len);
				(*num_explicit_dereg_contact)++;
				bytes_needed_explicit_dereg_contact += chi->uri.len;
				LM_DBG("URI [%.*s] and current bytes needed [%d]\n",
						chi->uri.len, chi->uri.s,
						bytes_needed_explicit_dereg_contact);
			}
		}
	}

	LM_DBG("We have [%d] explicit contacts of total length [%d] to store\n",
			(*num_explicit_dereg_contact), bytes_needed_explicit_dereg_contact);

	//now load data

	len_num_explicit_dereg_contact =
			(sizeof(str) * (*num_explicit_dereg_contact))
			+ bytes_needed_explicit_dereg_contact;

	*explicit_dereg_contact = (str *)shm_malloc(len_num_explicit_dereg_contact);
	memset(*explicit_dereg_contact, 0, len_num_explicit_dereg_contact);

	char *ptr = (char *)(*explicit_dereg_contact + *num_explicit_dereg_contact);

	int count = 0;
	//populate data
	for(h = msg->contact; h; h = h->next) {
		if(h->type == HDR_CONTACT_T && h->parsed) {
			for(chi = ((contact_body_t *)h->parsed)->contacts; chi;
					chi = chi->next) {
				LM_DBG("Adding [%.*s] to list of explicit contacts that have "
					   "been de-reged\n",
						chi->uri.len, chi->uri.s);
				(*explicit_dereg_contact)[count].s = ptr;
				memcpy(ptr, chi->uri.s, chi->uri.len);
				(*explicit_dereg_contact)[count].len = chi->uri.len;
				ptr += chi->uri.len;
				count++;
			}
		}
	}

	if(ptr
			!= ((char *)*explicit_dereg_contact
					+ len_num_explicit_dereg_contact)) {
		LM_CRIT("buffer overflow\n");
		return -1;
	}

	return 1;
}

/**
 *
 * @param msg
 * @param _d
 * @param public_identity
 * @param assignment_type
 * @param s
 * @param ccf1
 * @param ccf2
 * @param ecf1
 * @param ecf2
 * @param contact_header
 * @return  1 - success(contacts left) - unregistered contacts and remaining contacts in contact buffer for reply message
 *          2 - success(no contacts left)
 *          <=0 - on failure
 */

int update_contacts(struct sip_msg *msg, udomain_t *_d, str *public_identity,
		int assignment_type, ims_subscription **s, str *ccf1, str *ccf2,
		str *ecf1, str *ecf2, contact_for_header_t **contact_header)
{
	int reg_state, i, j, k;
	ims_public_identity *pi = 0;
	impurecord_t *impu_rec, *tmp_impu_rec;
	int expires_hdr = -1; //by default registration doesn't expire
	struct hdr_field *h;
	contact_t *chi; //contact header information
	qvalue_t qvalue;
	int sos = 0;
	ims_subscription *subscription = 0;
	int first_unbarred_impu =
			1; //this is used to flag the IMPU as anchor for implicit set
	int is_primary_impu = 0;
	int ret = 1;
	str callid = {0, 0};
	str path = {0, 0};
	ucontact_t *ucontact;

	int num_explicit_dereg_contact = 0;
	str *explicit_dereg_contact = 0;

	if(msg) {
		expires_hdr = cscf_get_expires_hdr(msg,
				0); //get the expires from the main body of the sip message (global)
	}

	switch(assignment_type) {
		case AVP_IMS_SAR_REGISTRATION:
			LM_DBG("updating contacts in REGISTRATION state\n");
			reg_state = IMS_USER_REGISTERED;
			if(!s) {
				LM_ERR("no userdata supplied for AVP_IMS_SAR_REGISTRATION\n");
				goto error;
			}

			for(i = 0; i < (*s)->service_profiles_cnt; i++) {
				for(j = 0; j < (*s)->service_profiles[i].public_identities_cnt;
						j++) {
					pi = &((*s)->service_profiles[i].public_identities[j]);
					ul.lock_udomain(_d, &pi->public_identity);
					if(first_unbarred_impu && !pi->barring) {
						is_primary_impu = 1;
						first_unbarred_impu = 0;
					} else {
						is_primary_impu = 0;
					}
					if(ul.update_impurecord(_d, &pi->public_identity, 0,
							   reg_state,
							   -1 /*do not change send sar on delete */,
							   pi->barring, is_primary_impu, s, ccf1, ccf2,
							   ecf1, ecf2, &impu_rec)
							!= 0) {
						LM_ERR("Unable to update impurecord for <%.*s>\n",
								pi->public_identity.len, pi->public_identity.s);
						ul.unlock_udomain(_d, &pi->public_identity);
						goto error;
					}
					//here we can do something with impu_rec if we want but we must unlock when done
					//lets update the contacts
					if(update_contacts_helper(
							   msg, impu_rec, assignment_type, expires_hdr)
							!= 0) {
						LM_ERR("Failed trying to update contacts\n");
						ul.unlock_udomain(_d, &pi->public_identity);
						goto error;
					}
					ul.unlock_udomain(_d, &pi->public_identity);
				}
			}
			//if we were successful up to this point, then we need to copy the contacts from main impu record (asserted IMPU) into the register response
			ul.lock_udomain(_d, public_identity);
			if(ul.get_impurecord(_d, public_identity, &impu_rec) != 0) {
				LM_ERR("Error, we should have a record after registraion\n");
				ul.unlock_udomain(_d, public_identity);
				goto error;
			}
			//now build the contact buffer to be include in the reply message and unlock
			build_contact(impu_rec, contact_header, 0);
			build_p_associated_uri(*s);


			for(h = msg->contact; h; h = h->next) {
				if(h->type == HDR_CONTACT_T && h->parsed) {
					for(chi = ((contact_body_t *)h->parsed)->contacts; chi;
							chi = chi->next) {
						if(ul.get_ucontact(
								   &chi->uri, &callid, &path, 0, &ucontact)
								!= 0) {
							LM_DBG("Contact does not exist <%.*s>\n",
									chi->uri.len, chi->uri.s);
							goto error;
						}
						event_reg(0, impu_rec, ucontact,
								IMS_REGISTRAR_CONTACT_REGISTERED, 0, 0,
								&chi->uri, 0, 0);
						ul.release_ucontact(ucontact);
					}
				}
			}


			ul.unlock_udomain(_d, public_identity);
			break;
		case AVP_IMS_SAR_RE_REGISTRATION:
			/* first update all the implicit IMPU based on the existing IMPUs subscription
             * then, once done with the implicits, update the explicit with the new subscription data
             */
			LM_DBG("updating contacts in RE-REGISTRATION state\n");
			reg_state = IMS_USER_REGISTERED;
			ul.lock_udomain(_d, public_identity);
			if(ul.get_impurecord(_d, public_identity, &impu_rec) != 0) {
				LM_ERR("No IMPU record fond for re-registration...aborting\n");
				ul.unlock_udomain(_d, public_identity);
				goto error;
			}

			if(update_contacts_helper(
					   msg, impu_rec, assignment_type, expires_hdr)
					!= 0) { //update the contacts for the explicit IMPU
				LM_ERR("Failed trying to update contacts for "
					   "re-registration\n");
				ul.unlock_udomain(_d, public_identity);
				goto error;
			}
			//build the contact buffer for the exact registered contact for reply on explicit IMPU
			build_contact(impu_rec, contact_header, msg);
			build_p_associated_uri(impu_rec->s);

			subscription = impu_rec->s;
			if(!subscription) {
				LM_ERR("No subscriber info associated with <%.*s>, not doing "
					   "any implicit re-registrations\n",
						impu_rec->public_identity.len,
						impu_rec->public_identity.s);
				//update the new subscription infor for the explicit IMPU
				if(ul.update_impurecord(_d, public_identity, 0, reg_state,
						   -1 /*do not change send sar on delete */,
						   0 /*this is explicit so barring must be 0*/, 0, s,
						   ccf1, ccf2, ecf1, ecf2, &impu_rec)
						!= 0) {
					LM_ERR("Unable to update explicit impurecord for <%.*s>\n",
							public_identity->len, public_identity->s);
				}
				build_contact(impu_rec, contact_header, msg);
				ul.unlock_udomain(_d, public_identity);
				break;
			}

			ul.lock_subscription(subscription);
			subscription->ref_count++;
			LM_DBG("ref count after add is now %d\n", subscription->ref_count);
			ul.unlock_subscription(subscription);
			ul.unlock_udomain(_d, public_identity);

			//now update the implicit set
			for(i = 0; i < subscription->service_profiles_cnt; i++) {
				for(j = 0; j < subscription->service_profiles[i]
									   .public_identities_cnt;
						j++) {
					pi = &(subscription->service_profiles[i]
									.public_identities[j]);

					if(memcmp(public_identity->s, pi->public_identity.s,
							   public_identity->len)
							== 0) { //we don't need to update the explicit IMPU
						LM_DBG("Ignoring explicit identity <%.*s>, updating "
							   "later.....\n",
								public_identity->len, public_identity->s);
						continue;
					}
					ul.lock_udomain(_d, &pi->public_identity);

					//update the implicit IMPU with the new data
					if(ul.update_impurecord(_d, &pi->public_identity, 0,
							   reg_state,
							   -1 /*do not change send sar on delete */,
							   pi->barring, 0, s, ccf1, ccf2, ecf1, ecf2,
							   &impu_rec)
							!= 0) {
						LM_ERR("Unable to update implicit impurecord for "
							   "<%.*s>.... continuing\n",
								pi->public_identity.len, pi->public_identity.s);
						ul.unlock_udomain(_d, &pi->public_identity);
						continue;
					}

					//update the contacts for the explicit IMPU
					if(update_contacts_helper(
							   msg, impu_rec, assignment_type, expires_hdr)
							!= 0) {
						LM_ERR("Failed trying to update contacts for "
							   "re-registration of implicit IMPU "
							   "<%.*s>.......continuing\n",
								pi->public_identity.len, pi->public_identity.s);
						ul.unlock_udomain(_d, &pi->public_identity);
						continue;
					}
					ul.unlock_udomain(_d, &pi->public_identity);
				}
			}
			ul.lock_subscription(subscription);
			subscription->ref_count--;
			LM_DBG("ref count after sub is now %d\n", subscription->ref_count);
			ul.unlock_subscription(subscription);

			ul.lock_udomain(_d, public_identity);
			//finally we update the explicit IMPU record with the new data
			if(ul.update_impurecord(_d, public_identity, 0, reg_state,
					   -1 /*do not change send sar on delete */,
					   0 /*this is explicit so barring must be 0*/, 0, s, ccf1,
					   ccf2, ecf1, ecf2, &impu_rec)
					!= 0) {
				LM_ERR("Unable to update explicit impurecord for <%.*s>\n",
						public_identity->len, public_identity->s);
			}

			for(h = msg->contact; h; h = h->next) {
				if(h->type == HDR_CONTACT_T && h->parsed) {
					for(chi = ((contact_body_t *)h->parsed)->contacts; chi;
							chi = chi->next) {
						if(ul.get_ucontact(
								   &chi->uri, &callid, &path, 0, &ucontact)
								!= 0) {
							LM_DBG("Contact does not exist <%.*s>\n",
									chi->uri.len, chi->uri.s);
							goto error;
						}
						event_reg(0, impu_rec, ucontact,
								IMS_REGISTRAR_CONTACT_REFRESHED, 0, 0,
								&chi->uri, 0, 0);
						ul.release_ucontact(ucontact);
					}
				}
			}

			ul.unlock_udomain(_d, public_identity);
			break;
		case AVP_IMS_SAR_USER_DEREGISTRATION:
			/*TODO: if its not a star lets find all the contact records and remove them*/
			//first we update the state of the contact/s
			for(h = msg->contact; h; h = h->next) {
				if(h->type == HDR_CONTACT_T && h->parsed) {
					for(chi = ((contact_body_t *)h->parsed)->contacts; chi;
							chi = chi->next) {
						if(calc_contact_q(chi->q, &qvalue) != 0) {
							LM_ERR("error on <%.*s>\n", chi->uri.len,
									chi->uri.s);
							goto error;
						}
						sos = cscf_get_sos_uri_param(chi->uri);
						if(sos < 0) {
							LM_ERR("Error trying to determine if this is a sos "
								   "contact <%.*s>\n",
									chi->uri.len, chi->uri.s);
							goto error;
						}
						calc_contact_expires(chi, expires_hdr, sos);
						if(unregister_contact(chi, CONTACT_DELETE_PENDING)
								!= 0) {
							LM_DBG("Unable to remove contact <%.*s\n",
									chi->uri.len, chi->uri.s);
						}
						//add this contact to the successful unregistered in the 200OK so the PCSCF can also see what is de-registered
						build_expired_contact(chi, contact_header);
					}
				}
			}

			if(store_explicit_dereg_contact(msg, &explicit_dereg_contact,
					   &num_explicit_dereg_contact)
					== -1) {
				LM_ERR("Error trying to store explicit dereg contacts\n");
				goto error;
			}

			for(k = 0; k < num_explicit_dereg_contact; k++) {
				LM_DBG("Stored explicit contact to dereg: [%.*s]\n",
						(explicit_dereg_contact)[k].len,
						(explicit_dereg_contact)[k].s);
			}

			//now, we get the subscription
			ul.lock_udomain(_d, public_identity);
			if(ul.get_impurecord(_d, public_identity, &impu_rec) != 0) {
				LM_DBG("Error retrieving impu record on explicit de-reg "
					   "nothing we can do from here on... aborting..\n");
				ul.unlock_udomain(_d, public_identity);
				goto error;
			}
			int num_contacts = get_number_of_valid_contacts(impu_rec);
			if(num_contacts > 0) {
				LM_DBG("contacts still available\n");
				//TODO: add all other remaining contacts to reply message (contacts still registered for this IMPU)
				ret = 1;
			} else {
				LM_DBG("no more contacts available\n");
				ret = 2;
			}
			ims_subscription *subscription = impu_rec->s;

			if(!subscription) {
				LM_WARN("subscription is null..... continuing without "
						"de-registering implicit set\n");
			} else {
				ul.lock_subscription(subscription);
				subscription
						->ref_count++; //this is so we can de-reg the implicit set just now without holding the lock on the current IMPU
				ul.unlock_subscription(subscription);
			}

			ul.unlock_udomain(_d, public_identity);

			if(subscription) {
				for(i = 0; i < subscription->service_profiles_cnt; i++) {
					for(j = 0; j < subscription->service_profiles[i]
										   .public_identities_cnt;
							j++) {
						pi = &(subscription->service_profiles[i]
										.public_identities[j]);
						//                        if (memcmp(public_identity->s, pi->public_identity.s, public_identity->len) == 0) { //we don't need to update the explicit IMPU
						//                            LM_DBG("Ignoring explicit identity <%.*s>, already de-reg/updated\n", public_identity->len, public_identity->s);
						//                            continue;
						//                        }
						ul.lock_udomain(_d, &pi->public_identity);
						if(ul.get_impurecord(
								   _d, &pi->public_identity, &tmp_impu_rec)
								!= 0) {
							LM_ERR("Can't find IMPU for implicit "
								   "de-registration update....continuing\n");
							ul.unlock_udomain(_d, &pi->public_identity);
							continue;
						}
						LM_DBG("Implicit deregistration of IMPU <%.*s>\n",
								pi->public_identity.len, pi->public_identity.s);
						//TODO_LATEST: need to add back the following functionality
						//                        if (!ue_unsubscribe_on_dereg) {
						//                            subscriber = tmp_impu_rec->shead;
						//                            LM_DBG("Checking if there is a subscription to this IMPU that has same watcher contact as this contact");
						//                            while (s) {
						//
						//                                LM_DBG("Subscription for this impurecord: watcher uri [%.*s] presentity uri [%.*s] watcher contact [%.*s] ", subscriber->watcher_uri.len, subscriber->watcher_uri.s,
						//                                        subscriber->presentity_uri.len, subscriber->presentity_uri.s, subscriber->watcher_contact.len, subscriber->watcher_contact.s);
						//                                LM_DBG("Contact to be removed [%.*s] ", ucontact->c.len, ucontact->c.s);
						//                                if (contact_port_ip_match(&subscriber->watcher_contact, &ucontact->c)) {
						//                                    //if ((s->watcher_contact.len == ucontact->c.len) && (strncasecmp(s->watcher_contact.s, ucontact->c.s, ucontact->c.len) == 0)) {
						//                                    LM_DBG("This contact has a subscription to its own status - so going to delete the subscription");
						//                                    ul.external_delete_subscriber(subscriber, _d, 0 /*domain is locked*/);
						//                                }
						//                                s = s->next;
						//                            }
						//                        }

						for(h = msg->contact; h; h = h->next) {
							if(h->type == HDR_CONTACT_T && h->parsed) {
								for(chi = ((contact_body_t *)h->parsed)
												  ->contacts;
										chi; chi = chi->next) {
									if(ul.get_ucontact(&chi->uri, &callid,
											   &path, 0, &ucontact)
											!= 0) {
										LM_DBG("Contact does not exist "
											   "<%.*s>\n",
												chi->uri.len, chi->uri.s);
										goto error;
									}
									notify_subscribers(tmp_impu_rec, ucontact,
											(str *)explicit_dereg_contact,
											num_explicit_dereg_contact,
											IMS_REGISTRAR_CONTACT_UNREGISTERED);
									ul.release_ucontact(ucontact);

									if(calc_contact_q(chi->q, &qvalue) != 0) {
										LM_ERR("error on <%.*s>\n",
												chi->uri.len, chi->uri.s);
										ul.unlock_udomain(
												_d, &pi->public_identity);
										LM_ERR("no q value of implicit "
											   "de-reg....continuing\n");
										continue;
									}
									sos = cscf_get_sos_uri_param(chi->uri);
									if(sos < 0) {
										LM_ERR("Error trying to determine if "
											   "this is a sos contact <%.*s>\n",
												chi->uri.len, chi->uri.s);
										ul.unlock_udomain(_d, public_identity);
										goto error;
									}
									calc_contact_expires(chi, expires_hdr, sos);
									if(unregister_contact(chi, CONTACT_DELETED)
											!= 0) {
										LM_ERR("Unable to remove contact "
											   "<%.*s>\n",
												chi->uri.len, chi->uri.s);
									}
								}
							}
						}
						/*now lets see if we still have any contacts left to decide on return value*/
						int num_valid_contacts =
								get_number_of_valid_contacts(tmp_impu_rec);
						if(num_valid_contacts)
							LM_DBG("contacts still available after implicit "
								   "dereg for IMPU: <%.*s>\n",
									pi->public_identity.len,
									pi->public_identity.s);
						else {
							LM_DBG("no contacts left after implicit dereg for "
								   "IMPU: <%.*s>\n",
									pi->public_identity.len,
									pi->public_identity.s);
							LM_DBG("Updating impu record to not send SAR on "
								   "delete as this is explicit dereg");
							reg_state =
									IMS_USER_REGISTERED; //keep reg_state as it is
							if(ul.update_impurecord(_d,
									   0 /*&pi->public_identity*/, tmp_impu_rec,
									   reg_state,
									   0 /*do not send sar on delete */,
									   -1 /*do not change barring*/, 0, 0, 0, 0,
									   0, 0, &tmp_impu_rec)
									!= 0) {
								LM_ERR("Unable to update explicit impurecord "
									   "for <%.*s>\n",
										pi->public_identity.len,
										pi->public_identity.s);
							}
						}

						ul.unlock_udomain(_d, &pi->public_identity);
					}
				}
				ul.lock_subscription(subscription);
				subscription->ref_count--;
				ul.unlock_subscription(subscription);
			}

			//TODO: clean here too - maybe do earlier with the lock on the domain already held...
			if(ret == 2) {
				LM_DBG("no contacts left after explicit dereg for IMPU: "
					   "<%.*s>\n",
						public_identity->len, public_identity->s);
				LM_DBG("Updating impu record to not send SAR on delete as this "
					   "is explicit dereg");
				reg_state = IMS_USER_REGISTERED; //keep reg_state as it is
				ul.lock_udomain(_d, public_identity);
				if(ul.update_impurecord(_d, public_identity, 0, reg_state,
						   0 /*do not send sar on delete */,
						   -1 /*do not change barring*/, 0, 0, 0, 0, 0, 0,
						   &tmp_impu_rec)
						!= 0) {
					LM_ERR("Unable to update explicit impurecord for <%.*s>\n",
							public_identity->len, public_identity->s);
				}
				ul.unlock_udomain(_d, public_identity);
			}
			break;

		case AVP_IMS_SAR_UNREGISTERED_USER:
			LM_DBG("updating contacts for UNREGISTERED_USER state\n");
			reg_state = IMS_USER_UNREGISTERED;
			for(i = 0; i < (*s)->service_profiles_cnt; i++)
				for(j = 0; j < (*s)->service_profiles[i].public_identities_cnt;
						j++) {
					pi = &((*s)->service_profiles[i].public_identities[j]);
					ul.lock_udomain(_d, &pi->public_identity);
					if(ul.update_impurecord(_d, &pi->public_identity, 0,
							   reg_state,
							   -1 /*do not change send sar on delete */,
							   pi->barring, 0, s, ccf1, ccf2, ecf1, ecf2,
							   &impu_rec)
							!= 0) {
						LM_ERR("Unable to update impurecord for <%.*s>\n",
								pi->public_identity.len, pi->public_identity.s);
						ul.unlock_udomain(_d, &pi->public_identity);
						goto error;
					}
					ul.unlock_udomain(_d, &pi->public_identity);
				}
			//if we were successful up to this point, then we need to copy the contacts from main impu record (asserted IMPU) into the register response
			break;
		default:
			LM_ERR("unimplemented assignment_type when trying to update "
				   "contacts\n");
	}

	if(explicit_dereg_contact) {
		shm_free(explicit_dereg_contact);
	}


	return ret;

error:
	return -1;
}

int assign_server_unreg(
		struct sip_msg *_m, char *str1, str *direction, char *route)
{
	str private_identity = {0, 0}, public_identity = {0, 0};
	int assignment_type = AVP_IMS_SAR_NO_ASSIGNMENT;
	int data_available = AVP_IMS_SAR_USER_DATA_NOT_AVAILABLE;
	int require_user_data = 1;
	rerrno = R_FINE;
	tm_cell_t *t = 0;
	str route_name;

	saved_transaction_t *saved_t;
	cfg_action_t *cfg_action;

	udomain_t *_d = (udomain_t *)str1;

	if(fixup_get_svalue(_m, (gparam_t *)route, &route_name) != 0) {
		LM_ERR("no async route block for assign_server_unreg\n");
		return -1;
	}

	LM_DBG("Looking for route block [%.*s]\n", route_name.len, route_name.s);
	int ri = route_get(&main_rt, route_name.s);
	if(ri < 0) {
		LM_ERR("unable to find route block [%.*s]\n", route_name.len,
				route_name.s);
		return -1;
	}
	cfg_action = main_rt.rlist[ri];
	if(cfg_action == NULL) {
		LM_ERR("empty action lists in route block [%.*s]\n", route_name.len,
				route_name.s);
		return -1;
	}

	LM_DBG("Assigning unregistered user for direction [%.*s]\n", direction->len,
			direction->s);

	enum cscf_dialog_direction dir = cscf_get_dialog_direction(direction->s);
	switch(dir) {
		case CSCF_MOBILE_ORIGINATING:
			public_identity = cscf_get_asserted_identity(_m, 0);
			break;
		case CSCF_MOBILE_TERMINATING:
			public_identity = cscf_get_public_identity_from_requri(_m);
			break;
		default:
			LM_ERR("Bad dialog direction [%.*s]\n", direction->len,
					direction->s);
			rerrno = R_SAR_FAILED;
			goto error;
	}

	if(!public_identity.s || public_identity.len <= 0) {
		LM_ERR("No public identity\n");
		rerrno = R_SAR_FAILED;
		goto error;
	}

	assignment_type = AVP_IMS_SAR_UNREGISTERED_USER;
	data_available = AVP_IMS_SAR_USER_DATA_NOT_AVAILABLE; //TODO: check this


	//before we send lets suspend the transaction
	t = tmb.t_gett();
	if(t == NULL || t == T_UNDEFINED) {
		if(tmb.t_newtran(_m) < 0) {
			LM_ERR("cannot create the transaction for SAR async\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}
		t = tmb.t_gett();
		if(t == NULL || t == T_UNDEFINED) {
			LM_ERR("cannot lookup the transaction\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}
	}

	saved_t = shm_malloc(sizeof(saved_transaction_t));
	if(!saved_t) {
		LM_ERR("no more memory trying to save transaction state\n");
		rerrno = R_SAR_FAILED;
		goto error;
	}
	memset(saved_t, 0, sizeof(saved_transaction_t));
	saved_t->act = cfg_action;

	saved_t->expires = 1; //not a dereg as this is server_assign_unreg
	saved_t->require_user_data = require_user_data;
	saved_t->sar_assignment_type = assignment_type;
	saved_t->domain = (udomain_t *)_d;

	saved_t->contact_header = 0;

	LM_DBG("Setting default AVP return code used for async callbacks to "
		   "default as ERROR \n");
	create_return_code(CSCF_RETURN_ERROR);

	LM_DBG("Suspending SIP TM transaction\n");
	if(tmb.t_suspend(_m, &saved_t->tindex, &saved_t->tlabel) != 0) {
		LM_ERR("failed to suspend the TM processing\n");
		free_saved_transaction_data(saved_t);
		rerrno = R_SAR_FAILED;
		goto error;
	}

	if(scscf_assign_server(_m, public_identity, private_identity,
			   assignment_type, data_available, saved_t)
			!= 0) {
		LM_ERR("ERR:I_MAR: Error sending SAR or SAR time-out\n");
		tmb.t_cancel_suspend(saved_t->tindex, saved_t->tlabel);
		free_saved_transaction_data(saved_t);
		rerrno = R_SAR_FAILED;
		goto error;
	}

	if(public_identity.s && dir == CSCF_MOBILE_TERMINATING)
		shm_free(
				public_identity
						.s); // shm_malloc in cscf_get_public_identity_from_requri

	return CSCF_RETURN_BREAK;


error:
	update_stat(rejected_registrations, 1);
	if((is_route_type(REQUEST_ROUTE)) && (reg_send_reply(_m, 0) < 0))
		return CSCF_RETURN_ERROR;
	return CSCF_RETURN_BREAK;
}

/*!\brief
 * Process REGISTER request and save it's contacts
 * 1. ensure request
 * 2. get impu, impi,realm,expiry,etc
 * 3. check expiry
 * 4. do SAR (reg,re-reg,dereg and no contacts left)
 * 5. update usrloc based on SAR results
 */
//int save(struct sip_msg* msg, udomain_t* _d) {

int save(struct sip_msg *msg, char *str1, char *route, int _cflags)
{
	int expires;
	int require_user_data = 0;
	int data_available;
	contact_t *c;
	int st;
	str public_identity, private_identity, realm = {0, 0};
	int sar_assignment_type = AVP_IMS_SAR_NO_ASSIGNMENT;
	str route_name;

	udomain_t *_d = (udomain_t *)str1;

	rerrno = R_FINE;
	get_act_time();

	tm_cell_t *t = 0;

	saved_transaction_t *saved_t;
	cfg_action_t *cfg_action;

	contact_for_header_t *contact_header = 0;

	if(fixup_get_svalue(msg, (gparam_t *)route, &route_name) != 0) {
		LM_ERR("no async route block for assign_server_unreg\n");
		return -1;
	}

	LM_DBG("Looking for route block [%.*s]\n", route_name.len, route_name.s);
	int ri = route_get(&main_rt, route_name.s);
	if(ri < 0) {
		LM_ERR("unable to find route block [%.*s]\n", route_name.len,
				route_name.s);
		return -1;
	}
	cfg_action = main_rt.rlist[ri];
	if(cfg_action == NULL) {
		LM_ERR("empty action lists in route block [%.*s]\n", route_name.len,
				route_name.s);
		return -1;
	}

	//check which route block we are in - if not request then we fail out.
	if(!is_route_type(REQUEST_ROUTE)) {
		LM_ERR("Should only be called in REQUEST route\n");
		rerrno = R_SAR_FAILED;
		goto error;
	}

	/* check and see that all the required headers are available and can be parsed */
	if(parse_message_for_register(msg) < 0) {
		LM_ERR("Unable to parse register message correctly\n");
		rerrno = R_SAR_FAILED;
		goto error;
	}
	/** check we have valid contacts according to IMS spec. */
	if(check_contacts(msg, &st) > 0) {
		LM_ERR("contacts not valid for REGISTER\n");
		rerrno = R_SAR_FAILED;
		goto error;
	}

	/* get IMPU,IMPI,realm,expires */
	public_identity = cscf_get_public_identity(msg);
	if(public_identity.len <= 0 || !public_identity.s) {
		LM_ERR("failed to extract Address Of Record\n");
		rerrno = R_SAR_FAILED;
		goto error;
	}

	if(!(_cflags & DO_NOT_USE_REALM_FOR_PRIVATE_IDENTITY)) {
		realm = cscf_get_realm_from_uri(public_identity);
		if(realm.len <= 0 || !realm.s) {
			LM_ERR("can't get realm\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}
	}

	private_identity = cscf_get_private_identity(msg, realm);
	if(private_identity.len <= 0 || !private_identity.s) {
		LM_ERR("cant get private identity\n");
	}

	expires = cscf_get_max_expires(msg, 0); //check all contacts for max expires
	if(expires
			!= 0) { //if <0 then no expires was found in which case we treat as reg/re-reg with default expires.
		if(is_impu_registered(_d, &public_identity)) {
			LM_DBG("preparing for SAR assignment for RE-REGISTRATION <%.*s>\n",
					public_identity.len, public_identity.s);
			sar_assignment_type = AVP_IMS_SAR_RE_REGISTRATION;
		} else {
			LM_DBG("preparing for SAR assignment for new REGISTRATION <%.*s>\n",
					public_identity.len, public_identity.s);
			sar_assignment_type = AVP_IMS_SAR_REGISTRATION;
			require_user_data = 1;
		}
	} else { //de-reg
		if(store_data_on_dereg) {
			LM_DBG("preparing for SAR assignment for DE-REGISTRATION with "
				   "storage <%.*s>\n",
					public_identity.len, public_identity.s);
			sar_assignment_type =
					AVP_IMS_SAR_USER_DEREGISTRATION_STORE_SERVER_NAME;
		} else {
			LM_DBG("preparing for SAR assignment for DE-REGISTRATION <%.*s>\n",
					public_identity.len, public_identity.s);
			sar_assignment_type = AVP_IMS_SAR_USER_DEREGISTRATION;
		}

		c = get_first_contact(msg);
		if(!c && !st) { //no contacts found - no need to do anything
			LM_ERR("no contacts found for de-registration and no star\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}

		//if we get here there are contacts to deregister, BUT we only send a SAR if there are no contacts (valid) left.
		if(!st) { //if it is a star then we delete all contacts and send a SAR
			//unregister the requested contacts, if none left at the end then send a SAR, otherwise return successfully
			LM_DBG("need to unregister contacts\n");
			//lets update the contacts - we need to know if all were deleted or not for the public identity
			int res = update_contacts(msg, _d, &public_identity,
					sar_assignment_type, 0, 0, 0, 0, 0, &contact_header);
			if(res <= 0) {
				LM_DBG("Error processing REGISTER for de-registration\n");
				free_contact_buf(contact_header);
				rerrno = R_SAR_FAILED;
				goto error;
			} else if(res == 2) {
				//send sar
				LM_DBG("no contacts left after explicit de-registration, doing "
					   "SAR\n");
			} else { //res=1
				//still contacts left so return success
				LM_DBG("contacts still available after deregister.... not "
					   "doing SAR\n");
				//we must send the de reged contacts in this! so we only free the contact header after sending
				//free_contact_buf(contact_header);
				rerrno = R_FINE;
				goto no_sar;
			}
		}
	}

	if(!user_data_always) {
		if(require_user_data)
			data_available = AVP_IMS_SAR_USER_DATA_NOT_AVAILABLE;
		else
			data_available = AVP_IMS_SAR_USER_DATA_ALREADY_AVAILABLE;
	} else {
		data_available = AVP_IMS_SAR_USER_DATA_NOT_AVAILABLE;
	}

	//before we send lets suspend the transaction
	t = tmb.t_gett();
	if(t == NULL || t == T_UNDEFINED) {
		if(tmb.t_newtran(msg) < 0) {
			LM_ERR("cannot create the transaction for SAR async\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}
		t = tmb.t_gett();
		if(t == NULL || t == T_UNDEFINED) {
			LM_ERR("cannot lookup the transaction\n");
			rerrno = R_SAR_FAILED;
			goto error;
		}
	}

	saved_t = shm_malloc(sizeof(saved_transaction_t));
	if(!saved_t) {
		LM_ERR("no more memory trying to save transaction state\n");
		free_contact_buf(contact_header);
		rerrno = R_SAR_FAILED;
		goto error;
	}
	memset(saved_t, 0, sizeof(saved_transaction_t));
	saved_t->act = cfg_action;

	//this is not server assign unreg - this is a save
	saved_t->expires = expires;
	saved_t->require_user_data = require_user_data;
	saved_t->sar_assignment_type = sar_assignment_type;

	saved_t->domain = _d;

	saved_t->public_identity.s = (char *)shm_malloc(public_identity.len + 1);
	if(!saved_t->public_identity.s) {
		LM_ERR("no more memory trying to save transaction state : callid\n");
		shm_free(saved_t);
		free_contact_buf(contact_header);
		rerrno = R_SAR_FAILED;
		goto error;
	}
	memset(saved_t->public_identity.s, 0, public_identity.len + 1);
	memcpy(saved_t->public_identity.s, public_identity.s, public_identity.len);
	saved_t->public_identity.len = public_identity.len;

	saved_t->contact_header = contact_header;

	create_return_code(CSCF_RETURN_ERROR);

	LM_DBG("Suspending SIP TM transaction with index [%d] and label [%d]\n",
			saved_t->tindex, saved_t->tlabel);
	if(tmb.t_suspend(msg, &saved_t->tindex, &saved_t->tlabel) != 0) {
		LM_ERR("failed to suspend the TM processing\n");
		free_saved_transaction_data(saved_t);
		rerrno = R_SAR_FAILED;
		goto error;
	}

	if(scscf_assign_server(msg, public_identity, private_identity,
			   sar_assignment_type, data_available, saved_t)
			!= 0) {
		LM_ERR("ERR:I_MAR: Error sending SAR or SAR time-out\n");
		tmb.t_cancel_suspend(saved_t->tindex, saved_t->tlabel);
		free_saved_transaction_data(saved_t);
		rerrno = R_SAR_FAILED;
		goto error;
	}

	return CSCF_RETURN_BREAK;

no_sar:

	update_stat(accepted_registrations, 1);

	//we must send the de reged contacts in this! so we only free the contact header after sending
	/* Only send reply upon request, not upon reply */
	if((is_route_type(REQUEST_ROUTE))
			&& (reg_send_reply(msg, contact_header) < 0)) {
		free_contact_buf(contact_header);
		return CSCF_RETURN_ERROR;
	}
	free_contact_buf(contact_header);
	return CSCF_RETURN_BREAK;

error:
	update_stat(rejected_registrations, 1);
	if((is_route_type(REQUEST_ROUTE))
			&& (reg_send_reply(msg, contact_header) < 0))
		return CSCF_RETURN_ERROR;
	return CSCF_RETURN_BREAK;
}

int unregister(struct sip_msg *_m, char *_d, char *_uri)
{
	str aor = {0, 0};
	str uri = {0, 0};

	if(fixup_get_svalue(_m, (gparam_p)_uri, &uri) != 0 || uri.len <= 0) {
		LM_ERR("invalid uri parameter\n");
		return -1;
	}

	if(extract_aor(&uri, &aor) < 0) {
		LM_ERR("failed to extract Address Of Record\n");
		return -1;
	}

	if(star((udomain_t *)_d, &aor) < 0) {
		LM_ERR("error unregistering user [%.*s]\n", aor.len, aor.s);
		return -1;
	}
	return 1;
}
