/*
 * rls module - resource list server
 *
 * Copyright (C) 2007 Voice Sistem S.R.L.
 *
 * 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>

#include "../../core/pt.h"
#include "../../lib/srdb1/db.h"
#include "../../core/sr_module.h"
#include "../../core/dprint.h"
#include "../../core/error.h"
#include "../../core/ut.h"
#include "../../core/timer_proc.h"
#include "../../core/hashes.h"
#include "../../core/mem/mem.h"
#include "../../core/mem/shm_mem.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"
#include "../../core/kemi.h"
#include "../../modules/tm/tm_load.h"
#include "../../modules/sl/sl.h"
#include "../presence/bind_presence.h"
#include "../presence/hash.h"
#include "../pua/pua_bind.h"
#include "../pua/pidf.h"
#include "../xcap_client/xcap_functions.h"
#include "rls.h"
#include "notify.h"
#include "resource_notify.h"
#include "api.h"
#include "subscribe.h"
#include "../../core/mod_fix.h"

MODULE_VERSION

#define P_TABLE_VERSION 1
#define W_TABLE_VERSION 3
#define X_TABLE_VERSION 4

/** database connection */
db1_con_t *rls_db = NULL;
db_func_t rls_dbf;
db1_con_t *rlpres_db = NULL;
db_func_t rlpres_dbf;
db1_con_t *rls_xcap_db = NULL;
db_func_t rls_xcap_dbf;

/** modules variables */
str rls_server_address = {0, 0};
int rls_expires_offset = 0;
int waitn_time = 5;
int rls_notifier_poll_rate = 10;
int rls_notifier_processes = 1;
str rlsubs_table = str_init("rls_watchers");
str rlpres_table = str_init("rls_presentity");
str rls_xcap_table = str_init("xcap");

int *rls_notifier_id = NULL;

str db_url = str_init(DEFAULT_DB_URL);
str xcap_db_url = str_init("");
str rlpres_db_url = str_init("");
int hash_size = 9;
shtable_t rls_table;
contains_event_t pres_contains_event;
search_event_t pres_search_event;
get_event_list_t pres_get_ev_list;
int clean_period = 100;
int rlpres_clean_period = -1;

/* Lock for rls_update_subs */
gen_lock_t *rls_update_subs_lock = NULL;


/* address and port(default: 80):"http://192.168.2.132:8000/xcap-root"*/
char *xcap_root;
unsigned int xcap_port = 8000;
int rls_restore_db_subs(void);
int rls_integrated_xcap_server = 0;

/** libxml api */
xmlDocGetNodeByName_t XMLDocGetNodeByName;
xmlNodeGetNodeByName_t XMLNodeGetNodeByName;
xmlNodeGetNodeContentByName_t XMLNodeGetNodeContentByName;
xmlNodeGetAttrContentByName_t XMLNodeGetAttrContentByName;

/* functions imported from presence to handle subscribe hash table */
extern shtable_t rls_new_shtable(int hash_size);
extern void rls_destroy_shtable(shtable_t htable, int hash_size);
extern int rls_insert_shtable(
		shtable_t htable, unsigned int hash_code, subs_t *subs);
extern subs_t *rls_search_shtable(shtable_t htable, str callid, str to_tag,
		str from_tag, unsigned int hash_code);
extern int rls_delete_shtable(
		shtable_t htable, unsigned int hash_code, subs_t *subs);
extern int rls_update_shtable(
		shtable_t htable, unsigned int hash_code, subs_t *subs, int type);
extern void rls_update_db_subs_timer(db1_con_t *db, db_func_t *dbf,
		shtable_t hash_table, int htable_size, int no_lock,
		handle_expired_func_t handle_expired_func);

static int rls_rpc_init(void);

new_shtable_t pres_new_shtable;
insert_shtable_t pres_insert_shtable;
search_shtable_t pres_search_shtable;
update_shtable_t pres_update_shtable;
delete_shtable_t pres_delete_shtable;
destroy_shtable_t pres_destroy_shtable;
mem_copy_subs_t pres_copy_subs;
update_db_subs_t pres_update_db_subs_timer;
extract_sdialog_info_t pres_extract_sdialog_info;
int rls_events = EVENT_PRESENCE;
int to_presence_code = 1;
int rls_max_expires = 7200;
int rls_reload_db_subs = 0;
int rls_max_notify_body_len = 0;
int dbmode = 0;

/* functions imported from xcap_client module */
xcapGetNewDoc_t xcap_GetNewDoc = 0;

/* functions imported from pua module*/
send_subscribe_t pua_send_subscribe;
get_record_id_t pua_get_record_id;
get_subs_list_t pua_get_subs_list;

/* TM bind */
struct tm_binds tmb;
/** SL API structure */
sl_api_t slb;

