debug=3
memdbg=5
server_signature=0
sip_warning=0
check_via=yes;
dns=no;
rev_dns=no;

children=4;
tcp_children=4;
tcp_max_connections=2048;
port=5060

loadmodule "/home/kubartv/SER/lib/ser/modules/sl.so";
#loadmodule "/home/kubartv/SER/lib/ser/modules/maxfwd.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/tm.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/rr.so";
#loadmodule "/home/kubartv/SER/lib/ser/modules/xmlrpc.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/mysql.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/domain.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/uri_db.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/avp.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/avp_db.so";

loadmodule "/home/kubartv/SER/lib/ser/modules/usrloc.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/registrar.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/xprint.so";
loadmodule "/home/kubartv/SER/lib/ser/modules/eval.so";

loadmodule "/home/kubartv/SER/lib/ser/modules/gflags.so"

#modparam("maxfwd", "max_limit", 70);

modparam("usrloc", "db_mode", 1);
modparam("usrloc|avp_db", "db_url", "mysql://ser:heslo@127.0.0.1/ser")

modparam("avp_db", "attr_group", "id=dlg,flag=dialog_flag,table=dlg_attrs,key_column=dlg_id");

modparam("gflags", "load_global_attrs", 1);

avpflags dialog_flag;

route["create_dialog"] {
	# sets attributes needed by dialog (stored when processing reply)
	$dlg_caller = @contact;
	$dlg_caller_cseq = @cseq.num;
	$dlg_status = "new";
	$dlg_init_method = @cseq.method;
	$dir = "caller2callee";

	t_on_reply("dialog_creation_reply");
	return 1;
}

onreply_route["dialog_creation_reply"] {
	xplog("L_ERR", "dialog creation reply (%rs, %@cseq.method) [%@from.tag, %@to.tag]\n");

	$res = @msg.response.code;
	xplog("L_ERR", " ... response: %$res\n");
	if ((!@to.tag) || (@to.tag=="")) {
		# don't create dialog from response without to tag
		break;
	}
	if ($res < 101) {
		xplog("L_ERR", " ... I won't create dialog from 100 response.\n");
		break;
	}
	
	del_attr("$id"); # xlset_attr works strange when the attribute already exists
	xlset_attr("$id", "call-id:%@call_id caller_tag:%@from.tag callee_tag:%@to.tag");
	if ($res > 299) {
		xplog("L_ERR", " ... dialog terminated\n");
		remove_extra_attrs("dlg", "$id");
		break;
	}

	xplog("L_ERR", " ... creating dialog '%$id'\n");
	# generate dialog id

	lock_extra_attrs("dlg", "$id");

	# TODO: try to load the dialog (early dialog may exist)?

	# update dialog data

	if (($dlg_status == "new") && ($res < 200)) {
		# early response may arrive after final one
		$dlg_callee = @contact;
		$dlg_status = "early";
		xplog("L_ERR", " ... creating early dialog\n");
	}
	if ($res >= 200) {
		$dlg_callee = @contact;
		$dlg_status = "confirmed";
		$dlg_confirmed_at = @sys.now.local;
		xplog("L_ERR", " ... confirming dialog\n");
	}

	route("save_dialog");
}

route["save_dialog"] {
	if ($dlg_status == "destroyed") {
		xplog("L_ERR", " ... destroying dialog %$id\n");
		
		# use this if you want to delete destroyed dialogs:
		# remove_extra_attrs("dlg", "$id");

		# else if you want leave them in DB (with the time of termination)
		$dlg_destroyed_at = @sys.now.local;
		
		# set flag for attributes with name beggining with dlg_
		setavpflag("$f./^dlg_/", "dialog_flag");
		save_extra_attrs("dlg", "$id");
	}
	else {
		# set flag for attributes with name beggining with dlg_
		setavpflag("$f./^dlg_/", "dialog_flag");

		save_extra_attrs("dlg", "$id");
	}
	unlock_extra_attrs("dlg", "$id");
}

route["load_dialog_data"] {
	lock_extra_attrs("dlg", "$id");

	del_attr("$dlg_init_method"); # used as flag of succesful read of data

	# delete all used dlg attrs (because load_extra_attrs doesn't delete them itself before adding)
	del_attr("$dlg_init_method");
	del_attr("$dlg_caller");
	del_attr("$dlg_callee");
	del_attr("$dlg_caller_cseq");
	del_attr("$dlg_callee_cseq");
	del_attr("$dlg_status");

	load_extra_attrs("dlg", "$id");
	if (!$dlg_init_method) {
		# dialog was not loaded
		unlock_extra_attrs("dlg", "$id");
		return -1;
	}
	return 1;
}

