/*
 * $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
 * Fraunhofer FOKUS 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: A lot 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.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * 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 "registrar_notify.h"

#include "ims_registrar_scscf_mod.h"
#include "../../lib/ims/ims_getters.h"
#include "regtime.h"
#include "usrloc_cb.h"


#include "../../core/parser/parse_from.h"
#include "../../core/parser/parse_content.h"
#include "../../core/parser/parse_uri.h"
#include "../../core/dprint.h" /* TIME_T_FMT */
#include <libxml/parser.h>

#include "../../lib/ims/useful_defs.h"
#include "../ims_usrloc_scscf/udomain.h"

#define STATE_ACTIVE 1
#define STATE_TERMINATED 0
#define STATE_UNKNOWN -1

#define EVENT_UNKNOWN -1
#define EVENT_REGISTERED 0
#define EVENT_UNREGISTERED 1
#define EVENT_TERMINATED 2
#define EVENT_CREATED 3
#define EVENT_REFRESHED 4
#define EVENT_EXPIRED 5

extern struct tm_binds tmb;

extern int notification_list_size_threshold;
extern int max_notification_list_size;

extern int subscription_default_expires;
extern int subscription_min_expires;
extern int subscription_max_expires;

extern str scscf_name_str;

static str event_hdr = {"Event: reg\r\n", 12};
static str maxfwds_hdr = {"Max-Forwards: 70\r\n", 18};
static str subss_hdr1 = {"Subscription-State: ", 20};
static str subss_hdr2 = {"\r\n", 2};
static str ctype_hdr1 = {"Content-Type: ", 14};
static str ctype_hdr2 = {"\r\n", 2};

extern int ue_unsubscribe_on_dereg;
extern int subscription_expires_range;

extern char *domain;

/* \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);
}

int notify_init()
{
	notification_list = shm_malloc(sizeof(reg_notification_list));
	if(!notification_list) {
		LM_ERR("No more SHM mem\n");
		return 0;
	}
	memset(notification_list, 0, sizeof(reg_notification_list));
	notification_list->lock = lock_alloc();
	if(!notification_list->lock) {
		LM_ERR("failed to create cdp event list lock\n");
		return 0;
	}
	if(lock_init(notification_list->lock) == 0) {
		lock_dealloc(notification_list->lock);
		notification_list->lock = 0;
		LM_ERR("failed to initialize cdp event list lock\n");
		return 0;
	}
	notification_list->size = 0;
	sem_new(notification_list->empty,
			0); //pre-locked - as we assume list is empty at start
	return 1;
}

/**
 * Destroys the reg notifications list.
 */
void notify_destroy()
{
	reg_notification *n, *nn;
	lock_get(notification_list->lock);
	n = notification_list->head;
	while(n) {
		nn = n->next;
		free_notification(n);
		n = nn;
	}
	lock_destroy(notification_list->lock);
	lock_dealloc(notification_list->lock);
	shm_free(notification_list);
}

int can_publish_reg(struct sip_msg *msg, char *_t, char *str2)
{

	int ret = CSCF_RETURN_FALSE;
	str presentity_uri = {0, 0};
	str event;
	str asserted_id;
	ucontact_t *c = 0;
	impurecord_t *r;
	int res;
	ims_public_identity *pi = 0;
	int i, j;
	impu_contact_t *impucontact;

	LM_DBG("Checking if allowed to publish reg event\n");

	//check that this is a request
	if(msg->first_line.type != SIP_REQUEST) {
		LM_ERR("This message is not a request\n");
		goto error;
	}

	//check that this is a subscribe request
	if(msg->first_line.u.request.method.len != 7
			|| memcmp(msg->first_line.u.request.method.s, "PUBLISH", 7) != 0) {
		LM_ERR("This message is not a PUBLISH\n");
		goto error;
	}

	//check that this is a reg event - currently we only support reg event!
	event = cscf_get_event(msg);
	if(event.len != 3 || strncasecmp(event.s, "reg", 3) != 0) {
		LM_ERR("Accepting only <Event: reg>. Found: <%.*s>\n", event.len,
				event.s);
		goto done;
	}

	asserted_id = cscf_get_asserted_identity(msg, 0);
	if(!asserted_id.len) {
		LM_ERR("P-Asserted-Identity empty.\n");
		goto error;
	}
	LM_DBG("P-Asserted-Identity <%.*s>.\n", asserted_id.len, asserted_id.s);

	//get presentity URI
	presentity_uri = cscf_get_public_identity_from_requri(msg);

	LM_DBG("Looking for IMPU in usrloc <%.*s>\n", presentity_uri.len,
			presentity_uri.s);

	ul.lock_udomain((udomain_t *)_t, &presentity_uri);
	res = ul.get_impurecord((udomain_t *)_t, &presentity_uri, &r);

	if(res != 0) {
		LM_DBG("'%.*s' Not found in usrloc\n", presentity_uri.len,
				presentity_uri.s);
		ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
		goto done;
	}

	LM_DBG("<%.*s> found in usrloc\n", presentity_uri.len, presentity_uri.s);

	//check if the asserted identity is in the same group as that presentity uri
	if(r->public_identity.len == asserted_id.len
			&& strncasecmp(r->public_identity.s, asserted_id.s, asserted_id.len)
					   == 0) {
		LM_DBG("Identity found as AOR <%.*s>\n", presentity_uri.len,
				presentity_uri.s);
		ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
		ret = CSCF_RETURN_TRUE;
		goto done;
	}

	//check if asserted identity is in service profile
	ul.lock_subscription(r->s);
	if(r->s) {
		for(i = 0; i < r->s->service_profiles_cnt; i++)
			for(j = 0; j < r->s->service_profiles[i].public_identities_cnt;
					j++) {
				pi = &(r->s->service_profiles[i].public_identities[j]);
				if(!pi->barring && pi->public_identity.len == asserted_id.len
						&& strncasecmp(pi->public_identity.s, asserted_id.s,
								   asserted_id.len)
								   == 0) {
					LM_DBG("Identity found in SP[%d][%d]\n", i, j);
					ret = CSCF_RETURN_TRUE;
					ul.unlock_subscription(r->s);
					ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
					goto done;
				}
			}
	}
	ul.unlock_subscription(r->s);
	LM_DBG("Did not find p-asserted-identity <%.*s> in SP\n", asserted_id.len,
			asserted_id.s);

	//check if asserted is present in any of the path headers
	j = 0;
	impucontact = r->linked_contacts.head;
	while(impucontact) {
		c = impucontact->contact;
		if(c->path.len) {
			LM_DBG("Path: <%.*s>.\n", c->path.len, c->path.s);
			for(i = 0; i < c->path.len - (asserted_id.len - 4); i++) {
				//we compare the asserted_id without "sip:" to the path
				if(strncasecmp(c->path.s + i, asserted_id.s + 4,
						   asserted_id.len - 4)
						== 0) {
					LM_DBG("Identity found in Path <%.*s>\n", c->path.len,
							c->path.s);
					ret = CSCF_RETURN_TRUE;
					ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
					goto done;
				}
			}
		}
		impucontact = impucontact->next;
	}
	LM_DBG("Did not find p-asserted-identity <%.*s> on Path\n", asserted_id.len,
			asserted_id.s);

	ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
	LM_DBG("Publish forbidden\n");

done:
	if(presentity_uri.s) {
		// shm_malloc in cscf_get_public_identity_from_requri
		shm_free(presentity_uri.s);
	}
	return ret;
error:
	if(presentity_uri.s) {
		// shm_malloc in cscf_get_public_identity_from_requri
		shm_free(presentity_uri.s);
	}
	ret = CSCF_RETURN_ERROR;
	return ret;
}

int can_subscribe_to_reg(struct sip_msg *msg, char *_t, char *str2)
{

	int ret = CSCF_RETURN_FALSE;
	str presentity_uri = {0, 0};
	str callid = {0, 0};
	str ftag = {0, 0};
	str ttag = {0, 0};
	str event;
	str asserted_id;
	ucontact_t *c = 0;
	impurecord_t *r;
	int res;
	impu_contact_t *impucontact;

	ims_public_identity *pi = 0;
	int i, j;

	LM_DBG("Checking if allowed to subscribe to event\n");

	//check that this is a request
	if(msg->first_line.type != SIP_REQUEST) {
		LM_ERR("This message is not a request\n");
		goto error;
	}

	//check that this is a subscribe request
	if(msg->first_line.u.request.method.len != 9
			|| memcmp(msg->first_line.u.request.method.s, "SUBSCRIBE", 9)
					   != 0) {
		LM_ERR("This message is not a SUBSCRIBE\n");
		goto error;
	}

	//check that this is a reg event - currently we only support reg event!
	event = cscf_get_event(msg);
	if(event.len != 3 || strncasecmp(event.s, "reg", 3) != 0) {
		LM_ERR("Accepting only <Event: reg>. Found: <%.*s>\n", event.len,
				event.s);
		goto done;
	}

	//get callid, from and to tags to be able to identify dialog
	//callid
	callid = cscf_get_call_id(msg, 0);
	if(callid.len <= 0 || !callid.s) {
		LM_ERR("unable to get callid\n");
		goto error;
	}
	//ftag
	if(!cscf_get_from_tag(msg, &ftag)) {
		LM_ERR("Unable to get ftag\n");
		goto error;
	}

	//ttag
	if(!cscf_get_to_tag(msg, &ttag)) {
		LM_ERR("Unable to get ttag\n");
		goto error;
	}

	//get presentity URI
	//check if SUBSCRIBE is initial or SUBSEQUENT
	if(ttag.len == 0) {
		LM_DBG("Msg has no ttag - this is initial subscribe - get presentity "
			   "URI from req URI\n");
		presentity_uri = cscf_get_public_identity_from_requri(msg);
	} else {
		presentity_uri =
				ul.get_presentity_from_subscriber_dialog(&callid, &ttag, &ftag);
		if(presentity_uri.len == 0) {
			LM_ERR("Unable to get presentity uri from subscriber dialog with "
				   "callid <%.*s>, ttag <%.*s> and ftag <%.*s>\n",
					callid.len, callid.s, ttag.len, ttag.s, ftag.len, ftag.s);
			goto done;
		}
	}


	asserted_id = cscf_get_asserted_identity(msg, 0);
	if(!asserted_id.len) {
		LM_ERR("P-Asserted-Identity empty.\n");
		goto error;
	}
	LM_DBG("P-Asserted-Identity <%.*s>.\n", asserted_id.len, asserted_id.s);

	LM_DBG("Looking for IMPU in usrloc <%.*s>\n", presentity_uri.len,
			presentity_uri.s);

	ul.lock_udomain((udomain_t *)_t, &presentity_uri);
	res = ul.get_impurecord((udomain_t *)_t, &presentity_uri, &r);

	if(res != 0) {
		LM_DBG("'%.*s' Not found in usrloc\n", presentity_uri.len,
				presentity_uri.s);
		ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
		goto done;
	}

	LM_DBG("<%.*s> found in usrloc\n", presentity_uri.len, presentity_uri.s);

	//check if the asserted identity is in the same group as that presentity uri
	if(r->public_identity.len == asserted_id.len
			&& strncasecmp(r->public_identity.s, asserted_id.s, asserted_id.len)
					   == 0) {
		LM_DBG("Identity found as AOR <%.*s>\n", presentity_uri.len,
				presentity_uri.s);
		ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
		ret = CSCF_RETURN_TRUE;
		goto done;
	}

	//check if asserted identity is in service profile
	ul.lock_subscription(r->s);
	if(r->s) {
		for(i = 0; i < r->s->service_profiles_cnt; i++)
			for(j = 0; j < r->s->service_profiles[i].public_identities_cnt;
					j++) {
				pi = &(r->s->service_profiles[i].public_identities[j]);
				if(!pi->barring && pi->public_identity.len == asserted_id.len
						&& strncasecmp(pi->public_identity.s, asserted_id.s,
								   asserted_id.len)
								   == 0) {
					LM_DBG("Identity found in SP[%d][%d]\n", i, j);
					ret = CSCF_RETURN_TRUE;
					ul.unlock_subscription(r->s);
					ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
					goto done;
				}
			}
	}
	ul.unlock_subscription(r->s);
	LM_DBG("Did not find p-asserted-identity <%.*s> in SP\n", asserted_id.len,
			asserted_id.s);

	//check if asserted is present in any of the path headers
	impucontact = r->linked_contacts.head;

	while(impucontact) {
		c = impucontact->contact;
		if(c->path.len) {
			LM_DBG("Path: <%.*s>.\n", c->path.len, c->path.s);
			for(i = 0; i < c->path.len - (asserted_id.len - 4); i++) {
				//we compare the asserted_id without "sip:" to the path
				if(strncasecmp(c->path.s + i, asserted_id.s + 4,
						   asserted_id.len - 4)
						== 0) {
					LM_DBG("Identity found in Path <%.*s>\n", c->path.len,
							c->path.s);
					ret = CSCF_RETURN_TRUE;
					ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
					goto done;
				}
			}
		}
		impucontact = impucontact->next;
	}

	LM_DBG("Did not find p-asserted-identity <%.*s> on Path\n", asserted_id.len,
			asserted_id.s);

	ul.unlock_udomain((udomain_t *)_t, &presentity_uri);
	LM_DBG("Subscribe forbidden\n");

done:
	if(presentity_uri.s) {
		// shm_malloc in cscf_get_public_identity_from_requri or get_presentity_from_subscriber_dialog
		shm_free(presentity_uri.s);
	}
	return ret;
error:
	ret = CSCF_RETURN_ERROR;
	if(presentity_uri.s) {
		// shm_malloc in cscf_get_public_identity_from_requri or get_presentity_from_subscriber_dialog
		shm_free(presentity_uri.s);
	}
	return ret;
}