str str_rlsubs_did_col = str_init("rlsubs_did");
str str_resource_uri_col = str_init("resource_uri");
str str_updated_col = str_init("updated");
str str_auth_state_col = str_init("auth_state");
str str_reason_col = str_init("reason");
str str_content_type_col = str_init("content_type");
str str_presence_state_col = str_init("presence_state");
str str_expires_col = str_init("expires");
str str_presentity_uri_col = str_init("presentity_uri");
str str_event_col = str_init("event");
str str_event_id_col = str_init("event_id");
str str_to_user_col = str_init("to_user");
str str_to_domain_col = str_init("to_domain");
str str_from_user_col = str_init("from_user");
str str_from_domain_col = str_init("from_domain");
str str_watcher_username_col = str_init("watcher_username");
str str_watcher_domain_col = str_init("watcher_domain");
str str_callid_col = str_init("callid");
str str_to_tag_col = str_init("to_tag");
str str_from_tag_col = str_init("from_tag");
str str_local_cseq_col = str_init("local_cseq");
str str_remote_cseq_col = str_init("remote_cseq");
str str_record_route_col = str_init("record_route");
str str_socket_info_col = str_init("socket_info");
str str_contact_col = str_init("contact");
str str_local_contact_col = str_init("local_contact");
str str_version_col = str_init("version");
str str_status_col = str_init("status");
str str_username_col = str_init("username");
str str_domain_col = str_init("domain");
str str_doc_type_col = str_init("doc_type");
str str_etag_col = str_init("etag");
str str_doc_col = str_init("doc");
str str_doc_uri_col = str_init("doc_uri");

/* outbound proxy address */
str rls_outbound_proxy = {0, 0};

int rls_fetch_rows = 500;

int rls_disable_remote_presence = 0;
int rls_max_backend_subs = 0;

/** module functions */

static int mod_init(void);
static int child_init(int);
static void destroy(void);
int rlsubs_table_restore();
void rlsubs_table_update(unsigned int ticks, void *param);
int add_rls_event(modparam_t type, void *val);
int rls_update_subs(struct sip_msg *msg, char *puri, char *pevent);
int fixup_update_subs(void **param, int param_no);
int fixup_free_update_subs(void **param, int param_no);

/* clang-format off */
static cmd_export_t cmds[] = {
	{"rls_handle_subscribe", (cmd_function)w_rls_handle_subscribe0, 0, 0, 0, REQUEST_ROUTE},
	{"rls_handle_subscribe", (cmd_function)w_rls_handle_subscribe1, 1,
		fixup_spve_null, fixup_free_spve_null, REQUEST_ROUTE},
	{"rls_handle_notify", (cmd_function)w_rls_handle_notify, 0, 0, 0, REQUEST_ROUTE},
	{"rls_update_subs", (cmd_function)w_rls_update_subs, 2,
		fixup_update_subs, fixup_free_update_subs, ANY_ROUTE},
	{"bind_rls", (cmd_function)bind_rls, 1, 0, 0, 0},
	{0, 0, 0, 0, 0, 0}
};

static param_export_t params[] = {
	{"server_address", PARAM_STR, &rls_server_address},
	{"db_url", PARAM_STR, &db_url},
	{"rlpres_db_url", PARAM_STR, &rlpres_db_url},
	{"xcap_db_url", PARAM_STR, &xcap_db_url},
	{"rlsubs_table", PARAM_STR, &rlsubs_table},
	{"rlpres_table", PARAM_STR, &rlpres_table},
	{"xcap_table", PARAM_STR, &rls_xcap_table},
	{"waitn_time", PARAM_INT, &waitn_time},
	{"notifier_poll_rate", PARAM_INT, &rls_notifier_poll_rate},
	{"notifier_processes", PARAM_INT, &rls_notifier_processes},
	{"clean_period", PARAM_INT, &clean_period},
	{"rlpres_clean_period", PARAM_INT, &rlpres_clean_period},
	{"max_expires", PARAM_INT, &rls_max_expires},
	{"hash_size", PARAM_INT, &hash_size},
	{"integrated_xcap_server", PARAM_INT, &rls_integrated_xcap_server},
	{"to_presence_code", PARAM_INT, &to_presence_code},
	{"xcap_root", PARAM_STRING, &xcap_root},
	{"rls_event", PARAM_STRING | PARAM_USE_FUNC, (void *)add_rls_event},
	{"outbound_proxy", PARAM_STR, &rls_outbound_proxy},
	{"reload_db_subs", PARAM_INT, &rls_reload_db_subs},
	{"max_notify_body_length", PARAM_INT, &rls_max_notify_body_len},
	{"db_mode", PARAM_INT, &dbmode},
	{"expires_offset", PARAM_INT, &rls_expires_offset},
	{"fetch_rows", PARAM_INT, &rls_fetch_rows},
	{"disable_remote_presence", PARAM_INT, &rls_disable_remote_presence},
	{"max_backend_subs", PARAM_INT, &rls_max_backend_subs},
	{0, 0, 0}
};

