/*
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 *
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fruanhofer Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 *
 * NB: Alot of this code was originally part of OpenIMSCore,
 * FhG Fokus.
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "config.h"


#include <libxml/parser.h>
#include <stdio.h>
#include <string.h>

extern int errno;

static xmlValidCtxt	cvp;	/**< XML Validation context */

/**
 * Initializes the libxml parser
 * @returns 1 always
 */
static inline int parser_init()
{
	cvp.userData = (void*)stderr;
	cvp.error = (xmlValidityErrorFunc) fprintf;
	cvp.warning = (xmlValidityWarningFunc) fprintf;
	return 1;
}

/**
 * Destroys the parser
 */
static inline void parser_destroy()
{
	xmlCleanupParser();
}

/**
 * Trim the quotes from a string and duplicate it.
 * @param dest - destination for the untrimmed and duplicated string
 * @param src - source string
 */
static inline void quote_trim_dup(str *dest, char *src)
{
	int i=0;
	dest->s=0;
	dest->len=0;
	if (!src) return;
	dest->len = strlen(src);
	if (src[0]=='\"') {i++;dest->len--;}
	if (src[dest->len-1]=='\"') {dest->len--;}

	dest->s = shm_malloc(dest->len+1);
	if (!dest->s) {
		LOG_NO_MEM("shm",dest->len);
		dest->len=0;
		return;
	}
	memcpy(dest->s,src+i,dest->len);
	dest->s[dest->len]=0;
}

/**
 * Parse the cdp configuration from file to xml
 * @param filename
 * @return the xmlDocPtr or null on error
 */
xmlDocPtr parse_dp_config_file(char* filename)
{
	FILE *f=0;
	xmlDocPtr doc;

	parser_init();

	if (!filename){
		LM_ERR("ERROR:parse_dp_config_file(): filename parameter is null\n");
		goto error;
	}
	f = fopen(filename,"r");
	if (!f){
		LM_ERR("ERROR:parse_dp_config_file(): Error opening <%s> file > %s\n",filename,strerror(errno));
		goto error;
	}
	fclose(f);

	doc = xmlParseFile(filename);
	if (!doc){
		LM_ERR("parse_dp_config_file():  This is not a valid XML file <%s>\n",
				filename);
		goto error;
	}

	return doc;
error:
	return 0;
}

/**
 * Parse the cdp configuration from str to xml
 * @param filename
 * @return the xmlDocPtr or null on error
 */
xmlDocPtr parse_dp_config_str(str config_str)
{
	xmlDocPtr doc;

	char c = config_str.s[config_str.len];
	if (!config_str.len){
		LM_ERR("ERROR:parse_dp_config_str(): empty string\n");
		goto error;
	}
	parser_init();

	config_str.s[config_str.len] = 0;
	doc = xmlParseDoc((xmlChar*)config_str.s);
	config_str.s[config_str.len] = c;

	if (!doc){
		LM_ERR("parse_dp_config_file():  This is not a valid XML string <%.*s>\n",
				config_str.len,config_str.s);
		goto error;
	}

	return  doc;
error:
	return 0;
}

/**
 * Parses a DiameterPeer configuration file.
 * @param filename - path to the file
 * @returns the dp_config* structure containing the parsed configuration
 */