route["load_dialog"] {
	# tries to load dialog according tags and callid
	
	# try to load dialog
	del_attr("$id"); # xlset_attr works strange when the attribute already exists
	xlset_attr("$id", "call-id:%@call_id caller_tag:%@from.tag callee_tag:%@to.tag");
	if (route("load_dialog_data")) { 
		$dir = "caller2callee";
		return 1; 
	}

	# try to load dialog in other direction
	del_attr("$id"); # xlset_attr works strange when the attribute already exists
	xlset_attr("$id", "call-id:%@call_id caller_tag:%@to.tag callee_tag:%@from.tag");
	if (route("load_dialog_data")) { 
		$dir = "callee2caller";
		return 1; 
	}
	
	$id = "error";

	return -1;
}

route["update_dialog_reply"] {
	if ((@cseq.method == "INVITE") || (@cseq.method == "UPDATE")) {
		# target refresh for INVITE dialogs

		if ($dir == "caller2calle") { # if request from caller
			$dlg_callee = @contact; # update callee's contact (response!)
		}
		else {
			$dlg_caller = @contact;
		}
	}
	if (@cseq.method=="BYE") {
		$dlg_status = "destroyed"; # will be removed in save_dialog
	}
}

route["update_dialog"] {
	if ((@cseq.method == "INVITE") || (@cseq.method == "UPDATE")) {
		# target refresh for INVITE dialogs

		if ($dir == "caller2calle") { # if request from caller
			$dlg_caller = @contact; # update caller's contact (request!)
		}
		else {
			$dlg_callee = @contact;
		}
	}

	if ($dir == "caller2callee") { # if request from caller
		# TODO: verify CSeq before modifying and return 500 if lower than last one
		$dlg_caller_cseq = @cseq.num;
	}
	else {
		# TODO: verify CSeq before modifying and return 500 if lower than last one
		$dlg_callee_cseq = @cseq.num;
	}

	if (method=="BYE") {
		$dlg_status = "pre-destroyed"; # to see that BYE already went through
	}
	return 1;
}

route["trace_dialog"] {
	xplog("L_ERR", " ... dialog '%$id'\n");
	xplog("L_ERR", "       -> initial method: %$dlg_init_method\n");
	xplog("L_ERR", "       -> request dir: %$dir\n");
	xplog("L_ERR", "       -> caller: %$dlg_caller\n");
	xplog("L_ERR", "       -> caller's CSeq: %$dlg_caller_cseq\n");
	xplog("L_ERR", "       -> callee: %$dlg_callee\n");
	xplog("L_ERR", "       -> callee's CSeq: %$dlg_callee_cseq\n");
	xplog("L_ERR", "       -> status: %$dlg_status\n");
	return 1;
}

onreply_route["dialog_reply"] {
	if ($id) {
		xplog("L_ERR", "In-dialog reply (%rs, %@cseq.method) [%@to.tag, %@from.tag]\n");
		if (!route("load_dialog")) {
			xplog("L_ERR", "Can't load dialog data\n");
		}
		else {
			route("update_dialog_reply");
			route("trace_dialog");
			route("save_dialog");
		}
	}
}

route {
	if (method=="SUBSCRIBE") {
		# here we support only INVITE/BYE dialogs
		sl_reply("400", "Unsupported");
		break;
	}
	
	if (!lookup_domain("$td", "@to.uri.host")) {
		sl_send_reply("404", "Domain Not Found");
		break;
	}
	
	if (!lookup_user("To")) {
		sl_send_reply("404", "Unknown user");
		break;
	}

	if (method=="REGISTER") {
		save("location");
		break;
	};
		
	xplog("L_ERR", "----> processing request %@cseq.method\n");
		
	if (!lookup_domain("$fd", "@from.uri.host")) {
		sl_send_reply("404", "From domain Not Found");
		break;
	}

	if (!lookup_user("From")) {
		sl_send_reply("404", "Unknown user in From");
		break;
	}

	if (method != "REGISTER") record_route();

	# dialog needs transactions
	if (!t_newtran()) {
		sl_send_reply("500", "Can not start transaction");
		drop;
	}

	# dialog creation/loading
	if (!@to.tag || (@to.tag == "")) {
		# initial request or non-dialog message
		if (method=="INVITE") {
			route("create_dialog");
			# we don't save the dialog here because AVPs will be set
			# when reply comes and the dialog will be stored then
		}
		else {
			xplog("L_ERR", "Non-dialog message: %@cseq\n");
		}
	}
	else {
		# message within dialog
		if (route("load_dialog")) {
			route("update_dialog");
			route("trace_dialog");
			route("save_dialog");
			t_on_reply("dialog_reply");
		}
		else {	
			xplog("L_ERR", "Message within unknown dialog: %@cseq, to_tag=%@to.tag from_tag=%@from.tag\n");
		}
	}

	if (loose_route()) {
		route(1);
		break;
	}

	if (!lookup("location")) {
		t_reply("404", "Not Found");
		break;
	};
	route(1);
}

route[1] 
{
	xplog("L_ERR", "<---- request %@cseq.method processed\n");
	# send it out now; use stateful forwarding as it works reliably
	# even for UDP2TCP
	if (!t_relay()) {
		sl_reply_error();
	};
}