/** module exports */
struct module_exports exports = {
	"rls",           /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* exported functions */
	params,          /* exported parameters */
	0,               /* RPC method exports */
	0,               /* exported pseudo-variables */
	0,               /* response handling function */
	mod_init,        /* module initialization function */
	child_init,      /* per-child init function */
	destroy          /* module destroy function */
};
/* clang-format on */

/**
 * init module function
 */
static int mod_init(void)
{
	bind_presence_t bind_presence;
	presence_api_t pres;
	bind_pua_t bind_pua;
	pua_api_t pua;
	bind_libxml_t bind_libxml;
	libxml_api_t libxml_api;
	bind_xcap_t bind_xcap;
	xcap_api_t xcap_api;
	char *sep;

	LM_DBG("start\n");

	if(rls_rpc_init() < 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}

	if(dbmode < RLS_DB_DEFAULT || dbmode > RLS_DB_ONLY) {
		LM_ERR("Invalid dbmode-set to default mode\n");
		dbmode = 0;
	}

	if(!rls_server_address.s || rls_server_address.len <= 0) {
		LM_ERR("server_address parameter not set in configuration file\n");
		return -1;
	}

	if(!rls_integrated_xcap_server && xcap_root == NULL) {
		LM_ERR("xcap_root parameter not set\n");
		return -1;
	}
	/* extract port if any */
	if(xcap_root) {
		sep = strchr(xcap_root, ':');
		if(sep) {
			char *sep2 = NULL;
			sep2 = strchr(sep + 1, ':');
			if(sep2)
				sep = sep2;

			str port_str;

			port_str.s = sep + 1;
			port_str.len = strlen(xcap_root) - (port_str.s - xcap_root);

			if(str2int(&port_str, &xcap_port) < 0) {
				LM_ERR("converting string to int [port]= %.*s\n", port_str.len,
						port_str.s);
				return -1;
			}
			if(xcap_port < 1 || xcap_port > 65535) {
				LM_ERR("wrong xcap server port\n");
				return -1;
			}
			*sep = '\0';
		}
	}

	/* bind the SL API */
	if(sl_load_api(&slb) != 0) {
		LM_ERR("cannot bind to SL API\n");
		return -1;
	}

	/* load all TM stuff */
	if(load_tm_api(&tmb) == -1) {
		LM_ERR("can't load tm functions\n");
		return -1;
	}
	bind_presence = (bind_presence_t)find_export("bind_presence", 1, 0);
	if(!bind_presence) {
		LM_ERR("Can't bind presence\n");
		return -1;
	}
	if(bind_presence(&pres) < 0) {
		LM_ERR("Can't bind presence\n");
		return -1;
	}
	pres_contains_event = pres.contains_event;
	pres_search_event = pres.search_event;
	pres_get_ev_list = pres.get_event_list;

	if(rls_expires_offset < 0) {
		LM_ERR("Negative expires_offset, defaulted to zero\n");
		rls_expires_offset = 0;
	}

	if(dbmode == RLS_DB_ONLY) {
		pres_new_shtable = rls_new_shtable;
		pres_destroy_shtable = rls_destroy_shtable;
		pres_insert_shtable = rls_insert_shtable;
		pres_delete_shtable = rls_delete_shtable;
		pres_update_shtable = rls_update_shtable;
		pres_search_shtable = rls_search_shtable;
		pres_update_db_subs_timer = rls_update_db_subs_timer;
	} else {
		pres_new_shtable = pres.new_shtable;
		pres_destroy_shtable = pres.destroy_shtable;
		pres_insert_shtable = pres.insert_shtable;
		pres_delete_shtable = pres.delete_shtable;
		pres_update_shtable = pres.update_shtable;
		pres_search_shtable = pres.search_shtable;
		pres_update_db_subs_timer = pres.update_db_subs_timer;
	}

	pres_copy_subs = pres.mem_copy_subs;
	pres_extract_sdialog_info = pres.extract_sdialog_info;

	if(!pres_contains_event || !pres_get_ev_list || !pres_new_shtable
			|| !pres_destroy_shtable || !pres_insert_shtable
			|| !pres_delete_shtable || !pres_update_shtable
			|| !pres_search_shtable || !pres_copy_subs
			|| !pres_extract_sdialog_info) {
		LM_ERR("importing functions from presence module\n");
		return -1;
	}

	LM_DBG("db_url=%s/%d/%p\n", ZSW(db_url.s), db_url.len, db_url.s);

	if(xcap_db_url.len == 0) {
		xcap_db_url.s = db_url.s;
		xcap_db_url.len = db_url.len;
	}

	LM_DBG("db_url=%s/%d/%p\n", ZSW(xcap_db_url.s), xcap_db_url.len,
			xcap_db_url.s);

	if(rlpres_db_url.len == 0) {
		rlpres_db_url.s = db_url.s;
		rlpres_db_url.len = db_url.len;
	}

	LM_DBG("db_url=%s/%d/%p\n", ZSW(rlpres_db_url.s), rlpres_db_url.len,
			rlpres_db_url.s);


	/* binding to mysql module  */

	if(db_bind_mod(&db_url, &rls_dbf)) {
		LM_ERR("Database module not found\n");
		return -1;
	}

	if(db_bind_mod(&rlpres_db_url, &rlpres_dbf)) {
		LM_ERR("Database module not found\n");
		return -1;
	}

	if(db_bind_mod(&xcap_db_url, &rls_xcap_dbf)) {
		LM_ERR("Database module not found\n");
		return -1;
	}

	if(!DB_CAPABILITY(rls_dbf, DB_CAP_ALL)) {
		LM_ERR("Database module does not implement all functions"
			   " needed by the module\n");
		return -1;
	}

	if(!DB_CAPABILITY(rlpres_dbf, DB_CAP_ALL)) {
		LM_ERR("Database module does not implement all functions"
			   " needed by the module\n");
		return -1;
	}

	if(!DB_CAPABILITY(rls_xcap_dbf, DB_CAP_ALL)) {
		LM_ERR("Database module does not implement all functions"
			   " needed by the module\n");
		return -1;
	}

	rls_db = rls_dbf.init(&db_url);
	if(!rls_db) {
		LM_ERR("while connecting database\n");
		return -1;
	}

	rlpres_db = rlpres_dbf.init(&rlpres_db_url);
	if(!rlpres_db) {
		LM_ERR("while connecting database\n");
		return -1;
	}

	rls_xcap_db = rls_xcap_dbf.init(&xcap_db_url);
	if(!rls_xcap_db) {
		LM_ERR("while connecting database\n");
		return -1;
	}

	/* verify table version */
	if(db_check_table_version(&rls_dbf, rls_db, &rlsubs_table, W_TABLE_VERSION)
			< 0) {
		DB_TABLE_VERSION_ERROR(rlsubs_table);
		goto dberror;
	}

	/* verify table version */
	if(db_check_table_version(
			   &rlpres_dbf, rlpres_db, &rlpres_table, P_TABLE_VERSION)
			< 0) {
		DB_TABLE_VERSION_ERROR(rlpres_table);
		goto dberror;
	}

	/* verify table version */
	if(db_check_table_version(
			   &rls_xcap_dbf, rls_xcap_db, &rls_xcap_table, X_TABLE_VERSION)
			< 0) {
		DB_TABLE_VERSION_ERROR(rls_xcap_table);
		goto dberror;
	}

	if(dbmode != RLS_DB_ONLY) {
		if(hash_size <= 1)
			hash_size = 512;
		else
			hash_size = 1 << hash_size;

		rls_table = pres_new_shtable(hash_size);
		if(rls_table == NULL) {
			LM_ERR("while creating new hash table\n");
			goto dberror;
		}
		if(rls_reload_db_subs != 0) {
			if(rls_restore_db_subs() < 0) {
				LM_ERR("while restoring rl watchers table\n");
				goto dberror;
			}
		}
	}

	if(rls_db)
		rls_dbf.close(rls_db);
	rls_db = NULL;

	if(rlpres_db)
		rlpres_dbf.close(rlpres_db);
	rlpres_db = NULL;

	if(rls_xcap_db)
		rls_xcap_dbf.close(rls_xcap_db);
	rls_xcap_db = NULL;

	if(waitn_time <= 0)
		waitn_time = 5;

	if(rls_notifier_poll_rate <= 0)
		rls_notifier_poll_rate = 10;

	if(rls_notifier_processes <= 0)
		rls_notifier_processes = 1;

	/* bind libxml wrapper functions */

	if((bind_libxml = (bind_libxml_t)find_export("bind_libxml_api", 1, 0))
			== NULL) {
		LM_ERR("can't import bind_libxml_api\n");
		return -1;
	}
	if(bind_libxml(&libxml_api) < 0) {
		LM_ERR("can not bind libxml api\n");
		return -1;
	}
	XMLNodeGetAttrContentByName = libxml_api.xmlNodeGetAttrContentByName;
	XMLDocGetNodeByName = libxml_api.xmlDocGetNodeByName;
	XMLNodeGetNodeByName = libxml_api.xmlNodeGetNodeByName;
	XMLNodeGetNodeContentByName = libxml_api.xmlNodeGetNodeContentByName;

	if(XMLNodeGetAttrContentByName == NULL || XMLDocGetNodeByName == NULL
			|| XMLNodeGetNodeByName == NULL
			|| XMLNodeGetNodeContentByName == NULL) {
		LM_ERR("libxml wrapper functions could not be bound\n");
		return -1;
	}

	/* bind pua */
	bind_pua = (bind_pua_t)find_export("bind_pua", 1, 0);
	if(!bind_pua) {
		LM_ERR("Can't bind pua\n");
		return -1;
	}

	if(bind_pua(&pua) < 0) {
		LM_ERR("mod_init Can't bind pua\n");
		return -1;
	}
	if(pua.send_subscribe == NULL) {
		LM_ERR("Could not import send_subscribe\n");
		return -1;
	}
	pua_send_subscribe = pua.send_subscribe;

	if(pua.get_record_id == NULL) {
		LM_ERR("Could not import get_record_id\n");
		return -1;
	}
	pua_get_record_id = pua.get_record_id;

	if(pua.get_subs_list == NULL) {
		LM_ERR("Could not import get_subs_list\n");
		return -1;
	}
	pua_get_subs_list = pua.get_subs_list;

	if(!rls_integrated_xcap_server) {
		/* bind xcap */
		bind_xcap = (bind_xcap_t)find_export("bind_xcap", 1, 0);
		if(!bind_xcap) {
			LM_ERR("Can't bind xcap_client\n");
			return -1;
		}

		if(bind_xcap(&xcap_api) < 0) {
			LM_ERR("Can't bind xcap\n");
			return -1;
		}
		xcap_GetNewDoc = xcap_api.getNewDoc;
		if(xcap_GetNewDoc == NULL) {
			LM_ERR("Can't import xcap_client functions\n");
			return -1;
		}
	}

	if(rlpres_clean_period < 0)
		rlpres_clean_period = clean_period;

	if(clean_period > 0)
		register_timer(rlsubs_table_update, 0, clean_period);

	if(rlpres_clean_period > 0)
		register_timer(rls_presentity_clean, 0, rlpres_clean_period);

	if(dbmode == RLS_DB_ONLY) {
		if((rls_notifier_id = shm_malloc(sizeof(int) * rls_notifier_processes))
				== NULL) {
			LM_ERR("allocating shared memory\n");
			return -1;
		}

		register_basic_timers(rls_notifier_processes);
	} else
		register_timer(timer_send_notify, 0, waitn_time);

	if((rls_update_subs_lock = lock_alloc()) == NULL) {
		LM_ERR("Failed to alloc rls_update_subs_lock\n");
		return -1;
	}
	if(lock_init(rls_update_subs_lock) == NULL) {
		LM_ERR("Failed to init rls_update_subs_lock\n");
		return -1;
	}

	return 0;

dberror:
	rls_dbf.close(rls_db);
	rls_db = NULL;
	rlpres_dbf.close(rlpres_db);
	rlpres_db = NULL;
	rls_xcap_dbf.close(rls_xcap_db);
	rls_xcap_db = NULL;
	return -1;
}