/*
 * called to deliver new event into notification process
 * return 0 on success. anything else failure
 */
int event_reg(udomain_t *_d, impurecord_t *r_passed, ucontact_t *c_passed,
		int event_type, str *presentity_uri, str *watcher_contact,
		str *contact_uri, str *explit_dereg_contact,
		int num_explit_dereg_contact)
{
	impurecord_t *r;
	int num_impus;
	str *impu_list = 0;
	int res = 0;
	udomain_t *udomain;

	get_act_time();

	LM_DBG("Sending Reg event notifies\n");
	LM_DBG("Switching on event type: %d\n", event_type);
	switch(event_type) {
		case IMS_REGISTRAR_NONE:
			return 0;
		case IMS_REGISTRAR_SUBSCRIBE:
		case IMS_REGISTRAR_SUBSEQUENT_SUBSCRIBE:
			if(r_passed || !presentity_uri || !watcher_contact || !_d) {
				LM_ERR("this is a subscribe called from cfg file: r_passed be "
					   "zero and presentity_uri, watcher_contact and _d should "
					   "be valid for a subscribe");
				return 0;
			}
			LM_DBG("Event type is IMS REGISTRAR SUBSCRIBE/RE-SUBSCRIBE about "
				   "to get "
				   "reginfo_full\n");
			//lets get IMPU list for presentity as well as register for callbacks (IFF it is a new SUBSCRIBE)

			ul.lock_udomain(_d, presentity_uri);
			res = ul.get_impurecord(_d, presentity_uri, &r);
			if(res != 0) {
				LM_WARN("Strange, '%.*s' Not found in usrloc\n",
						presentity_uri->len, presentity_uri->s);
				ul.unlock_udomain(_d, presentity_uri);
				//no point in continuing
				return 1;
			}

			//get IMPU set from the presentity's subscription
			res = ul.get_impus_from_subscription_as_string(_d, r,
					0 /*all unbarred impus*/, &impu_list, &num_impus,
					0 /*pkg*/);
			if(res != 0) {
				LM_WARN("failed to get IMPUs from subscription\n");
				ul.unlock_udomain(_d, presentity_uri);
				if(impu_list) {
					pkg_free(impu_list);
				}
				return 1;
			}
			ul.unlock_udomain((udomain_t *)_d, presentity_uri);
			LM_DBG("About to create notification\n");

			create_notifications(_d, r_passed, c_passed, presentity_uri,
					watcher_contact, contact_uri, impu_list, num_impus,
					event_type, explit_dereg_contact, num_explit_dereg_contact);
			if(impu_list) {
				pkg_free(impu_list);
			}
			return 0;
			break;

			//richard: we only use reg unreg expired and refresh
		case IMS_REGISTRAR_CONTACT_UNREGISTERED:
		case IMS_REGISTRAR_CONTACT_DEREGISTERED:
		case IMS_REGISTRAR_CONTACT_UNREGISTERED_IMPLICIT:
		case IMS_REGISTRAR_CONTACT_REGISTERED:
		case IMS_REGISTRAR_CONTACT_REFRESHED:
		case IMS_REGISTRAR_CONTACT_EXPIRED:
			if(!r_passed || presentity_uri || watcher_contact || _d) {
				LM_ERR("this is a contact change passed from ul callback: "
					   "r_passed and c_passed should both be valid and "
					   "presentity_uri, watcher_contact and _d should be 0 for "
					   "ul callback\n");
				return 0;
			}
			//this is a ulcallback so r_passed domain is already locked
			res = ul.get_impus_from_subscription_as_string(_d, r_passed,
					0 /*all unbarred impus*/, &impu_list, &num_impus,
					0 /*pkg*/);
			if(res != 0) {
				LM_WARN("failed to get IMPUs from subscription\n");
				if(impu_list) {
					pkg_free(impu_list);
				}
				return 1;
			}

			//TODO this should be a configurable module param
			if(ul.register_udomain(domain, &udomain) < 0) {
				LM_ERR("Unable to register usrloc domain....aborting\n");
				if(impu_list) {
					pkg_free(impu_list);
				}
				return 0;
			}
			LM_DBG("About to create notification\n");
			create_notifications(_d, r_passed, c_passed, presentity_uri,
					watcher_contact, contact_uri, impu_list, num_impus,
					event_type, explit_dereg_contact, num_explit_dereg_contact);
			if(impu_list) {
				pkg_free(impu_list);
			}
			return 1;

		default:
			LM_ERR("ERR:event_reg: Unknown event %d\n", event_type);
			//            if (send_now) notification_timer(0, 0);
			return 1;
	}
}

int notify_subscribers(impurecord_t *impurecord, ucontact_t *contact,
		str *explit_dereg_contact, int num_explit_dereg_contact, int event_type)
{
	event_reg(0, impurecord, contact, event_type, 0, 0, 0, explit_dereg_contact,
			num_explit_dereg_contact);

	return 0;
}

/*! Called to process a contact in a received publish document. No locks held when calling this function
 *
 * @param subscription
 * @param _d
 * @param expires
 * @param contact_uri
 * @param contact_state
 * @return
 */
int process_contact(ims_subscription *subscription, udomain_t *_d, int expires,
		str contact_uri, int contact_state)
{

	int ret = CSCF_RETURN_TRUE;
	int i, j;
	ims_public_identity *pi = 0;
	struct ucontact *ucontact;
	str callid = {0, 0};
	str path = {0, 0};
	impurecord_t *implicit_impurecord = 0;
	get_act_time();

	//first get the subscription
	//then go through each implicit public identity (exclude the explicit identity)
	//get the IMPU rec for each implicit public identity
	//then get the contact for each implicit IMPU and delete if contact_state == STATE_TERMINATED
	//then get the contact for each explicit IMPU and delete if contact_state == STATE_TERMINATED


	//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 ((presentity_aor->len == pi->public_identity.len) && memcmp(presentity_aor.s, pi->public_identity.s, presentity_aor->len) == 0) { //we don't need to update the explicit IMPU
			//                LM_DBG("Ignoring explicit identity <%.*s>, updating later..... end of fn\n", presentity_aor.len, presentity_aor->s);
			//                goto next_implicit_impu;
			//            }
			ul.lock_udomain(_d, &pi->public_identity);
			if(ul.get_impurecord(_d, &pi->public_identity, &implicit_impurecord)
					!= 0) {
				LM_DBG("usrloc does not have impurecord for implicitly IMPU, "
					   "ignore\n");
				goto next_implicit_impu;
			}
			if(ul.get_ucontact(
					   &contact_uri, &callid, &path, 0 /*cseq*/, &ucontact)
					!= 0) { //contact does not exist
				LM_DBG("This contact: <%.*s> is not in usrloc, ignore - NOTE: "
					   "You need S-CSCF usrloc set to match_mode "
					   "CONTACT_ONLY\n",
						contact_uri.len, contact_uri.s);
				goto next_implicit_impu;
			} else { //contact exists
				if(ucontact->state != CONTACT_DELETED) {
					if(contact_state == STATE_TERMINATED) {
						//delete contact
						LM_DBG("This contact <%.*s> is in state terminated and "
							   "is in usrloc so removing it from usrloc\n",
								contact_uri.len, contact_uri.s);
						ul.lock_contact_slot(&contact_uri);
						ucontact->state = CONTACT_DELETE_PENDING;
						if(implicit_impurecord->shead) {
							//send NOTIFY to all subscribers of this IMPU.
							notify_subscribers(implicit_impurecord, ucontact, 0,
									0, IMS_REGISTRAR_CONTACT_UNREGISTERED);
						}
						ul.unlock_contact_slot(&contact_uri);
						//                        if (ul.unlink_contact_from_impu(implicit_impurecord, ucontact, 1, 0 /*implicit dereg of contact from IMPU*/) != 0) {
						//                            LM_ERR("Failed to delete ucontact <%.*s> from implicit IMPU\n", contact_uri.len, contact_uri.s);
						//                            ul.unlock_contact_slot(&contact_uri);
						//                            ul.release_ucontact(ucontact);
						//                            goto next_implicit_impu; //TODO: don't need to use goto here...
						//                        }
					} else { //state is active
						LM_DBG("This contact: <%.*s> is not in state "
							   "terminated and is in usrloc, ignore\n",
								contact_uri.len, contact_uri.s);
						ul.release_ucontact(ucontact);
						goto next_implicit_impu;
					}
				}
				ul.release_ucontact(ucontact);
			}
		next_implicit_impu:
			ul.unlock_udomain(_d, &pi->public_identity);
		}
	}

	if(contact_state == STATE_TERMINATED) {
		//at this point we can assume that all notifies were sent out to the subscribers of the IMPUs related to the explicit and implicit impus.
		//we must now change the state of the contact to deleted so they can be removed from the IMPU in the timer process...
		if(ul.get_ucontact(&contact_uri, &callid, &path, 0 /*cseq*/, &ucontact)
				!= 0) { //contact does not exist
			LM_DBG("This contact: <%.*s> is not in usrloc, ignore - NOTE: You "
				   "need S-CSCF usrloc set to match_mode CONTACT_ONLY\n",
					contact_uri.len, contact_uri.s);
		} else {
			ul.lock_contact_slot(&contact_uri);
			ucontact->state = CONTACT_DELETED;
			ul.unlock_contact_slot(&contact_uri);
			ul.release_ucontact(ucontact);
		}
	}

	//    ul.lock_subscription(subscription);
	//    subscription->ref_count--;
	//    LM_DBG("subscription ref count after sub is now %d\n", subscription->ref_count);
	//    ul.unlock_subscription(subscription);

	//	ul.lock_udomain(_d, &presentity_impurecord->public_identity);

	//    if (ul.get_ucontact(&contact_uri, &callid, &path, 0/*cseq*/, &ucontact) != 0) { //contact does not exist
	//        LM_DBG("This contact: <%.*s> is not in usrloc, ignore - NOTE: You need S-CSCF usrloc set to match_mode CONTACT_ONLY\n", contact_uri.len, contact_uri.s);
	//        return ret;
	//    } else {//contact exists
	//        if (VALID_CONTACT(ucontact, act_time)) {
	//            if (contact_state == STATE_TERMINATED) {
	//                //delete contact
	//                LM_DBG("This contact <%.*s> is in state terminated and is in usrloc so removing it from usrloc\n", contact_uri.len, contact_uri.s);
	////                ul.lock_contact_slot(&contact_uri);
	////                if (ul.unlink_contact_from_impu(presentity_impurecord, ucontact, 1, 0 /*implicit dereg of contact from IMPU */) != 0) {
	////                    LM_ERR("Failed to delete ucontact <%.*s>\n", contact_uri.len, contact_uri.s);
	////                    ret = CSCF_RETURN_FALSE;
	////                    ul.unlock_contact_slot(&contact_uri);
	////                    ul.release_ucontact(ucontact);
	////                    goto done;
	////                }
	////                ul.unlock_contact_slot(&contact_uri);
	//                ucontact->state = CONTACT_DELETE_PENDING;
	//                if (implicit_impurecord->shead) {
	//                    //send NOTIFY to all subscribers of this IMPU.
	//                    notify_subscribers(implicit_impurecord);
	//                }
	//            } else {//state is active
	//                LM_DBG("This contact: <%.*s> is not in state terminated and is in usrloc, ignore\n", contact_uri.len, contact_uri.s);
	//                ul.release_ucontact(ucontact);
	//                goto done;
	//            }
	//        }
	//        ul.release_ucontact(ucontact);
	//    }

	//done:
	//    ul.unlock_udomain(_d, &presentity_impurecord->public_identity);
	return ret;
}