dp_config* parse_dp_config(xmlDocPtr doc)
{
	dp_config *x=0;
	xmlNodePtr root=0,child=0,nephew=0;
	xmlChar *xc=0;
	int k;
	routing_entry *re,*rei;
	routing_realm *rr,*rri;

	if (!doc)
		goto error;

	x = new_dp_config();

	root = xmlDocGetRootElement(doc);
	if (!root){
		LM_ERR("parse_dp_config():  Empty XML \n");
		goto error;
	}

	k = xmlStrlen(root->name);
	if (k>12) k = 12;
	if (strncasecmp((char*)root->name,"DiameterPeer",k)!=0){
		LM_ERR("parse_dp_config(): XML Root is not <DiameterPeer>\n");
		goto error;
	}

	xc = xmlGetProp(root,(xmlChar*)"FQDN");
	if (xc){
		quote_trim_dup(&(x->fqdn),(char*)xc);
		quote_trim_dup(&(x->identity),(char*)xc);
		xmlFree(xc);
	}

	xc = xmlGetProp(root,(xmlChar*)"Realm");
	if (xc){
		quote_trim_dup(&(x->realm),(char*)xc);
		xmlFree(xc);
	}

	xc = xmlGetProp(root,(xmlChar*)"Vendor_Id");
	if (xc) x->vendor_id = atoi((char*)xc);
	else x->vendor_id = 0;

	xc = xmlGetProp(root,(xmlChar*)"Product_Name");
	if (xc){
		quote_trim_dup(&(x->product_name),(char*)xc);
		xmlFree(xc);
	}

	xc = xmlGetProp(root,(xmlChar*)"AcceptUnknownPeers");
	if (xc) {x->accept_unknown_peers = atoi((char*)xc);xmlFree(xc);}
	else x->accept_unknown_peers = 1;

	xc = xmlGetProp(root,(xmlChar*)"DropUnknownOnDisconnect");
	if (xc) {x->drop_unknown_peers = atoi((char*)xc);xmlFree(xc);}
	else x->drop_unknown_peers = 1;

	xc = xmlGetProp(root,(xmlChar*)"Tc");
	if (xc) {x->tc = atoi((char*)xc);xmlFree(xc);}
	else x->tc = 30;

	xc = xmlGetProp(root,(xmlChar*)"Workers");
	if (xc) {x->workers = atoi((char*)xc);xmlFree(xc);}
	else x->workers = 4;

	xc = xmlGetProp(root,(xmlChar*)"QueueLength");
	if (xc) {x->queue_length = atoi((char*)xc);xmlFree(xc);}
	else x->queue_length = 32;

	xc = xmlGetProp(root,(xmlChar*)"ConnectTimeout");
	if (xc) {x->connect_timeout= atoi((char*)xc);xmlFree(xc);}
	else x->connect_timeout = 5;

	xc = xmlGetProp(root,(xmlChar*)"TransactionTimeout");
	if (xc) {x->transaction_timeout = atoi((char*)xc);xmlFree(xc);}
	else x->transaction_timeout = 5;

	xc = xmlGetProp(root,(xmlChar*)"SessionsHashSize");
	if (xc) {x->sessions_hash_size = atoi((char*)xc);xmlFree(xc);}
	else x->sessions_hash_size = 128;

	xc = xmlGetProp(root,(xmlChar*)"DefaultAuthSessionTimeout");
	if (xc) {x->default_auth_session_timeout = atoi((char*)xc);xmlFree(xc);}
	else x->default_auth_session_timeout = 60;

	xc = xmlGetProp(root,(xmlChar*)"MaxAuthSessionTimeout");
	if (xc) {x->max_auth_session_timeout = atoi((char*)xc);xmlFree(xc);}
	else x->max_auth_session_timeout = 300;

	for(child = root->children; child; child = child->next)
		if (child->type == XML_ELEMENT_NODE)
		{
			if (xmlStrlen(child->name)==4 && strncasecmp((char*)child->name,"Peer",4)==0){
				//PEER
				x->peers_cnt++;
			}
			else if (xmlStrlen(child->name)==8 && strncasecmp((char*)child->name,"Acceptor",8)==0){
				//Acceptor
				x->acceptors_cnt++;
			}
			else if (xmlStrlen(child->name)==4 && (strncasecmp((char*)child->name,"Auth",4)==0||
						strncasecmp((char*)child->name,"Acct",4)==0)){
				//Application
				x->applications_cnt++;
			}
			else if (xmlStrlen(child->name)==15 && (strncasecmp((char*)child->name,"SupportedVendor",15)==0)){
				//SupportedVendor
				x->supported_vendors_cnt++;
			}
		}
	x->peers = shm_malloc(x->peers_cnt*sizeof(peer_config));
	if (!x->peers){
		LOG_NO_MEM("shm",x->peers_cnt*sizeof(peer_config));
		goto error;
	}
	memset(x->peers,0,x->peers_cnt*sizeof(peer_config));
	x->peers_cnt=0;
	x->acceptors = shm_malloc(x->acceptors_cnt*sizeof(acceptor_config));
	if (!x->acceptors){
		LOG_NO_MEM("shm",x->acceptors_cnt*sizeof(acceptor_config));
		goto error;
	}
	memset(x->acceptors,0,x->acceptors_cnt*sizeof(acceptor_config));
	x->acceptors_cnt=0;
	x->applications = shm_malloc(x->applications_cnt*sizeof(app_config));
	if (!x->applications){
		LOG_NO_MEM("shm",x->applications_cnt*sizeof(app_config));
		goto error;
	}
	memset(x->applications,0,x->applications_cnt*sizeof(app_config));
	x->applications_cnt=0;

	x->supported_vendors = shm_malloc(x->supported_vendors_cnt*sizeof(int));
	if (!x->supported_vendors){
		LOG_NO_MEM("shm",x->supported_vendors_cnt*sizeof(int));
		goto error;
	}
	memset(x->supported_vendors,0,x->supported_vendors_cnt*sizeof(int));
	x->supported_vendors_cnt=0;

	for(child = root->children; child; child = child->next)
		if (child->type == XML_ELEMENT_NODE)
		{
			if (xmlStrlen(child->name)==4 && strncasecmp((char*)child->name,"Peer",4)==0){
				//PEER
				xc = xmlGetProp(child,(xmlChar*)"FQDN");
				if (xc){
					quote_trim_dup(&(x->peers[x->peers_cnt].fqdn),(char*)xc);
					xmlFree(xc);
				}
				xc = xmlGetProp(child,(xmlChar*)"Realm");
				if (xc){
					quote_trim_dup(&(x->peers[x->peers_cnt].realm),(char*)xc);
					xmlFree(xc);
				}
				xc = xmlGetProp(child,(xmlChar*)"port");
				if (xc){
					x->peers[x->peers_cnt].port = atoi((char*)xc);
					xmlFree(xc);
				}
				xc = xmlGetProp(child,(xmlChar*)"src_addr");
				if (xc){
					quote_trim_dup(&(x->peers[x->peers_cnt].src_addr),(char*)xc);
					xmlFree(xc);
				}
				x->peers_cnt++;
			}
			else if (xmlStrlen(child->name)==8 && strncasecmp((char*)child->name,"Acceptor",8)==0){
				//Acceptor
				xc = xmlGetProp(child,(xmlChar*)"bind");
				if (xc){
					quote_trim_dup(&(x->acceptors[x->acceptors_cnt].bind),(char*)xc);
					xmlFree(xc);
				}
				xc = xmlGetProp(child,(xmlChar*)"port");
				if (xc){
					x->acceptors[x->acceptors_cnt].port = atoi((char*)xc);
					xmlFree(xc);
				}
				x->acceptors_cnt++;
			}
			else if (xmlStrlen(child->name)==4 && (strncasecmp((char*)child->name,"Auth",4)==0||
						strncasecmp((char*)child->name,"Acct",4)==0)){
				//Application
				xc = xmlGetProp(child,(xmlChar*)"id");
				if (xc){
					x->applications[x->applications_cnt].id = atoi((char*)xc);
					xmlFree(xc);
				}
				xc = xmlGetProp(child,(xmlChar*)"vendor");
				if (xc){
					x->applications[x->applications_cnt].vendor = atoi((char*)xc);
					xmlFree(xc);
				}
				if (child->name[1]=='u'||child->name[1]=='U')
					x->applications[x->applications_cnt].type = DP_AUTHORIZATION;
				else
					x->applications[x->applications_cnt].type = DP_ACCOUNTING;
				x->applications_cnt++;
			}
			else if (xmlStrlen(child->name)==15 && (strncasecmp((char*)child->name,"SupportedVendor",15)==0)){
				//SupportedVendor
				xc = xmlGetProp(child,(xmlChar*)"vendor");
				if (xc){
					x->supported_vendors[x->supported_vendors_cnt] = atoi((char*)xc);
					xmlFree(xc);
				}
				x->supported_vendors_cnt++;
			}
			else if (xmlStrlen(child->name)==12 && (strncasecmp((char*)child->name,"DefaultRoute",12)==0)){
				if (!x->r_table) {
					x->r_table = shm_malloc(sizeof(routing_table));
					memset(x->r_table,0,sizeof(routing_table));
				}
				re = new_routing_entry();
				if (re){
					xc = xmlGetProp(child,(xmlChar*)"FQDN");
					if (xc){
						quote_trim_dup(&(re->fqdn),(char*)xc);
						xmlFree(xc);
					}
					xc = xmlGetProp(child,(xmlChar*)"metric");
					if (xc){
						re->metric = atoi((char*)xc);
						xmlFree(xc);
					}

					/* add it the list in ascending order */
					if (! x->r_table->routes || re->metric <= x->r_table->routes->metric){
						re->next = x->r_table->routes;
						x->r_table->routes = re;
					}else{
						for(rei=x->r_table->routes;rei;rei=rei->next)
							if (!rei->next){
								rei->next = re;
								break;
							}else{
								if (re->metric <= rei->next->metric){
									re->next = rei->next;
									rei->next = re;
									break;
								}
							}
					}
				}
			}
			else if (xmlStrlen(child->name)==5 && (strncasecmp((char*)child->name,"Realm",5)==0)){
				if (!x->r_table) {
					x->r_table = shm_malloc(sizeof(routing_table));
					memset(x->r_table,0,sizeof(routing_table));
				}
				rr = new_routing_realm();
				if (rr){
					xc = xmlGetProp(child,(xmlChar*)"name");
					quote_trim_dup(&(rr->realm),(char*)xc);

					if (!x->r_table->realms) {
						x->r_table->realms = rr;
					}else{
						for(rri=x->r_table->realms;rri->next;rri=rri->next);
						rri->next = rr;
					}
					for(nephew = child->children; nephew; nephew = nephew->next)
						if (nephew->type == XML_ELEMENT_NODE){
							if (xmlStrlen(nephew->name)==5 && (strncasecmp((char*)nephew->name,"Route",5)==0))
							{
								re = new_routing_entry();
								if (re) {
									xc = xmlGetProp(nephew,(xmlChar*)"FQDN");
									if (xc){
										quote_trim_dup(&(re->fqdn),(char*)xc);
										xmlFree(xc);
									}
									xc = xmlGetProp(nephew,(xmlChar*)"metric");
									if (xc){
										re->metric = atoi((char*)xc);
										xmlFree(xc);
									}
									/* add it the list in ascending order */
									if (! rr->routes || re->metric <= rr->routes->metric){
										re->next = rr->routes;
										rr->routes = re;
									}else{
										for(rei=rr->routes;rei;rei=rei->next)
											if (!rei->next){
												rei->next = re;
												break;
											}else{
												if (re->metric <= rei->next->metric){
													re->next = rei->next;
													rei->next = re;
													break;
												}
											}
									}
								}
							}
						}
				}
			}
		}

	if (doc) xmlFreeDoc(doc);
	parser_destroy();
	return x;
error:
	if (doc) xmlFreeDoc(doc);
	parser_destroy();
	if (x) free_dp_config(x);
	return 0;
}