/**
 * Initialize children
 */
static int child_init(int rank)
{
	if(rank == PROC_INIT || rank == PROC_TCP_MAIN)
		return 0;

	if(rank == PROC_MAIN && dbmode == RLS_DB_ONLY) {
		int i;

		for(i = 0; i < rls_notifier_processes; i++) {
			char tmp[25];
			snprintf(tmp, 25, "RLS NOTIFIER %d", i);
			rls_notifier_id[i] = i;

			if(fork_basic_utimer(PROC_TIMER, tmp, 1, timer_send_notify,
					   &rls_notifier_id[i], 1000000 / rls_notifier_poll_rate)
					< 0) {
				LM_ERR("Failed to start RLS NOTIFIER %d\n", i);
				return -1;
			}
		}

		return 0;
	}

	LM_DBG("child [%d]  pid [%d]\n", rank, getpid());

	if(rls_dbf.init == 0) {
		LM_CRIT("database not bound\n");
		return -1;
	}
	/* In DB only mode do not pool the connections where possible. */
	if(dbmode == RLS_DB_ONLY && rls_dbf.init2)
		rls_db = rls_dbf.init2(&db_url, DB_POOLING_NONE);
	else
		rls_db = rls_dbf.init(&db_url);
	if(!rls_db) {
		LM_ERR("child %d: Error while connecting database\n", rank);
		return -1;
	} else {
		if(rls_dbf.use_table(rls_db, &rlsubs_table) < 0) {
			LM_ERR("child %d: Error in use_table rlsubs_table\n", rank);
			return -1;
		}

		LM_DBG("child %d: Database connection opened successfully\n", rank);
	}

	if(rlpres_dbf.init == 0) {
		LM_CRIT("database not bound\n");
		return -1;
	}
	/* In DB only mode do not pool the connections where possible. */
	if(dbmode == RLS_DB_ONLY && rlpres_dbf.init2)
		rlpres_db = rlpres_dbf.init2(&db_url, DB_POOLING_NONE);
	else
		rlpres_db = rlpres_dbf.init(&db_url);
	if(!rlpres_db) {
		LM_ERR("child %d: Error while connecting database\n", rank);
		return -1;
	} else {
		if(rlpres_dbf.use_table(rlpres_db, &rlpres_table) < 0) {
			LM_ERR("child %d: Error in use_table rlpres_table\n", rank);
			return -1;
		}

		LM_DBG("child %d: Database connection opened successfully\n", rank);
	}

	if(rls_xcap_dbf.init == 0) {
		LM_CRIT("database not bound\n");
		return -1;
	}
	rls_xcap_db = rls_xcap_dbf.init(&xcap_db_url);
	if(!rls_xcap_db) {
		LM_ERR("child %d: Error while connecting database\n", rank);
		return -1;
	} else {
		if(rls_xcap_dbf.use_table(rls_xcap_db, &rls_xcap_table) < 0) {
			LM_ERR("child %d: Error in use_table rls_xcap_table\n", rank);
			return -1;
		}

		LM_DBG("child %d: Database connection opened successfully\n", rank);
	}

	return 0;
}