int reginfo_parse_state(char *s)
{
	if(s == NULL) {
		return STATE_UNKNOWN;
	}
	switch(strlen(s)) {
		case 6:
			if(strncmp(s, "active", 6) == 0)
				return STATE_ACTIVE;
			break;
		case 10:
			if(strncmp(s, "terminated", 10) == 0)
				return STATE_TERMINATED;
			break;
		default:
			LM_ERR("Unknown State %s\n", s);
			return STATE_UNKNOWN;
	}
	LM_ERR("Unknown State %s\n", s);
	return STATE_UNKNOWN;
}

int reginfo_parse_event(char *s)
{
	if(s == NULL) {
		return EVENT_UNKNOWN;
	}
	switch(strlen(s)) {
		case 7:
			if(strncmp(s, "created", 7) == 0)
				return EVENT_CREATED;
			if(strncmp(s, "expired", 7) == 0)
				return EVENT_EXPIRED;
			break;
		case 9:
			if(strncmp(s, "refreshed", 9) == 0)
				return EVENT_CREATED;
			break;
		case 10:
			if(strncmp(s, "registered", 10) == 0)
				return EVENT_REGISTERED;
			if(strncmp(s, "terminated", 10) == 0)
				return EVENT_TERMINATED;
			break;
		case 12:
			if(strncmp(s, "unregistered", 12) == 0)
				return EVENT_UNREGISTERED;
			break;
		default:
			LM_ERR("Unknown Event %s\n", s);
			return EVENT_UNKNOWN;
	}
	LM_ERR("Unknown Event %s\n", s);
	return EVENT_UNKNOWN;
}

xmlNodePtr xmlGetNodeByName(xmlNodePtr parent, const char *name)
{
	xmlNodePtr cur = parent;
	xmlNodePtr match = NULL;
	while(cur) {
		if(xmlStrcasecmp(cur->name, (unsigned char *)name) == 0)
			return cur;
		match = xmlGetNodeByName(cur->children, name);
		if(match)
			return match;
		cur = cur->next;
	}
	return NULL;
}

char *xmlGetAttrContentByName(xmlNodePtr node, const char *name)
{
	xmlAttrPtr attr = node->properties;
	while(attr) {
		if(xmlStrcasecmp(attr->name, (unsigned char *)name) == 0)
			return (char *)xmlNodeGetContent(attr->children);
		attr = attr->next;
	}
	return NULL;
}

int process_publish_body(
		struct sip_msg *msg, str publish_body, udomain_t *domain)
{
	xmlDocPtr doc = NULL;
	xmlNodePtr doc_root = NULL, registrations = NULL, contacts = NULL,
			   uris = NULL;
	str aor = {0, 0};
	str callid = {0, 0};
	str contact_uri = {0, 0};
	str received = {0, 0};
	str path = {0, 0};
	str user_agent = {0, 0};
	int reg_state, contact_state, event, expires, result,
			final_result = CSCF_RETURN_FALSE;
	char *expires_char, *cseq_char;
	int cseq = 0;
	impurecord_t *presentity_impurecord;
	ims_subscription *subscription = 0;
	int subscription_locked;

	doc = xmlParseMemory(publish_body.s, publish_body.len);
	if(doc == NULL) {
		LM_ERR("Error while parsing the xml body message, Body is:\n%.*s\n",
				publish_body.len, publish_body.s);
		return -1;
	}
	doc_root = xmlGetNodeByName(doc->children, "reginfo");
	if(doc_root == NULL) {
		LM_ERR("while extracting the reginfo node\n");
		goto error;
	}
	registrations = doc_root->children;
	while(registrations) {
		subscription_locked = 0;
		/* Only process registration sub-items */
		if(xmlStrcasecmp(registrations->name, BAD_CAST "registration") != 0)
			goto next_registration;
		reg_state = reginfo_parse_state(
				xmlGetAttrContentByName(registrations, "state"));
		aor.s = xmlGetAttrContentByName(registrations, "aor");
		if(aor.s == NULL) {
			LM_ERR("No AOR for this registration!\n");
			goto next_registration;
		}
		aor.len = strlen(aor.s);
		LM_DBG("AOR %.*s has reg_state \"%d\"\n", aor.len, aor.s, reg_state);

		//TOD get IMPU record here
		ul.lock_udomain(domain, &aor);
		if(ul.get_impurecord(domain, &aor, &presentity_impurecord) != 0) {
			LM_DBG("usrloc does not have impurecord for presentity being "
				   "published too, ignore\n");
			ul.unlock_udomain(domain, &aor);
			goto next_registration;
		}
		//get the subscription which we will use later

		subscription = presentity_impurecord->s;
		if(!subscription) {
			LM_DBG("No subscriber info associated with <%.*s>, so no implicit "
				   "IMPUs to process\n",
					presentity_impurecord->public_identity.len,
					presentity_impurecord->public_identity.s);
			ul.unlock_udomain(domain, &aor);
			goto next_registration;
		}
		ul.lock_subscription(subscription);
		subscription->ref_count++;
		LM_DBG("subscription ref count after add is now %d\n",
				subscription->ref_count);
		ul.unlock_subscription(subscription);
		subscription_locked = 1;

		ul.unlock_udomain(domain, &aor);

		LM_DBG("Received impurecord for presentity being published on [%.*s]\n",
				aor.len, aor.s);

		if(reg_state == STATE_TERMINATED) {
			LM_DBG("This impurecord is in STATE_TERMINATED - TODO we should "
				   "should delete all contacts\n");
		} else {
			/* Now lets process the Contact's from this Registration: */
			contacts = registrations->children;
			while(contacts) {
				if(xmlStrcasecmp(contacts->name, BAD_CAST "contact") != 0)
					goto next_contact;
				callid.s = xmlGetAttrContentByName(contacts, "callid");
				if(callid.s == NULL) {
					LM_DBG("No Call-ID for this contact!\n");
					callid.len = 0;
				} else {
					callid.len = strlen(callid.s);
					LM_DBG("contact has callid <%.*s>\n", callid.len, callid.s);
				}

				received.s = xmlGetAttrContentByName(contacts, "received");
				if(received.s == NULL) {
					LM_DBG("No received for this contact!\n");
					received.len = 0;
				} else {
					received.len = strlen(received.s);
					LM_DBG("contact has received <%.*s>\n", received.len,
							received.s);
				}

				path.s = xmlGetAttrContentByName(contacts, "path");
				if(path.s == NULL) {
					LM_DBG("No path for this contact!\n");
					path.len = 0;
				} else {
					path.len = strlen(path.s);
					LM_DBG("contact has path <%.*s>\n", path.len, path.s);
				}

				user_agent.s = xmlGetAttrContentByName(contacts, "user_agent");
				if(user_agent.s == NULL) {
					LM_DBG("No user_agent for this contact!\n");
					user_agent.len = 0;
				} else {
					user_agent.len = strlen(user_agent.s);
					LM_DBG("contact has user_agent <%.*s>\n", user_agent.len,
							user_agent.s);
				}
				event = reginfo_parse_event(
						xmlGetAttrContentByName(contacts, "event"));
				if(event == EVENT_UNKNOWN) {
					LM_ERR("No event for this contact - going to next "
						   "contact!\n");
					goto next_contact;
				}
				expires_char = xmlGetAttrContentByName(contacts, "expires");
				if(expires_char == NULL) {
					LM_ERR("No expires for this contact - going to next "
						   "contact!\n");
					goto next_contact;
				}
				expires = atoi(expires_char);
				if(expires < 0) {
					LM_ERR("No valid expires for this contact - going to next "
						   "contact!\n");
					goto next_contact;
				}

				contact_state = reginfo_parse_state(
						xmlGetAttrContentByName(contacts, "state"));
				if(contact_state == STATE_UNKNOWN) {
					LM_ERR("No state for this contact - going to next "
						   "contact!\n");
					goto next_contact;
				}

				LM_DBG("Contact state %d: Event \"%d\", expires %d\n",
						contact_state, event, expires);

				cseq_char = xmlGetAttrContentByName(contacts, "cseq");
				if(cseq_char == NULL) {
					LM_DBG("No cseq for this contact!\n");
				} else {
					cseq = atoi(cseq_char);
					if(cseq < 0) {
						LM_DBG("No valid cseq for this contact!\n");
					}
				}

				/* Now lets process the URI's from this Contact: */
				uris = contacts->children;
				while(uris) {
					if(xmlStrcasecmp(uris->name, BAD_CAST "uri") != 0)
						goto next_uri;
					contact_uri.s = (char *)xmlNodeGetContent(uris);
					if(contact_uri.s == NULL) {
						LM_ERR("No URI for this contact - going to next "
							   "registration!\n");
						goto next_registration;
					}
					contact_uri.len = strlen(contact_uri.s);
					LM_DBG("Contact: %.*s\n", contact_uri.len, contact_uri.s);

					/* Add to Usrloc: */
					result = process_contact(subscription, domain, expires,
							contact_uri, contact_state);

					/* Process the result */
					if(final_result != CSCF_RETURN_TRUE)
						final_result = result;
				next_uri:
					uris = uris->next;
				}
			next_contact:
				contacts = contacts->next;
			}
		}
	next_registration:
		registrations = registrations->next;
		if(subscription_locked) {
			ul.lock_subscription(subscription);
			subscription->ref_count--;
			LM_DBG("subscription ref count after sub is now %d\n",
					subscription->ref_count);
			ul.unlock_subscription(subscription);
		}
	}
error:
	/* Free the XML-Document */
	if(doc)
		xmlFreeDoc(doc);
	return final_result;
}

/**
 * Modify the subscription based on publish
 * @param msg - the SIP PUBLISH message
 * @param str1 - not used
 * @param str2 - not used
 * @returns #CSCF_RETURN_TRUE if allowed, #CSCF_RETURN_FALSE if not, #CSCF_RETURN_ERROR on error
 */
int publish_reg(struct sip_msg *msg, char *_t, char *str2)
{

	udomain_t *domain = (udomain_t *)_t;
	int expires = 0;
	int ret = CSCF_RETURN_FALSE;
	str body;

	LM_DBG("Publishing reg info\n");


	/* If not done yet, parse the whole message now: */
	if(parse_headers(msg, HDR_EOH_F, 0) == -1) {
		LM_ERR("Error parsing headers\n");
		return -1;
	}
	if(get_content_length(msg) == 0) {
		LM_DBG("Content length = 0\n");
		/* No Body? Then there is no published information available, which is ok. */
		goto done;
	} else {
		body.s = get_body(msg);
		if(body.s == NULL) {
			LM_ERR("cannot extract body from msg\n");
			goto done;
		}
		body.len = get_content_length(msg);
	}

	LM_DBG("Body is %.*s\n", body.len, body.s);

	ret = process_publish_body(msg, body, (udomain_t *)domain);

done:
	//get expires
	expires = cscf_get_expires_hdr(msg, 0);
	if(expires == -1)
		expires = subscription_default_expires;

	if(ret == CSCF_RETURN_TRUE) {
		LM_DBG("Sending 200 OK to publishing user\n");
		subscribe_reply(
				msg, 200, MSG_REG_PUBLISH_OK, &expires, &scscf_name_str);
	}

	return ret;
}

/**
 * Save this subscription.
 * @param msg - the SIP SUBSCRIBE message
 * @param str1 - not used
 * @param str2 - not used
 * @returns #CSCF_RETURN_TRUE if allowed, #CSCF_RETURN_FALSE if not, #CSCF_RETURN_ERROR on error
 */
