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