/*
 * destroy function
 */
static void destroy(void)
{
	LM_DBG("start\n");

	if(rls_table) {
		if(rls_db)
			rlsubs_table_update(0, 0);
		pres_destroy_shtable(rls_table, hash_size);
	}
	if(rls_db && rls_dbf.close)
		rls_dbf.close(rls_db);
	if(rlpres_db && rlpres_dbf.close)
		rlpres_dbf.close(rlpres_db);
	if(rls_xcap_db && rls_xcap_dbf.close)
		rls_xcap_dbf.close(rls_xcap_db);

	if(rls_update_subs_lock != NULL) {
		lock_destroy(rls_update_subs_lock);
		lock_dealloc(rls_update_subs_lock);
	}

	if(rls_notifier_id != NULL)
		shm_free(rls_notifier_id);
}

int handle_expired_record(subs_t *s)
{
	int ret;
	int tmp;
	/* send NOTIFY with state terminated - make sure exires value is 0 */
	tmp = s->expires;
	s->expires = 0;
	ret = rls_send_notify(s, NULL, NULL, NULL);
	s->expires = tmp;
	if(ret < 0) {
		LM_ERR("in function send_notify\n");
		return -1;
	}

	return 0;
}

void rlsubs_table_update(unsigned int ticks, void *param)
{
	int no_lock = 0;

	if(dbmode == RLS_DB_ONLY) {
		delete_expired_subs_rlsdb();
		return;
	}

	if(ticks == 0 && param == NULL)
		no_lock = 1;

	if(rls_dbf.use_table(rls_db, &rlsubs_table) < 0) {
		LM_ERR("sql use table failed\n");
		return;
	}
	pres_update_db_subs_timer(rls_db, &rls_dbf, rls_table, hash_size, no_lock,
			handle_expired_record);
}