int subscribe_to_reg(struct sip_msg *msg, char *_t, char *str2)
{
	int ret = CSCF_RETURN_FALSE;
	int res;
	str presentity_uri = {0, 0};
	str event;
	int event_i = IMS_EVENT_NONE;
	int expires = 0, expires_time = 0;
	str watcher_impu;
	str watcher_contact;
	impurecord_t *presentity_impurecord;
	reg_subscriber *reg_subscriber;
	subscriber_data_t subscriber_data;

	int event_type = IMS_REGISTRAR_NONE;

	int rt = 0;

	str callid = {0, 0};
	str ftag = {0, 0};
	str ttag = {0, 0};
	str record_route = {0, 0};

	int remote_cseq = 0;
	int local_cseq = 0;

	udomain_t *domain = (udomain_t *)_t;

	LM_DBG("Saving SUBSCRIBE\n");

	//check we have a valid transaction, if not, create one TODO

	//check that this is a request
	if(msg->first_line.type != SIP_REQUEST) {
		LM_ERR("This message is not a request\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}

	//check that this is a subscribe
	if(msg->first_line.type != SIP_REQUEST
			|| msg->first_line.u.request.method_value != METHOD_SUBSCRIBE) {
		LM_ERR("This message is not a SUBSCRIBE\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}

	//check that this is a reg event  - we currently only support reg event
	event = cscf_get_event(msg);
	if(event.len != 3 || strncasecmp(event.s, "reg", 3) != 0) {
		LM_WARN("Accepting only <Event: reg>. Found: <%.*s>\n", event.len,
				event.s);
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}
	event_i = IMS_EVENT_REG;

	//get callid, from and to tags to be able to identify dialog
	//callid
	callid = cscf_get_call_id(msg, 0);
	if(callid.len <= 0 || !callid.s) {
		LM_ERR("unable to get callid\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}
	//ftag
	if(!cscf_get_from_tag(msg, &ftag)) {
		LM_ERR("Unable to get ftag\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}

	//ttag
	if(!cscf_get_to_tag(msg, &ttag)) {
		LM_ERR("Unable to get ttag\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}

	//check if SUBSCRIBE is initial or SUBSEQUENT
	if(ttag.len == 0) {
		LM_DBG("Msg has no ttag - this is initial subscribe\n");
		//to tag - doesn't exist in initial request, must use tm to get it
		tmb.t_get_reply_totag(msg, &ttag);
		LM_DBG("Got to tag from sent response: [%.*s]\n", ttag.len, ttag.s);
		LM_DBG("This is initial subscribe - get presentity URI from req URI\n");
		presentity_uri = cscf_get_public_identity_from_requri(msg);
		event_type = IMS_REGISTRAR_SUBSCRIBE;
	} else {
		LM_DBG("Msg has ttag: [%.*s] - this is subsequent subscribe\n",
				ttag.len, ttag.s);
		//cscf_get_to_uri(msg, &presentity_uri);
		LM_DBG("This is subsequent subscribe - get presentity URI from stored "
			   "subscriber dialog\n");
		//get the presentity uri from To Header
		//cscf_get_to_uri(msg, &presentity_uri);
		presentity_uri =
				ul.get_presentity_from_subscriber_dialog(&callid, &ttag, &ftag);
		if(presentity_uri.len == 0) {
			LM_ERR("Unable to get presentity uri from subscriber dialog with "
				   "callid <%.*s>, ttag <%.*s> and ftag <%.*s>\n",
					callid.len, callid.s, ttag.len, ttag.s, ftag.len, ftag.s);
			ret = CSCF_RETURN_FALSE;
			goto doneorerror;
		}
		event_type = IMS_REGISTRAR_SUBSEQUENT_SUBSCRIBE;
	}

	//get cseq
	remote_cseq = cscf_get_cseq(msg, 0);
	local_cseq = remote_cseq + 1;

	//get sockinfo_str
	str sockinfo_str = msg->rcv.bind_address->sock_str;

	//get record route
	/*process record route and add it to a string*/
	if(msg->record_route != NULL) {
		rt = print_rr_body(msg->record_route, &record_route, 0, 0);
		if(rt != 0) {
			LM_ERR("Failed processing the record route [%d]\n", rt);
			record_route.s = NULL;
			record_route.len = 0;
			ret = CSCF_RETURN_FALSE;
			goto doneorerror;
		}
	}

	//get the presentity uri from To Header
	//cscf_get_to_uri(msg, &presentity_uri);

	//get the watcher uri from the to header
	cscf_get_from_uri(msg, &watcher_impu);

	if(!watcher_impu.len) {
		LM_ERR("Failed to get URI from To header.\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}
	LM_DBG("To header URI (watcher URI) <%.*s>.\n", watcher_impu.len,
			watcher_impu.s);

	//get the watcher contact from contact header
	watcher_contact = cscf_get_contact(msg);
	if(!watcher_contact.len) {
		LM_ERR("ERR: Contact empty.\n");
		ret = CSCF_RETURN_FALSE;
		goto doneorerror;
	}
	LM_DBG("watcher Contact <%.*s>.\n", watcher_contact.len, watcher_contact.s);

	//get expires
	expires = cscf_get_expires_hdr(msg, 0);
	if(expires == -1)
		expires = subscription_default_expires;

	//build subscriber parcel for passing data around more easily
	subscriber_data.callid = &callid;
	subscriber_data.event = event_i;
	subscriber_data.ftag = &ftag;
	subscriber_data.ttag = &ttag;
	subscriber_data.record_route = &record_route;
	subscriber_data.sockinfo_str = &sockinfo_str;
	subscriber_data.local_cseq = local_cseq;
	subscriber_data.watcher_uri = &watcher_impu;
	subscriber_data.watcher_contact = &watcher_contact;
	subscriber_data.version = 1; /*default version starts at 1*/

	if(expires > 0) {
		LM_DBG("expires is more than zero - SUBSCRIBE\n");

		if(expires < subscription_min_expires)
			expires = subscription_min_expires;
		if(expires > subscription_max_expires)
			expires = subscription_max_expires;
		expires = randomize_expires(expires, subscription_expires_range);
		get_act_time();
		expires_time = expires + act_time;
		subscriber_data.expires = expires_time;

		LM_DBG("Subscription expires time <%d> expiry length <%d>\n",
				expires_time, expires);

		LM_DBG("Received a new subscription (expires > 0), checking to see of "
			   "impurecord for presentity exists\n");
		ul.lock_udomain(domain, &presentity_uri);
		res = ul.get_impurecord(
				domain, &presentity_uri, &presentity_impurecord);
		if(res != 0) {
			LM_WARN("usrloc does not have impurecord for presentity being "
					"subscribed too, This a problem we shouldn't get here as "
					"offline users should have been assigned in config file\n");
			ul.unlock_udomain(domain, &presentity_uri);
			ret = CSCF_RETURN_FALSE;
			goto doneorerror;
		}

		LM_DBG("Received impurecord for presentity being subscribed to "
			   "[%.*s]\n",
				presentity_impurecord->public_identity.len,
				presentity_impurecord->public_identity.s);

		res = ul.get_subscriber(presentity_impurecord, &presentity_uri,
				&watcher_contact, event_i, &reg_subscriber);
		if(res != 0) {
			LM_DBG("this must be a new subscriber, lets add it\n");
			subscriber_data.presentity_uri =
					&presentity_impurecord->public_identity;
			res = ul.add_subscriber(presentity_impurecord, &subscriber_data,
					&reg_subscriber, 0 /*not a db_load*/);
			if(res != 0) {
				LM_ERR("Failed to add new subscription\n");
				ul.unlock_udomain(domain, &presentity_uri);
				ret = CSCF_RETURN_FALSE;
				goto doneorerror;
			}
		} else {
			if(memcmp(reg_subscriber->call_id.s, subscriber_data.callid->s,
					   reg_subscriber->call_id.len)
							== 0
					&& memcmp(reg_subscriber->from_tag.s,
							   subscriber_data.ftag->s,
							   reg_subscriber->from_tag.len)
							   == 0
					&& memcmp(reg_subscriber->to_tag.s, subscriber_data.ttag->s,
							   reg_subscriber->to_tag.len)
							   == 0) {
				LM_DBG("This has same callid, fromtag and totag - must be a re "
					   "subscribe, lets update it\n");
				res = ul.update_subscriber(presentity_impurecord,
						&reg_subscriber, &expires_time, 0, 0);
				if(res != 1) {
					LM_ERR("Failed to update subscription - expires is %d\n",
							expires_time);
					ul.unlock_udomain(domain, &presentity_uri);
					ret = CSCF_RETURN_FALSE;
					goto doneorerror;
				}
			} else {
				LM_ERR("Re-subscribe for same watcher_contact, presentity_uri, "
					   "event but with different callid [%.*s], fromtag [%.*s] "
					   "and totag [%.*s] for presentity [%.*s] and watcher "
					   "contact [%.*s] - What happened?\n",
						subscriber_data.callid->len, subscriber_data.callid->s,
						subscriber_data.ftag->len, subscriber_data.ftag->s,
						subscriber_data.ttag->len, subscriber_data.ttag->s,
						presentity_impurecord->public_identity.len,
						presentity_impurecord->public_identity.s,
						subscriber_data.watcher_contact->len,
						subscriber_data.watcher_contact->s);
				LM_DBG("Removing old subscriber and adding new one\n");
				subscriber_data.presentity_uri =
						&presentity_impurecord->public_identity;
				ul.external_delete_subscriber(reg_subscriber, (udomain_t *)_t,
						0 /*domain is already locked*/);
				res = ul.add_subscriber(presentity_impurecord, &subscriber_data,
						&reg_subscriber, 0 /*not a db_load*/);
				if(res != 0) {
					LM_ERR("Failed to add new subscription\n");
					ul.unlock_udomain(domain, &presentity_uri);
					ret = CSCF_RETURN_FALSE;
					goto doneorerror;
				}
			}
		}

		ul.unlock_udomain(domain, &presentity_uri);

		ret = CSCF_RETURN_TRUE;
		LM_DBG("Sending 200 OK to subscribing user\n");
		subscribe_reply(
				msg, 200, MSG_REG_SUBSCRIBE_OK, &expires, &presentity_uri);

		if(event_type == IMS_REGISTRAR_SUBSCRIBE
				|| event_type == IMS_REGISTRAR_SUBSEQUENT_SUBSCRIBE) {
			if(event_reg(domain, 0, 0, event_type, &presentity_uri,
					   &watcher_contact, 0, 0, 0)
					!= 0) {
				LM_ERR("failed adding notification for reg events\n");
				ret = CSCF_RETURN_ERROR;
				goto doneorerror;
			} else {
				LM_DBG("success adding notification for reg events\n");
			}
		}
	} else {
		event_type = IMS_REGISTRAR_UNSUBSCRIBE;
		LM_DBG("expires is zero or less - UNSUBSCRIBE\n");

		ul.lock_udomain(domain, &presentity_uri);
		res = ul.get_impurecord(
				domain, &presentity_uri, &presentity_impurecord);
		if(res != 0) {
			LM_DBG("usrloc does not have impurecord for presentity being "
				   "subscribed too, we should create one.... TODO\n");
			ul.unlock_udomain(domain, &presentity_uri);
			goto doneorerror;
		}
		LM_DBG("Received impurecord for presentity being unsubscribed to "
			   "[%.*s]\n",
				presentity_impurecord->public_identity.len,
				presentity_impurecord->public_identity.s);
		//        //get the subscription if it exists
		LM_DBG("Getting subscription s from usrloc\n");

		res = ul.get_subscriber(presentity_impurecord, &presentity_uri,
				&watcher_contact, event_i, &reg_subscriber);
		if(res != 0) {
			LM_WARN("could not get subscriber\n");
			ret = CSCF_RETURN_FALSE;
			ul.unlock_udomain(domain, &presentity_uri);
			goto doneorerror;
		} else {
			LM_DBG("subscription s exists\n");
			LM_DBG("deleting subscriber from usrloc\n");
			ul.external_delete_subscriber(reg_subscriber, (udomain_t *)_t,
					0 /*domain is already locked*/);
			ul.unlock_udomain(domain, &presentity_uri);
		}
		ret = CSCF_RETURN_TRUE;
		LM_DBG("Sending 200 OK to subscribing user\n");
		subscribe_reply(
				msg, 200, MSG_REG_UNSUBSCRIBE_OK, &expires, &presentity_uri);
	}

doneorerror:
	//free memory
	// shm_malloc in cscf_get_public_identity_from_requri or get_presentity_from_subscriber_dialog
	if(presentity_uri.s)
		shm_free(presentity_uri.s);
	if(record_route.s)
		pkg_free(record_route.s);
	return ret;
}


str expires_hdr1 = {"Expires: ", 9};
str expires_hdr2 = {"\r\n", 2};
str contact_hdr1 = {"Contact: <", 10};
str contact_hdr2 = {">\r\n", 3};

/**
 * Replies to a SUBSCRIBE and also adds the need headers.
 * Path for example.
 * @param msg - the SIP SUBSCRIBE message
 * @param code - response code to send
 * @param text - response phrase to send
 * @param expires - expiration interval in seconds
 * @param contact - contact to add to reply
 * @returns the tmn.r_reply returned value value
 */
int subscribe_reply(
		struct sip_msg *msg, int code, char *text, int *expires, str *contact)
{
	str hdr = {0, 0};

	if(expires) {
		hdr.len = expires_hdr1.len + 12 + expires_hdr1.len;
		hdr.s = pkg_malloc(hdr.len);
		if(!hdr.s) {
			LM_ERR("Error allocating %d bytes.\n", hdr.len);
		} else {
			hdr.len = 0;
			STR_APPEND(hdr, expires_hdr1);
			sprintf(hdr.s + hdr.len, "%d", *expires);
			hdr.len += strlen(hdr.s + hdr.len);
			STR_APPEND(hdr, expires_hdr2);
			cscf_add_header_rpl(msg, &hdr);
			pkg_free(hdr.s);
		}
	}

	if(contact) {
		hdr.len = contact_hdr1.len + contact->len + contact_hdr2.len;
		hdr.s = pkg_malloc(hdr.len);
		if(!hdr.s) {
			LM_ERR("Error allocating %d bytes.\n", hdr.len);
		} else {
			hdr.len = 0;
			STR_APPEND(hdr, contact_hdr1);
			STR_APPEND(hdr, *contact);
			STR_APPEND(hdr, contact_hdr2);
			cscf_add_header_rpl(msg, &hdr);
			pkg_free(hdr.s);
		}
	}

	return tmb.t_reply(msg, code, text);
}

/* function to convert contact aor to only have data after @ - ie strip user part */
int aor_to_contact(str *aor, str *contact)
{
	char *p;
	int ret = 0; //success

	contact->s = aor->s;
	contact->len = aor->len;
	if(memcmp(aor->s, "sip:", 4) == 0) {
		contact->s = aor->s + 4;
		contact->len -= 4;
	}

	if((p = memchr(contact->s, '@', contact->len))) {
		contact->len -= (p - contact->s + 1);
		contact->s = p + 1;
	}

	if((p = memchr(contact->s, ';', contact->len))) {
		contact->len = p - contact->s;
	}

	if((p = memchr(contact->s, '>', contact->len))) {
		contact->len = p - contact->s;
	}

	// remove default port 5060
	if((p = memchr(contact->s, ':', contact->len))) {
		if(contact->len - (p - contact->s) == 5 && !memcmp(p, ":5060", 5)) {
			contact->len -= 5;
		}
	}

	return ret;
}

/*!
 * \brief Match two contacts with full string - ips, ports and aliases
 * \param c1 contact string 1
 * \param c2 contact string 2
 * \return 1 when they match, 0 when they not match
 */
static int contact_match(str *c1, str *c2)
{
	LM_DBG("Matching full contact string - comparing [%.*s] and [%.*s]\n",
			c1->len, c1->s, c2->len, c2->s);
	if((c1->len == c2->len) && !memcmp(c1->s, c2->s, c1->len)) {
		return 1;
	}

	return 0;
}

/*!
 * \brief Match a contact record to a contact string but only compare the ip port portion
 * \param ptr contact record
 * \param _c contact string
 * \return ptr on successfull match, 0 when they not match
 */
int contact_port_ip_match(str *c1, str *c2)
{

	str ip_port1, ip_port2;
	aor_to_contact(c1, &ip_port1); //strip userpart from test contact
	aor_to_contact(c2, &ip_port2); //strip userpart from test contact
	LM_DBG("Matching contact using only port and ip - comparing [%.*s] and "
		   "[%.*s]\n",
			ip_port1.len, ip_port1.s, ip_port2.len, ip_port2.s);
	if((ip_port1.len == ip_port2.len)
			&& !memcmp(ip_port1.s, ip_port2.s, ip_port1.len)) {
		return 1;
	}
	return 0;
}

/*!
 * \brief Extract ip and port from contact alias if exists
 * \param contact contact string 1
 * \param port_ip extracted ip and port
 * \return 0 on successfull, 1 failed
 */
static int extract_alias_ip_port(str *contact, str *port_ip)
{
	char *p, *port_s;
	int tmp_len;

	port_ip->s = contact->s;
	port_ip->len = contact->len;

	// if NULL -> alias is not present
	if(port_ip->len > 6
			&& (p = _strnistr(port_ip->s, "alias=", port_ip->len)) != NULL) {
		// strip all before 'alias=' and 'alias=' itself
		// this is the length of 'IP~PORT~PROTO' string
		port_ip->len -= (p - port_ip->s + 6);
		// this is the IP's starting position
		port_ip->s = p + 6;

		LM_DBG("alias->len=%d [%.*s]\n", port_ip->len, port_ip->len,
				port_ip->s);

		// find the first '~' separates IP from PORT
		// if NULL -> alias contains only IP
		if((p = memchr(port_ip->s, '~', port_ip->len))) {
			// this is the temporary length of 'PORT~PROTO' string
			tmp_len = port_ip->s + port_ip->len - p - 1;
			// set PORT starting position
			port_s = p + 1;

			LM_DBG("port~proto->len=%d [%.*s]\n", tmp_len, tmp_len, port_s);

			// find the second '~' separates PORT from PROTO
			if((p = memchr(port_s, '~', tmp_len))) {
				// strip '~PROTO' string
				tmp_len = (port_ip->len + port_ip->s - p);

				port_ip->len -= (port_ip->len + port_ip->s - p);

				LM_DBG("~proto->len=%d [%.*s]\n", tmp_len, tmp_len, p);
			} else {
				LM_DBG("No alias proto in contact[%.*s]\n", contact->len,
						contact->s);
			}
		} else {
			LM_DBG("No alias port~proto in contact[%.*s]\n", contact->len,
					contact->s);
		}
	} else {
		LM_DBG("No alias in contact [%.*s]\n", contact->len, contact->s);
	}

	return 0;
}

/*!
 * \brief Match the aliases of two contacts - compare only ip and port portion, without proto
 * \param c1 contact string 1
 * \param c2 contact string 2
 * \return 1 on successfull match, 0 when they not match
 */
static int alias_port_ip_match(str *c1, str *c2)
{
	str ip_port1, ip_port2;
	extract_alias_ip_port(c1, &ip_port1);
	extract_alias_ip_port(c2, &ip_port2);
	LM_DBG("Matching contact alias ip and port - comparing [%.*s] and [%.*s]\n",
			ip_port1.len, ip_port1.s, ip_port2.len, ip_port2.s);
	if((ip_port1.len == ip_port2.len)
			&& !memcmp(ip_port1.s, ip_port2.s, ip_port1.len)) {
		return 1;
	}
	return 0;
}

static str subs_terminated = {"terminated", 10};
static str subs_active = {"active;expires=", 15};

/**
 * Creates notifications with the given content for all of the subscribers.
 * @param r - r_public* to which it refers
 * @param for_s - the r_subscriber*  to which it refers or NULL if for all
 * @param content - the body content
 * @param expires - the remaining subscription expiration time in seconds
 */
void create_notifications(udomain_t *_t, impurecord_t *r_passed,
		ucontact_t *c_passed, str *presentity_uri, str *watcher_contact,
		str *contact_uri, str *impus, int num_impus, int event_type,
		str *explit_dereg_contact, int num_explit_dereg_contact)
{

	reg_notification *n;
	reg_subscriber *s;
	impurecord_t *r;
	int local_cseq = 0;
	int version = 0;
	int create_notification;

	str subscription_state = {"active;expires=10000000000", 26},
		content_type = {"application/reginfo+xml", 23};

	int domain_locked = -1;

	get_act_time();

	int res;

	LM_DBG("Creating notification\n");

	if(r_passed && !presentity_uri && !watcher_contact) {
		LM_DBG("r_passed and c_passed are valid and presentity uri and "
			   "watcher_contact is 0 - this must be a ul callback no need to "
			   "lock domain\n");
		r = r_passed;

	} else {
		LM_DBG("This must be a cfg file subscribe need to lock domain and get "
			   "impurecord\n");
		ul.lock_udomain(_t, presentity_uri);
		res = ul.get_impurecord(_t, presentity_uri, &r);
		if(res != 0) {
			LM_WARN("No IMPU... ignoring\n");
			ul.unlock_udomain(_t, presentity_uri);
			return;
		}
		domain_locked = 1;
	}

	s = r->shead;
	while(s) {
		LM_DBG("Scrolling through reg subscribers for this IMPU [%.*s]\n",
				r->public_identity.len, r->public_identity.s);

		create_notification = 0;

		if((event_type != IMS_REGISTRAR_CONTACT_EXPIRED
				   && event_type != IMS_REGISTRAR_CONTACT_UNREGISTERED)
				&& s->expires > act_time) {
			subscription_state.s = (char *)pkg_malloc(32 * sizeof(char *));
			subscription_state.len = 0;
			if(subscription_state.s) {

				sprintf(subscription_state.s, "%.*s%" TIME_T_FMT,
						subs_active.len, subs_active.s,
						TIME_T_CAST(s->expires - act_time));
				subscription_state.len = strlen(subscription_state.s);
			}

			LM_DBG("Expires is greater than current time! Subscription state: "
				   "[%.*s]\n",
					subscription_state.len, subscription_state.s);

		} else {
			STR_PKG_DUP(subscription_state, subs_terminated, "pkg subs state");
			LM_DBG("Expires is past than current time or UE deregistered! "
				   "Subscription state: [%.*s]\n",
					subscription_state.len, subscription_state.s);
		}

		//This is a fix to ensure that when a user subscribes a full reg info is only sent to that UE
		if(event_type == IMS_REGISTRAR_SUBSCRIBE
				|| event_type == IMS_REGISTRAR_SUBSEQUENT_SUBSCRIBE) {
			if(contact_match(watcher_contact, &s->watcher_contact)
					&& (presentity_uri->len == s->presentity_uri.len)
					&& (memcmp(s->presentity_uri.s, presentity_uri->s,
								presentity_uri->len)
							== 0)) {
				LM_DBG("This is a fix to ensure that we only send full reg "
					   "info XML to the UE that just subscribed.\n");
				create_notification = 1;
			}
		} else if(event_type == IMS_REGISTRAR_CONTACT_REGISTERED
				  || event_type == IMS_REGISTRAR_CONTACT_REFRESHED) {
			if(contact_match(contact_uri, &s->watcher_contact)
					&& (r_passed->public_identity.len == s->presentity_uri.len)
					&& (memcmp(s->presentity_uri.s, r_passed->public_identity.s,
								r_passed->public_identity.len)
							== 0)) {
				LM_DBG("This is a fix to ensure that we only send full reg "
					   "info XML to the UE that just registered.\n");
				create_notification = 1;
			}
		} else if(event_type == IMS_REGISTRAR_CONTACT_EXPIRED
				  || event_type == IMS_REGISTRAR_CONTACT_UNREGISTERED) {
			if(!ue_unsubscribe_on_dereg
					&& contact_port_ip_match(&c_passed->c, &s->watcher_contact)
					&& alias_port_ip_match(&c_passed->c, &s->watcher_contact)
					&& r_passed->public_identity.len == s->presentity_uri.len
					&& memcmp(s->presentity_uri.s, r_passed->public_identity.s,
							   r_passed->public_identity.len)
							   == 0) {
				LM_DBG("This is an UNREGISTER/EXPIRE event for a UE that "
					   "subscribed to its own state that does not unsubscribe "
					   "to dereg - therefore no notification\n");

				if(r->linked_contacts.numcontacts < 2) {
					// marking the contact as Notify ready if there aren't other linked contacts to the current impu record
					ul.lock_contact_slot_i(c_passed->sl);
					if(c_passed->state == CONTACT_EXPIRE_PENDING_NOTIFY) {
						LM_DBG("Setting contact state from "
							   "CONTACT_EXPIRE_PENDING_NOTIFY to "
							   "CONTACT_NOTIFY_READY for contact [%.*s]\n",
								c_passed->c.len, c_passed->c.s);
						c_passed->state = CONTACT_NOTIFY_READY;
					}
					ul.unlock_contact_slot_i(c_passed->sl);
				}
			} else {
				create_notification = 1;
			}
		} else {
			create_notification = 1;
		}

		if(create_notification) {
			LM_DBG("About to make new notification! We always increment the "
				   "local cseq and version before we send a new "
				   "notification\n");

			local_cseq = s->local_cseq + 1;
			version = s->version + 1;
			ul.update_subscriber(r, &s, 0, &local_cseq, &version);

			n = new_notification(subscription_state, content_type, &impus,
					num_impus, s, &explit_dereg_contact,
					num_explit_dereg_contact);
			if(n) {
				n->_d = _t;
				LM_DBG("Notification exists - about to add it\n");
				add_notification(n);
			} else {
				LM_DBG("Notification does not exist\n");
			}
		}

		s = s->next;

		if(subscription_state.s) {
			pkg_free(subscription_state.s);
		}
	}

	if(domain_locked == 1) {
		ul.unlock_udomain(_t, presentity_uri);
	}

	return;
out_of_memory:
	return;
}

/*We currently only support certain unknown params to be sent in NOTIFY bodies
 This prevents having compatibility issues with UEs including non-standard params in contact header
 Supported params:
 */
static str param_q = {"q", 1};
static str param_video = {"video", 5};
static str param_expires = {"expires", 7};
static str param_sip_instance = {"+sip.instance", 13};
static str param_3gpp_smsip = {"+g.3gpp.smsip", 13};
static str param_3gpp_icsi_ref = {"+g.3gpp.icsi-ref", 16};

static int inline supported_param(str *param_name)
{

	if(strncasecmp(param_name->s, param_q.s, param_name->len) == 0) {
		return 0;
	} else if(strncasecmp(param_name->s, param_video.s, param_name->len) == 0) {
		return 0;
	} else if(strncasecmp(param_name->s, param_expires.s, param_name->len)
			  == 0) {
		return 0;
	} else if(strncasecmp(param_name->s, param_sip_instance.s, param_name->len)
			  == 0) {
		return 0;
	} else if(strncasecmp(param_name->s, param_3gpp_smsip.s, param_name->len)
			  == 0) {
		return 0;
	} else if(strncasecmp(param_name->s, param_3gpp_icsi_ref.s, param_name->len)
			  == 0) {
		return 0;
	} else {
		return -1;
	}
}

/** Maximum reginfo XML size */
#define MAX_REGINFO_SIZE 16384

static str xml_start = {"<?xml version=\"1.0\"?>\n", 22};

static str r_full = {"full", 4};
static str r_partial = {"partial", 7};
static str r_reginfo_s = {"<reginfo xmlns=\"urn:ietf:params:xml:ns:reginfo\" "
						  "version=\"%d\" state=\"%.*s\">\n",
		74};
static str r_reginfo_e = {"</reginfo>\n", 11};

static str r_active = {"active", 6};
static str r_terminated = {"terminated", 10};
static str registration_s = {
		"\t<registration aor=\"%.*s\" id=\"%p\" state=\"%.*s\">\n", 48};
static str registration_e = {"\t</registration>\n", 17};

//richard: we only use reg unreg refrsh and expire
static str r_registered = {"registered", 10};
static str r_refreshed = {"refreshed", 9};
static str r_expired = {"expired", 7};
static str r_unregistered = {"unregistered", 12};
static str contact_s = {"\t\t<contact id=\"%p\" state=\"%.*s\" event=\"%.*s\" "
						"expires=\"%d\">\n",
		59};
static str contact_s_q = {"\t\t<contact id=\"%p\" state=\"%.*s\" "
						  "event=\"%.*s\" expires=\"%d\" q=\"%.3f\">\n",
		69};
static str contact_s_params_with_body = {
		"\t\t<unknown-param name=\"%.*s\">\"%.*s\"</unknown-param>\n", 1};
/**NOTIFY XML needs < to be replaced by &lt; and > to be replaced by &gt;*/
/*For params that need to be fixed we pass in str removing first and last character and replace them with &lt; and &gt;**/
static str contact_s_params_with_body_fix = {
		"\t\t<unknown-param name=\"%.*s\">\"&lt;%.*s&gt;\"</unknown-param>\n",
		1};
static str contact_s_params_no_body = {
		"\t\t<unknown-param name=\"%.*s\"></unknown-param>\n", 1};
static str contact_e = {"\t\t</contact>\n", 13};

static str uri_s = {"\t\t\t<uri>", 8};
static str uri_e = {"</uri>\n", 7};

static void process_xml_for_explit_dereg_contact(
		str *buf, str *pad, str explit_dereg_contact)
{

	LM_DBG("Processing XML for explicit dereg contact address: <%.*s>\n",
			explit_dereg_contact.len, explit_dereg_contact.s);

	sprintf(pad->s, contact_s_q.s, 1, r_terminated.len, r_terminated.s,
			r_expired.len, r_expired.s, 0);

	pad->len = strlen(pad->s);
	STR_APPEND(*buf, *pad);
	STR_APPEND(*buf, uri_s);

	STR_APPEND(*buf, (explit_dereg_contact));
	STR_APPEND(*buf, uri_e);
	STR_APPEND(*buf, contact_e);
}

static void process_xml_for_contact(str *buf, str *pad, ucontact_t *ptr)
{
	int expires;
	param_t *param;

	if(ptr->state == CONTACT_DELETED)
		return;

	expires = ptr->expires - act_time;
	if(expires < 0 || (ptr->state == CONTACT_DELETE_PENDING)
			|| (ptr->state == CONTACT_EXPIRE_PENDING_NOTIFY)) {
		LM_WARN("Contact expires is negative - setting to 0\n");
		expires = 0;
	}
	if(expires == 0) {
		if(ptr->q != -1) {
			float q = (float)ptr->q / 1000;
			sprintf(pad->s, contact_s_q.s, ptr, r_terminated.len,
					r_terminated.s, r_expired.len, r_expired.s, expires, q);
		} else {
			sprintf(pad->s, contact_s_q.s, ptr, r_terminated.len,
					r_terminated.s, r_expired.len, r_expired.s, expires);
		}
	} else {
		if(ptr->q != -1) {
			float q = (float)ptr->q / 1000;
			sprintf(pad->s, contact_s_q.s, ptr, r_active.len, r_active.s,
					r_registered.len, r_registered.s, expires, q);
		} else {
			sprintf(pad->s, contact_s_q.s, ptr, r_active.len, r_active.s,
					r_registered.len, r_registered.s, expires);
		}
	}

	pad->len = strlen(pad->s);
	STR_APPEND(*buf, *pad);
	STR_APPEND(*buf, uri_s);

	LM_DBG("Appending contact address: <%.*s>\n", ptr->c.len, ptr->c.s);

	STR_APPEND(*buf, (ptr->c));
	STR_APPEND(*buf, uri_e);

	param = ptr->params;
	while(param) {
		if(supported_param(&param->name) != 0) {
			param = param->next;
			continue;
		}

		if(param->body.len > 0) {
			LM_DBG("This contact has params name: [%.*s] body [%.*s]\n",
					param->name.len, param->name.s, param->body.len,
					param->body.s);
			if(param->body.s[0] == '<'
					&& param->body.s[param->body.len - 1] == '>') {
				LM_DBG("This param body starts with '<' and ends with '>' we "
					   "will clean these for the NOTIFY XML with &lt; and "
					   "&gt;\n");
				sprintf(pad->s, contact_s_params_with_body_fix.s,
						param->name.len, param->name.s, param->body.len - 2,
						param->body.s + 1);
			} else {
				sprintf(pad->s, contact_s_params_with_body.s, param->name.len,
						param->name.s, param->body.len, param->body.s);
			}

			pad->len = strlen(pad->s);
			STR_APPEND(*buf, *pad);
		} else {
			LM_DBG("This contact has params name: [%.*s] \n", param->name.len,
					param->name.s);
			sprintf(pad->s, contact_s_params_no_body.s, param->name.len,
					param->name.s);
			pad->len = strlen(pad->s);
			STR_APPEND(*buf, *pad);
		}
		param = param->next;
	}
	STR_APPEND(*buf, contact_e);
}

/**
 * Creates the full reginfo XML.
 * @param pv - the r_public to create for
 * @param event_type - event type
 * @param subsExpires - subscription expiration
 * @returns the str with the XML content
 * if it is a new subscription we do things like subscribe to updates on IMPU, etc
 */
str generate_reginfo_full(udomain_t *_t, str *impu_list, int num_impus,
		str *explit_dereg_contact, str *watcher_contact,
		int num_explit_dereg_contact, unsigned int reginfo_version)
{
	str x = {0, 0};
	str buf, pad;
	char bufc[MAX_REGINFO_SIZE], padc[MAX_REGINFO_SIZE];
	impurecord_t *r;
	int i, k, res;
	ucontact_t *ptr;

	buf.s = bufc;
	buf.len = 0;
	pad.s = padc;
	pad.len = 0;
	get_act_time();
	//    int domain_locked = 1;
	int terminate_impu = 1;
	impu_contact_t *impucontact;

	unsigned int num_pending_notify_contacts = 0;
	unsigned int max_num_pending_notify_contacts = 0;
	ucontact_t **pending_notify_contacts = 0;

	LM_DBG("Getting reginfo_full\n");

	STR_APPEND(buf, xml_start);
	sprintf(pad.s, r_reginfo_s.s, reginfo_version, r_full.len, r_full.s);
	pad.len = strlen(pad.s);
	STR_APPEND(buf, pad);

	// reserve memory for all contacts for all impus
	for(i = 0; i < num_impus; i++) {
		ul.lock_udomain(_t, &impu_list[i]);

		res = ul.get_impurecord(_t, (&impu_list[i]), &r);
		if(res != 0) {
			LM_DBG("impu disappeared, ignoring it\n");
			ul.unlock_udomain(_t, &impu_list[i]);
			continue;
		}

		max_num_pending_notify_contacts += r->linked_contacts.numcontacts;

		ul.unlock_udomain(_t, &impu_list[i]);
	}

	pending_notify_contacts = (ucontact_t **)pkg_malloc(
			max_num_pending_notify_contacts * sizeof(ucontact_t **));
	if(!pending_notify_contacts) {
		LM_WARN("no more pkg mem trying to allocate [%lu] bytes\n",
				max_num_pending_notify_contacts * sizeof(ucontact_t **));
		max_num_pending_notify_contacts = 0;
	}

	for(i = 0; i < num_impus; i++) {
		LM_DBG("Scrolling through public identities, current one <%.*s>\n",
				impu_list[i].len, impu_list[i].s);
		//        if (primary_locked && strncasecmp(impu_list[i].s, primary_impu->s, impu_list[i].len) == 0) {
		//            LM_DBG("Don't need to lock this impu [%.*s] as it is a ul callback so already locked\n", impu_list[i].len, impu_list[i].s);
		//            domain_locked = 0;
		//        } else {
		//            LM_DBG("Need to lock this impu\n");
		//            ul.lock_udomain(_t, &impu_list[i]);
		//            domain_locked = 1;
		//        }

		ul.lock_udomain(_t, &impu_list[i]);

		res = ul.get_impurecord(_t, (&impu_list[i]), &r);
		if(res != 0) {
			LM_DBG("impu disappeared, ignoring it\n");
			//            if (domain_locked) {
			ul.unlock_udomain(_t, &impu_list[i]);
			//            }
			continue;
		}
		//        domain_locked = 1;

		LM_DBG("Retrieved IMPU record\n");

		terminate_impu = 1;

		impucontact = r->linked_contacts.head;
		while(impucontact) {
			ptr = impucontact->contact;
			if(VALID_CONTACT(ptr, act_time)) {
				LM_DBG("IMPU <%.*s> has another active contact <%.*s> so will "
					   "set its state to active\n",
						r->public_identity.len, r->public_identity.s,
						ptr->c.len, ptr->c.s);
				terminate_impu = 0;
				break;
			}
			impucontact = impucontact->next;
		}
		if(terminate_impu) {
			LM_DBG("IMPU reg state has no active contacts so putting in status "
				   "terminated\n");
			sprintf(pad.s, registration_s.s, r->public_identity.len,
					r->public_identity.s, r, r_terminated.len, r_terminated.s);
		} else {
			LM_DBG("IMPU has active contacts so putting in status active\n");
			sprintf(pad.s, registration_s.s, r->public_identity.len,
					r->public_identity.s, r, r_active.len, r_active.s);
		}

		pad.len = strlen(pad.s);
		STR_APPEND(buf, pad);

		LM_DBG("Scrolling through contact for this IMPU\n");
		//        if (contact && !domain_locked /* we're dealing with the primary impu most likely related to de-reg */) {
		//            LM_DBG("We're dealing with the primary IMPU here AND a contact was passed in - must have been an explicit dereg\n");
		//            process_xml_for_contact(&buf, &pad, contact); //we do this because in the unlink_contact_from_impu the contact has already gone so we pass it in as a param...
		//        }

		for(k = 0; k < num_explit_dereg_contact; k++) {
			LM_DBG("This must have been an explicit dereg - therefore we need "
				   "to include the de-reged contact(s) that have already been "
				   "unlinked\n");
			LM_DBG("Scrolling through list of provided "
				   "num_explit_dereg_contact: [%.*s]\n",
					explit_dereg_contact[k].len, explit_dereg_contact[k].s);
			process_xml_for_explit_dereg_contact(
					&buf, &pad, explit_dereg_contact[k]);
		}

		impucontact = r->linked_contacts.head;
		while(impucontact) {
			ptr = impucontact->contact;

			ul.lock_contact_slot_i(ptr->sl);

			process_xml_for_contact(&buf, &pad, ptr);

			if(ptr->state == CONTACT_EXPIRE_PENDING_NOTIFY
					&& num_pending_notify_contacts
							   < max_num_pending_notify_contacts) {
				pending_notify_contacts[num_pending_notify_contacts++] = ptr;
			}

			ul.unlock_contact_slot_i(ptr->sl);

			impucontact = impucontact->next;
		}

		STR_APPEND(buf, registration_e);

		//        if (domain_locked) {
		ul.unlock_udomain(_t, &impu_list[i]);
		//        }
	}

	// set all expire pending notify contacts to notify ready state
	for(i = 0; i < num_pending_notify_contacts; ++i) {
		ptr = pending_notify_contacts[i];
		ul.lock_contact_slot_i(ptr->sl);

		if(ptr->state == CONTACT_EXPIRE_PENDING_NOTIFY) {
			LM_DBG("Setting contact state from CONTACT_EXPIRE_PENDING_NOTIFY "
				   "to CONTACT_NOTIFY_READY for contact [%.*s]\n",
					ptr->c.len, ptr->c.s);
			ptr->state = CONTACT_NOTIFY_READY;
		}

		ul.unlock_contact_slot_i(ptr->sl);
	}

	if(pending_notify_contacts) {
		pkg_free(pending_notify_contacts);
	}

	STR_APPEND(buf, r_reginfo_e);

	x.s = pkg_malloc(buf.len + 1);
	if(x.s) {
		x.len = buf.len;
		memcpy(x.s, buf.s, buf.len);
		x.s[x.len] = 0;
	} else {
		LM_ERR("no more pkg memory\n");
	}

	LM_DBG("Returned full reg-info: [%.*s]\n", x.len, x.s);

	return x;
}

/**
 * Creates the partial reginfo XML.
 * @param pv - the r_public to create for
 * @param pc - the r_contact to create for
 * @param event_type - event type
 * @param subsExpires - subscription expiration
 * @returns the str with the XML content
 */

str get_reginfo_partial(impurecord_t *r, ucontact_t *c, int event_type,
		unsigned int reginfo_version)
{
	str x = {0, 0};
	str buf, pad;
	char bufc[MAX_REGINFO_SIZE], padc[MAX_REGINFO_SIZE];
	int expires = -1;
	int terminate_impu = 1;
	ucontact_t *c_tmp;
	str state, event;
	param_t *param;
	impu_contact_t *impurecord;

	buf.s = bufc;
	buf.len = 0;
	pad.s = padc;
	pad.len = 0;

	STR_APPEND(buf, xml_start);
	sprintf(pad.s, r_reginfo_s.s, reginfo_version, r_partial.len, r_partial.s);
	pad.len = strlen(pad.s);
	STR_APPEND(buf, pad);


	if(r) {
		expires = c->expires - act_time;
		if(expires < 0) {
			LM_WARN("Contact expires is negative - setting to 0\n");
			expires = 0;
		}

		if( //richard we only use expired and unregistered
				(event_type == IMS_REGISTRAR_CONTACT_EXPIRED
						|| event_type == IMS_REGISTRAR_CONTACT_UNREGISTERED
						|| event_type == IMS_REGISTRAR_CONTACT_DEREGISTERED)) {
			//check if impu record has any other active contacts - if not then set this to terminated - if so then keep this active
			//check if asserted is present in any of the path headers


			impurecord = r->linked_contacts.head;
			while(impurecord) {
				c_tmp = impurecord->contact;
				if((strncasecmp(c_tmp->c.s, c->c.s, c_tmp->c.len) != 0)
						&& ((c_tmp->expires - act_time) > 0)) {
					LM_DBG("IMPU <%.*s> has another active contact <%.*s> so "
						   "will set its state to active\n",
							r->public_identity.len, r->public_identity.s,
							c_tmp->c.len, c_tmp->c.s);
					terminate_impu = 0;
					break;
				}
				impurecord = impurecord->next;
			}
			if(terminate_impu)
				sprintf(pad.s, registration_s.s, r->public_identity.len,
						r->public_identity.s, r, r_terminated.len,
						r_terminated.s);
			else
				sprintf(pad.s, registration_s.s, r->public_identity.len,
						r->public_identity.s, r, r_active.len, r_active.s);
		} else {
			sprintf(pad.s, registration_s.s, r->public_identity.len,
					r->public_identity.s, r, r_active.len, r_active.s);
		}

		pad.len = strlen(pad.s);
		STR_APPEND(buf, pad);
		if(c) {
			switch(event_type) {

					//richard we only use registered and refreshed and expired and unregistered
				case IMS_REGISTRAR_CONTACT_REGISTERED:
					state = r_active;
					event = r_registered;
					break;
				case IMS_REGISTRAR_CONTACT_REFRESHED:
					state = r_active;
					event = r_refreshed;
					break;
				case IMS_REGISTRAR_CONTACT_EXPIRED:
					state = r_terminated;
					event = r_expired;
					expires = 0;
					break;
				case IMS_REGISTRAR_CONTACT_DEREGISTERED:
				case IMS_REGISTRAR_CONTACT_UNREGISTERED:
					state = r_terminated;
					event = r_unregistered;
					expires = 0;
					break;
				default:
					state = r_active;
					event = r_registered;
			}
			if(c->q != -1) {
				float q = (float)c->q / 1000;
				sprintf(pad.s, contact_s_q.s, c, state.len, state.s, event.len,
						event.s, expires, q);
			} else
				sprintf(pad.s, contact_s.s, c, state.len, state.s, event.len,
						event.s, expires);
			pad.len = strlen(pad.s);
			STR_APPEND(buf, pad);
			STR_APPEND(buf, uri_s);
			STR_APPEND(buf, (c->c));
			STR_APPEND(buf, uri_e);

			param = c->params;
			while(param && supported_param(&param->name) == 0) {

				if(param->body.len > 0) {
					LM_DBG("This contact has params name: [%.*s] body [%.*s]\n",
							param->name.len, param->name.s, param->body.len,
							param->body.s);
					if(param->body.s[0] == '<'
							&& param->body.s[param->body.len - 1] == '>') {
						LM_DBG("This param body starts with '<' and ends with "
							   "'>' we will clean these for the NOTIFY XML "
							   "with &lt; and &gt;\n");
						sprintf(pad.s, contact_s_params_with_body_fix.s,
								param->name.len, param->name.s,
								param->body.len - 2, param->body.s + 1);
					} else {
						sprintf(pad.s, contact_s_params_with_body.s,
								param->name.len, param->name.s, param->body.len,
								param->body.s);
					}

					pad.len = strlen(pad.s);
					STR_APPEND(buf, pad);
				} else {
					LM_DBG("This contact has params name: [%.*s] \n",
							param->name.len, param->name.s);
					sprintf(pad.s, contact_s_params_no_body.s, param->name.len,
							param->name.s);
					pad.len = strlen(pad.s);
					STR_APPEND(buf, pad);
				}
				param = param->next;
			}

			STR_APPEND(buf, contact_e);
			STR_APPEND(buf, registration_e);
		}
	}

	STR_APPEND(buf, r_reginfo_e);


	x.s = pkg_malloc(buf.len + 1);
	if(x.s) {
		x.len = buf.len;
		memcpy(x.s, buf.s, buf.len);
		x.s[x.len] = 0;
	}
	return x;
}

/**
 * Used on NOTIFY failure.
 */
int drop_subscription(struct sip_msg *msg, str *watcher_contact)
{
	str callid = {0};
	str ftag = {0};
	str ttag = {0};
	str presentity_uri = {0};
	impurecord_t *presentity_impurecord = 0;
	reg_subscriber *reg_subscriber = 0;
	struct udomain *domain = 0;

	if(msg == NULL) {
		LM_ERR("msg is null\n");
		goto error;
	}

	// TODO - initialize domain somehow from the transaction/dialog/etc state,
	// such that this will work for different domains.
	if(!domain) {
		ul.register_udomain("location", &domain);
	}

	callid = cscf_get_call_id(msg, 0);
	if(callid.len <= 0 || !callid.s) {
		LM_ERR("unable to get callid\n");
		goto error;
	}
	if(!cscf_get_from_tag(msg, &ftag)) {
		LM_ERR("Unable to get ftag\n");
		goto error;
	}
	if(!cscf_get_to_tag(msg, &ttag)) {
		LM_ERR("Unable to get ttag\n");
		goto error;
	}
	// ftag and totag are swapped, since NOTIFY goes in the other direction
	presentity_uri =
			ul.get_presentity_from_subscriber_dialog(&callid, &ftag, &ttag);
	if(presentity_uri.len == 0) {
		LM_ERR("Unable to get presentity uri from subscriber dialog with "
			   "callid <%.*s>, ttag <%.*s> and ftag <%.*s>\n",
				callid.len, callid.s, ttag.len, ttag.s, ftag.len, ftag.s);
		goto error;
	}
	ul.lock_udomain(domain, &presentity_uri);
	int res =
			ul.get_impurecord(domain, &presentity_uri, &presentity_impurecord);
	if(res != 0) {
		LM_INFO("usrloc does not have impurecord for presentity being "
				"notified - inconsistent state or already dropped\n");
		ul.unlock_udomain(domain, &presentity_uri);
		goto error;
	}
	LM_INFO("Dropping subscription for impurecord [%.*s] on notification "
			"failure\n",
			presentity_impurecord->public_identity.len,
			presentity_impurecord->public_identity.s);
	res = ul.get_subscriber(presentity_impurecord, &presentity_uri,
			watcher_contact, IMS_EVENT_REG, &reg_subscriber);
	if(res != 0) {
		LM_WARN("could not get subscriber\n");
		ul.unlock_udomain(domain, &presentity_uri);
		goto error;
	}
	ul.external_delete_subscriber(
			reg_subscriber, domain, 0 /*domain is already locked*/);
	ul.unlock_udomain(domain, &presentity_uri);

	// shm_malloc in get_presentity_from_subscriber_dialog
	if(presentity_uri.s)
		shm_free(presentity_uri.s);
	return 0;
error:
	// shm_malloc in get_presentity_from_subscriber_dialog
	if(presentity_uri.s)
		shm_free(presentity_uri.s);
	return -1;
}

/**
 * Callback for the UAC response to NOTIFY
 */
void uac_request_cb(struct cell *t, int type, struct tmcb_params *ps)
{
	str *watcher_contact = 0;
	if(ps->param)
		watcher_contact = (str *)(*(ps->param));

	LM_DBG("received NOTIFY reply type [%d] and code [%d]\n", type, ps->code);
	if(ps->code < 300) {
		goto done;
	}
	// https://datatracker.ietf.org/doc/html/rfc3265#section-3.2.2
	// Search fro Retry-After in ps->rpl
	int has_retry_after = 0;
	if(ps->rpl != NULL && ps->rpl->headers != NULL) {
		struct hdr_field *hf = ps->rpl->headers;
		while(hf) {
			if(hf->name.len == 11
					&& strncasecmp(hf->name.s, "Retry-After", 11) == 0) {
				has_retry_after = 1;
				break;
			}
			hf = hf->next;
		}
	}
	if(has_retry_after) {
		// TODO - save and obey Retry-After
		goto done;
	}
	LM_INFO("NOTIFY failed with code [%d] and no Retry-After - the "
			"subscription will be removed\n",
			ps->code);
	if(drop_subscription(ps->rpl, watcher_contact) != 0) {
		LM_ERR("Error dropping subscription\n");
	}
done:
	if(watcher_contact) {
		ims_str_free(*watcher_contact, shm);
		shm_free(watcher_contact);
		*(ps->param) = 0;
	}
}

static int free_tm_dlg(dlg_t *td)
{
	if(td) {
		if(td->route_set)
			free_rr(&td->route_set);
		pkg_free(td);
	}
	return 0;
}

/**
 * Creates a NOTIFY message and sends it
 * @param n - the r_notification to create the NOTIFY after
 */


void send_notification(reg_notification *n)
{
	str h = {0, 0};
	str content = {0, 0};
	uac_req_t uac_r;
	dlg_t *td = NULL;
	char bufc[MAX_REGINFO_SIZE];
	str buf;
	str *watcher_contact = 0;

	struct udomain *domain = (struct udomain *)n->_d;
	if(!domain) {
		ul.register_udomain("location", &domain);
	}

	LM_DBG("Have a notification to send for the following IMPUs using domain "
		   "[%.*s]\n",
			domain->name->len, domain->name->s);


	content = generate_reginfo_full(domain, n->impus, n->num_impus,
			n->explit_dereg_contact, &n->watcher_contact,
			n->num_explit_dereg_contact, n->reginfo_s_version);

	if(content.len > MAX_REGINFO_SIZE) {
		LM_ERR("content size (%d) exceeds MAX_REGINFO_SIZE (%d)!\n",
				content.len, MAX_REGINFO_SIZE);
		if(content.s) {
			pkg_free(content.s);
		}
		return;
	}

	memset(bufc, 0, sizeof(bufc));
	memcpy(bufc, content.s, content.len);

	buf.s = bufc;
	buf.len = strlen(bufc);

	str method = {"NOTIFY", 6};

	LM_DBG("Notification content: [%.*s]\n", buf.len, buf.s);
	LM_DBG("DBG:send_notification: NOTIFY about <%.*s>\n", n->watcher_uri.len,
			n->watcher_uri.s);

	h.len = 0;
	h.len += contact_hdr1.len + scscf_name_str.len + contact_hdr2.len;
	if(n->subscription_state.len)
		h.len += subss_hdr1.len + subss_hdr2.len + n->subscription_state.len;
	h.len += event_hdr.len;
	h.len += maxfwds_hdr.len;
	if(n->content_type.len)
		h.len += ctype_hdr1.len + ctype_hdr2.len + n->content_type.len;
	h.s = pkg_malloc(h.len);
	if(!h.s) {
		LM_ERR("ERR:send_notification: Error allocating %d bytes\n", h.len);
		h.len = 0;
	}


	//Add SCSCF name as contact address
	h.len = 0;
	STR_APPEND(h, contact_hdr1);
	STR_APPEND(h, scscf_name_str);
	STR_APPEND(h, contact_hdr2);

	STR_APPEND(h, event_hdr);
	STR_APPEND(h, maxfwds_hdr);
	if(n->subscription_state.len) {
		STR_APPEND(h, subss_hdr1);
		STR_APPEND(h, n->subscription_state);
		STR_APPEND(h, subss_hdr2);
	}
	if(n->content_type.len) {
		STR_APPEND(h, ctype_hdr1);
		STR_APPEND(h, n->content_type);
		STR_APPEND(h, ctype_hdr2);
	}
	watcher_contact = shm_malloc(sizeof(str));
	if(!watcher_contact)
		goto out_of_memory;
	STR_SHM_DUP(*watcher_contact, n->watcher_contact, "");

	/* construct the dlg_t structure */
	td = build_dlg_t_from_notification(n);
	if(td == NULL) {
		LM_ERR("while building dlg_t structure\n");
		goto done;
	}

	if(buf.len) {
		LM_DBG("Notification content exists - about to send notification with "
			   "subscription state: [%.*s] content_type: [%.*s] content: "
			   "[%.*s] : presentity_uri: [%.*s] watcher_uri: [%.*s]\n",
				n->subscription_state.len, n->subscription_state.s,
				n->content_type.len, n->content_type.s, buf.len, buf.s,
				n->presentity_uri.len, n->presentity_uri.s, n->watcher_uri.len,
				n->watcher_uri.s);

		set_uac_req(&uac_r, &method, &h, &buf, td, TMCB_LOCAL_COMPLETED,
				uac_request_cb, watcher_contact);
		tmb.t_request_within(&uac_r);
	} else {
		LM_DBG("o notification content - about to send notification with "
			   "subscription state: [%.*s] presentity_uri: [%.*s] watcher_uri: "
			   "[%.*s]\n",
				n->subscription_state.len, n->subscription_state.s,
				n->presentity_uri.len, n->presentity_uri.s, n->watcher_uri.len,
				n->watcher_uri.s);


		set_uac_req(&uac_r, &method, &h, 0, td, TMCB_LOCAL_COMPLETED,
				uac_request_cb, watcher_contact);
		tmb.t_request_within(&uac_r);
	}
	watcher_contact = 0;
done:
out_of_memory:
	ims_str_free(h, pkg);
	ims_str_free(content, pkg);
	if(watcher_contact) {
		ims_str_free(*watcher_contact, shm);
		shm_free(watcher_contact);
	}
	free_tm_dlg(td);
}

/**
 * Creates a notification based on the given parameters
 * @param req_uri - the Request-URI for the NOTIFY
 * @param uri - uri to send to
 * @param subscription_state - the Subscription-State header value
 * @param event - the event
 * @param content_type - content type
 * @param content - content
 * @param dialog - dialog to send on
 * @returns the r_notification or NULL on error
 */
reg_notification *new_notification(str subscription_state, str content_type,
		str **impus, int num_impus, reg_subscriber *r,
		str **explit_dereg_contact, int num_explit_dereg_contact)
{
	int i;
	reg_notification *n = 0;
	int len;
	char *p;

	len = sizeof(reg_notification) + r->call_id.len + r->from_tag.len
		  + r->to_tag.len + r->watcher_uri.len + r->watcher_contact.len
		  + r->record_route.len + r->sockinfo_str.len + r->presentity_uri.len
		  + subscription_state.len + content_type.len
		  + (num_impus) * sizeof(str)
		  + (num_explit_dereg_contact) * sizeof(str); // + buf.len;
	for(i = 0; i < num_impus; i++) {
		len += (*impus)[i].len;
	}

	for(i = 0; i < num_explit_dereg_contact; i++) {
		len += (*explit_dereg_contact)[i].len;
	}

	LM_DBG("Creating new notification\n");

	n = (reg_notification *)shm_malloc(len);
	if(n == 0) {
		LM_ERR("no more shm mem (%d)\n", len);
		return 0;
	}
	memset(n, 0, len);

	p = (char *)(n + 1);

	n->reginfo_s_version = r->version;

	n->local_cseq = r->local_cseq;

	n->call_id.s = p;
	n->call_id.len = r->call_id.len;
	memcpy(p, r->call_id.s, r->call_id.len);
	p += r->call_id.len;
	LM_DBG("call id: [%.*s]\n", n->call_id.len, n->call_id.s);

	n->from_tag.s = p;
	n->from_tag.len = r->from_tag.len;
	memcpy(p, r->from_tag.s, r->from_tag.len);
	p += r->from_tag.len;
	LM_DBG("from tag: [%.*s]\n", n->from_tag.len, n->from_tag.s);

	n->to_tag.s = p;
	n->to_tag.len = r->to_tag.len;
	memcpy(p, r->to_tag.s, r->to_tag.len);
	p += r->to_tag.len;
	LM_DBG("to tag: [%.*s]\n", n->to_tag.len, n->to_tag.s);

	n->watcher_uri.s = p;
	n->watcher_uri.len = r->watcher_uri.len;
	memcpy(p, r->watcher_uri.s, r->watcher_uri.len);
	p += r->watcher_uri.len;
	LM_DBG("watcher_uri: [%.*s]\n", n->watcher_uri.len, n->watcher_uri.s);

	n->watcher_contact.s = p;
	n->watcher_contact.len = r->watcher_contact.len;
	memcpy(p, r->watcher_contact.s, r->watcher_contact.len);
	p += r->watcher_contact.len;
	LM_DBG("watcher_contact: [%.*s]\n", n->watcher_contact.len,
			n->watcher_contact.s);

	n->record_route.s = p;
	n->record_route.len = r->record_route.len;
	memcpy(p, r->record_route.s, r->record_route.len);
	p += r->record_route.len;
	LM_DBG("record_route: [%.*s]\n", n->record_route.len, n->record_route.s);

	n->sockinfo_str.s = p;
	n->sockinfo_str.len = r->sockinfo_str.len;
	memcpy(p, r->sockinfo_str.s, r->sockinfo_str.len);
	p += r->sockinfo_str.len;
	LM_DBG("sockinfo_str: [%.*s]\n", n->sockinfo_str.len, n->sockinfo_str.s);

	n->presentity_uri.s = p;
	n->presentity_uri.len = r->presentity_uri.len;
	memcpy(p, r->presentity_uri.s, r->presentity_uri.len);
	p += r->presentity_uri.len;
	LM_DBG("presentity_uri: [%.*s]\n", n->presentity_uri.len,
			n->presentity_uri.s);

	n->subscription_state.s = p;
	n->subscription_state.len = subscription_state.len;
	memcpy(p, subscription_state.s, subscription_state.len);
	p += subscription_state.len;
	LM_DBG("Notification subscription state: [%.*s]\n",
			n->subscription_state.len, n->subscription_state.s);

	n->content_type.s = p;
	n->content_type.len = content_type.len;
	memcpy(p, content_type.s, content_type.len);
	p += content_type.len;
	LM_DBG("Notification content type: [%.*s]\n", n->content_type.len,
			n->content_type.s);

	n->impus = (str *)p;
	p += sizeof(str) * num_impus;

	for(i = 0; i < num_impus; i++) {
		n->impus[i].s = p;
		memcpy(p, (*impus)[i].s, (*impus)[i].len);
		n->impus[i].len = (*impus)[i].len;
		p += (*impus)[i].len;
	}
	n->num_impus = num_impus;

	if(num_explit_dereg_contact > 0) {
		n->explit_dereg_contact = (str *)p;
		p += sizeof(str) * num_explit_dereg_contact;

		for(i = 0; i < num_explit_dereg_contact; i++) {
			n->explit_dereg_contact[i].s = p;
			memcpy(p, (*explit_dereg_contact)[i].s,
					(*explit_dereg_contact)[i].len);
			n->explit_dereg_contact[i].len = (*explit_dereg_contact)[i].len;
			p += (*explit_dereg_contact)[i].len;
		}
		n->num_explit_dereg_contact = num_explit_dereg_contact;
	}

	if(p != (((char *)n) + len)) {
		LM_CRIT("buffer overflow\n");
		free_notification(n);
		return 0;
	}

	return n;
}

/**
 * Adds a notification to the list of notifications at the end (FIFO).
 * @param n - the notification to be added
 */
void add_notification(reg_notification *n)
{

	LM_DBG("Adding notification\n");
	if(!n) {
		LM_DBG("Notification does not exist\n");
		return;
	} else {
		LM_DBG("Notification exists\n");
	}

	if(max_notification_list_size > 0
			&& ((notification_list->size + 1) > max_notification_list_size)) {
		LM_WARN("Dropping notification, list too big [%d]\n",
				notification_list->size);
		return;
	}

	LM_DBG("Adding to notification list\n");
	lock_get(notification_list->lock);
	n->next = 0;
	n->prev = notification_list->tail;
	if(notification_list->tail)
		notification_list->tail->next = n;
	notification_list->tail = n;
	if(!notification_list->head)
		notification_list->head = n;
	notification_list->size++;
	if(notification_list_size_threshold > 0
			&& notification_list->size > notification_list_size_threshold) {
		LM_WARN("notification_list is size [%d] and has exceed "
				"notification_list_size_threshold of [%d]\n",
				notification_list->size, notification_list_size_threshold);
	}

	sem_release(notification_list->empty);
	lock_release(notification_list->lock);
}

/**
 * Pop a notification to the list of notifications from the top
 */
reg_notification *get_notification()
{
	reg_notification *n;

	lock_get(notification_list->lock);
	while(notification_list->head == 0) {
		lock_release(notification_list->lock);
		sem_get(notification_list->empty);
		lock_get(notification_list->lock);
	}

	n = notification_list->head;
	notification_list->head = n->next;

	if(n == notification_list->tail) { //list now empty
		notification_list->tail = 0;
	}
	n->next = 0; //make sure whoever gets this can't access our list
	notification_list->size--;
	lock_release(notification_list->lock);

	return n;
}

/**
 * This is the main event process for notifications to be sent
 */
void notification_event_process()
{

	reg_notification *n = 0;

	LM_DBG("Running notification_event_process\n");

	for(;;) {
		n = get_notification();
		LM_DBG("About to send notification\n");
		send_notification(n);
		LM_DBG("About to free notification\n");
		free_notification(n);
	}
}

/**
 * Frees up space taken by a notification
 * @param n - the notification to be freed
 */
void free_notification(reg_notification *n)
{
	if(n) {
		shm_free(n);
	}
}

dlg_t *build_dlg_t_from_notification(reg_notification *n)
{
	dlg_t *td = NULL;
	td = (dlg_t *)pkg_malloc(sizeof(dlg_t));
	if(td == NULL) {
		LM_ERR("Error ran out of package memory\n");
	}
	memset(td, 0, sizeof(dlg_t));

	LM_DBG("Building dlg_t structure\n");

	td->loc_seq.value = n->local_cseq;
	LM_DBG("local cseq %d\n", n->local_cseq);
	td->loc_seq.is_set = 1;

	td->id.call_id = n->call_id;
	LM_DBG("call id %.*s\n", n->call_id.len, n->call_id.s);

	td->id.rem_tag = n->from_tag;
	LM_DBG("ftag %.*s\n", n->from_tag.len, n->from_tag.s);


	td->id.loc_tag = n->to_tag;
	LM_DBG("ttag %.*s\n", n->to_tag.len, n->to_tag.s);


	td->loc_uri = n->presentity_uri;
	LM_DBG("loc uri %.*s\n", n->presentity_uri.len, n->presentity_uri.s);

	td->rem_target = n->watcher_contact;
	LM_DBG("rem target %.*s\n", n->watcher_contact.len, n->watcher_contact.s);

	td->rem_uri = n->watcher_uri;
	LM_DBG("rem uri %.*s\n", n->watcher_uri.len, n->watcher_uri.s);

	if(n->record_route.s && n->record_route.len) {
		if(parse_rr_body(n->record_route.s, n->record_route.len, &td->route_set)
				< 0) {
			LM_ERR("in function parse_rr_body\n");
			goto error;
		}
	}
	td->state = DLG_CONFIRMED;

	if(n->sockinfo_str.len) {
		int port, proto;
		str host;
		char *tmp;
		if((tmp = as_asciiz(&n->sockinfo_str)) == NULL) {
			LM_ERR("no pkg memory left\n");
			goto error;
		}
		if(parse_phostport(tmp, &host.s, &host.len, &port, &proto)) {
			LM_ERR("bad sockinfo string\n");
			pkg_free(tmp);
			goto error;
		}
		pkg_free(tmp);
		td->send_sock = grep_sock_info(
				&host, (unsigned short)port, (unsigned short)proto);
	}

	return td;

error:
	free_tm_dlg(td);
	return NULL;
}