int rls_restore_db_subs(void)
{
	db_key_t result_cols[24];
	db1_res_t *res = NULL;
	db_row_t *row = NULL;
	db_val_t *row_vals = NULL;
	int i;
	int n_result_cols = 0;
	int pres_uri_col, expires_col, from_user_col, from_domain_col, to_user_col;
	int callid_col, totag_col, fromtag_col, to_domain_col, sockinfo_col,
			reason_col;
	int event_col, contact_col, record_route_col, event_id_col, status_col;
	int remote_cseq_col, local_cseq_col, local_contact_col, version_col;
	int watcher_user_col, watcher_domain_col;
	subs_t s;
	str ev_sname;
	pres_ev_t *event = NULL;
	event_t parsed_event;
	unsigned int expires;
	unsigned int hash_code;

	result_cols[pres_uri_col = n_result_cols++] = &str_presentity_uri_col;
	result_cols[expires_col = n_result_cols++] = &str_expires_col;
	result_cols[event_col = n_result_cols++] = &str_event_col;
	result_cols[event_id_col = n_result_cols++] = &str_event_id_col;
	result_cols[to_user_col = n_result_cols++] = &str_to_user_col;
	result_cols[to_domain_col = n_result_cols++] = &str_to_domain_col;
	result_cols[watcher_user_col = n_result_cols++] = &str_watcher_username_col;
	result_cols[watcher_domain_col = n_result_cols++] = &str_watcher_domain_col;
	result_cols[from_user_col = n_result_cols++] = &str_from_user_col;
	result_cols[from_domain_col = n_result_cols++] = &str_from_domain_col;
	result_cols[callid_col = n_result_cols++] = &str_callid_col;
	result_cols[totag_col = n_result_cols++] = &str_to_tag_col;
	result_cols[fromtag_col = n_result_cols++] = &str_from_tag_col;
	result_cols[local_cseq_col = n_result_cols++] = &str_local_cseq_col;
	result_cols[remote_cseq_col = n_result_cols++] = &str_remote_cseq_col;
	result_cols[record_route_col = n_result_cols++] = &str_record_route_col;
	result_cols[sockinfo_col = n_result_cols++] = &str_socket_info_col;
	result_cols[contact_col = n_result_cols++] = &str_contact_col;
	result_cols[local_contact_col = n_result_cols++] = &str_local_contact_col;
	result_cols[version_col = n_result_cols++] = &str_version_col;
	result_cols[status_col = n_result_cols++] = &str_status_col;
	result_cols[reason_col = n_result_cols++] = &str_reason_col;

	if(!rls_db) {
		LM_ERR("null database connection\n");
		return -1;
	}
	if(rls_dbf.use_table(rls_db, &rlsubs_table) < 0) {
		LM_ERR("in use table\n");
		return -1;
	}

	if(db_fetch_query(&rls_dbf, rls_fetch_rows, rls_db, 0, 0, 0, result_cols, 0,
			   n_result_cols, 0, &res)
			< 0) {
		LM_ERR("while querying table\n");
		if(res) {
			rls_dbf.free_result(rls_db, res);
			res = NULL;
		}
		return -1;
	}
	if(res == NULL)
		return -1;

	if(res->n <= 0) {
		LM_INFO("The query returned no result\n");
		rls_dbf.free_result(rls_db, res);
		res = NULL;
		return 0;
	}

	do {
		LM_DBG("found %d db entries\n", res->n);

		for(i = 0; i < res->n; i++) {
			row = &res->rows[i];
			row_vals = ROW_VALUES(row);
			memset(&s, 0, sizeof(subs_t));

			expires = row_vals[expires_col].val.int_val;

			if(expires < (int)time(NULL))
				continue;

			s.pres_uri.s = (char *)row_vals[pres_uri_col].val.string_val;
			s.pres_uri.len = strlen(s.pres_uri.s);

			s.to_user.s = (char *)row_vals[to_user_col].val.string_val;
			s.to_user.len = strlen(s.to_user.s);

			s.to_domain.s = (char *)row_vals[to_domain_col].val.string_val;
			s.to_domain.len = strlen(s.to_domain.s);

			s.from_user.s = (char *)row_vals[from_user_col].val.string_val;
			s.from_user.len = strlen(s.from_user.s);

			s.from_domain.s = (char *)row_vals[from_domain_col].val.string_val;
			s.from_domain.len = strlen(s.from_domain.s);

			s.watcher_user.s =
					(char *)row_vals[watcher_user_col].val.string_val;
			s.watcher_user.len = strlen(s.watcher_user.s);

			s.watcher_domain.s =
					(char *)row_vals[watcher_domain_col].val.string_val;
			s.watcher_domain.len = strlen(s.watcher_domain.s);


			s.to_tag.s = (char *)row_vals[totag_col].val.string_val;
			s.to_tag.len = strlen(s.to_tag.s);

			s.from_tag.s = (char *)row_vals[fromtag_col].val.string_val;
			s.from_tag.len = strlen(s.from_tag.s);

			s.callid.s = (char *)row_vals[callid_col].val.string_val;
			s.callid.len = strlen(s.callid.s);

			ev_sname.s = (char *)row_vals[event_col].val.string_val;
			ev_sname.len = strlen(ev_sname.s);

			event = pres_contains_event(&ev_sname, &parsed_event);
			if(event == NULL) {
				LM_ERR("event not found in list\n");
				goto error;
			}
			s.event = event;

			s.event_id.s = (char *)row_vals[event_id_col].val.string_val;
			if(s.event_id.s)
				s.event_id.len = strlen(s.event_id.s);

			s.remote_cseq = row_vals[remote_cseq_col].val.int_val;
			s.local_cseq = row_vals[local_cseq_col].val.int_val;
			s.version = row_vals[version_col].val.int_val;

			s.expires = expires - (int)time(NULL);
			s.status = row_vals[status_col].val.int_val;

			s.reason.s = (char *)row_vals[reason_col].val.string_val;
			if(s.reason.s)
				s.reason.len = strlen(s.reason.s);

			s.contact.s = (char *)row_vals[contact_col].val.string_val;
			s.contact.len = strlen(s.contact.s);

			s.local_contact.s =
					(char *)row_vals[local_contact_col].val.string_val;
			s.local_contact.len = strlen(s.local_contact.s);

			s.record_route.s =
					(char *)row_vals[record_route_col].val.string_val;
			if(s.record_route.s)
				s.record_route.len = strlen(s.record_route.s);

			s.sockinfo_str.s = (char *)row_vals[sockinfo_col].val.string_val;
			s.sockinfo_str.len = strlen(s.sockinfo_str.s);

			hash_code = core_hash(&s.pres_uri, &s.event->name, hash_size);
			if(pres_insert_shtable(rls_table, hash_code, &s) < 0) {
				LM_ERR("adding new record in hash table\n");
				goto error;
			}
		}
	} while((db_fetch_next(&rls_dbf, rls_fetch_rows, rls_db, &res) == 1)
			&& (RES_ROW_N(res) > 0));

	rls_dbf.free_result(rls_db, res);

	/* delete all records */
	if(rls_dbf.delete(rls_db, 0, 0, 0, 0) < 0) {
		LM_ERR("deleting all records from database table\n");
		return -1;
	}

	return 0;

error:
	rls_dbf.free_result(rls_db, res);
	return -1;
}

int add_rls_event(modparam_t type, void *val)
{
	char *event = (char *)val;
	event_t e;

	if(event_parser(event, strlen(event), &e) < 0) {
		LM_ERR("while parsing event = %s\n", event);
		return -1;
	}
	if(e.type & EVENT_OTHER) {
		LM_ERR("wrong event= %s\n", event);
		return -1;
	}

	rls_events |= e.type;

	return 0;
}

int bind_rls(struct rls_binds *pxb)
{
	if(pxb == NULL) {
		LM_WARN("bind_rls: Cannot load rls API into a NULL pointer\n");
		return -1;
	}

	pxb->rls_handle_subscribe = rls_handle_subscribe;
	pxb->rls_handle_subscribe0 = ki_rls_handle_subscribe;
	pxb->rls_handle_notify = w_rls_handle_notify;
	return 0;
}

static void rls_rpc_cleanup(rpc_t *rpc, void *ctx)
{
	LM_DBG("executing the clean up\n");

	(void)rlsubs_table_update(0, 0);
	(void)rls_presentity_clean(0, 0);
}

static const char *rls_rpc_cleanup_doc[2] = {"Trigger rls cleanup.", 0};

static void rls_rpc_update_subs(rpc_t *rpc, void *ctx)
{
	str uri = {0, 0};
	str event = {0, 0};
	int pn = 0;

	LM_DBG("executing update subs\n");
	pn = rpc->scan(ctx, "SS", &uri, &event);
	if(pn < 2) {
		rpc->fault(ctx, 500, "Not enough parameters");
		return;
	}

	if(uri.s == NULL || uri.len == 0) {
		LM_ERR("empty uri\n");
		rpc->fault(ctx, 500, "Empty URI");
		return;
	}

	if(event.s == NULL || event.len == 0) {
		LM_ERR("empty event parameter\n");
		rpc->fault(ctx, 500, "Empty event parameter");
		return;
	}
	LM_DBG("uri '%.*s' - event '%.*s'\n", uri.len, uri.s, event.len, event.s);

	if(ki_rls_update_subs(NULL, &uri, &event) < 0) {
		rpc->fault(ctx, 500, "Processing failure");
		return;
	}
}

static const char *rls_rpc_update_subs_doc[2] = {"Trigger rls cleanup.", 0};

rpc_export_t rls_rpc[] = {
		{"rls.cleanup", rls_rpc_cleanup, rls_rpc_cleanup_doc, 0},
		{"rls.update_subs", rls_rpc_update_subs, rls_rpc_update_subs_doc, 0},
		{0, 0, 0, 0}};

static int rls_rpc_init(void)
{
	if(rpc_register_array(rls_rpc) != 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}
	return 0;
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_rls_exports[] = {
	{ str_init("rls"), str_init("handle_subscribe"),
		SR_KEMIP_INT, ki_rls_handle_subscribe,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rls"), str_init("handle_subscribe_uri"),
		SR_KEMIP_INT, ki_rls_handle_subscribe_uri,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rls"), str_init("handle_notify"),
		SR_KEMIP_INT, ki_rls_handle_notify,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("rls"), str_init("update_subs"),
		SR_KEMIP_INT, ki_rls_update_subs,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},

	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
/* clang-format on */

int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_rls_exports);
	return 0;
}
