changeset 285:0daf6fc2b751

Added a test case for the app_acct extension
author Sebastien Decugis <sdecugis@nict.go.jp>
date Fri, 30 Apr 2010 17:55:16 +0900
parents 397cdcd41f53
children 43cdf237bb8c
files extensions/app_acct/CMakeLists.txt extensions/app_acct/acct_db.c extensions/app_acct/acct_records.c extensions/app_acct/app_acct.c extensions/app_acct/app_acct.h freeDiameter/extensions.c freeDiameter/fD.h freeDiameter/main.c freeDiameter/tests/CMakeLists.txt freeDiameter/tests/testappacct.c freeDiameter/tests/tests.h
diffstat 11 files changed, 579 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/extensions/app_acct/CMakeLists.txt	Wed Apr 28 18:54:08 2010 +0900
+++ b/extensions/app_acct/CMakeLists.txt	Fri Apr 30 17:55:16 2010 +0900
@@ -16,11 +16,13 @@
 SET( APP_ACCT_SRC
 	app_acct.h
 	app_acct.c
+	acct_db.c
+	acct_records.c
+)
+SET( APP_ACCT_SRC_GEN
 	lex.acct_conf.c
 	acct_conf.tab.c
 	acct_conf.tab.h
-	acct_db.c
-	acct_records.c
 )
 
 # Compile as a module
--- a/extensions/app_acct/acct_db.c	Wed Apr 28 18:54:08 2010 +0900
+++ b/extensions/app_acct/acct_db.c	Fri Apr 30 17:55:16 2010 +0900
@@ -50,8 +50,12 @@
 };
 
 static const char * stmt = "acct_db_stmt";
-static PGconn *conn = NULL;
+#ifndef TEST_DEBUG
+static 
+#endif /* TEST_DEBUG */
+PGconn *conn = NULL;
 
+/* Initialize the database context: connection to the DB, prepared statement to insert new records */
 int acct_db_init(void)
 {
 	struct acct_record_list emptyrecords;
@@ -62,7 +66,7 @@
 	size_t p;
 	int idx = 0;
 	PGresult * res;
-	#define REALLOC_SIZE	1024
+	#define REALLOC_SIZE	1024	/* We extend the buffer by this amount */
 	
 	TRACE_ENTRY();
 	CHECK_PARAMS( acct_config && acct_config->conninfo && acct_config->tablename ); 
@@ -75,9 +79,14 @@
 		fd_log_debug("Connection to database failed: %s\n", PQerrorMessage(conn));
 		acct_db_free();
 		return EINVAL;
-	} else {
-		TRACE_DEBUG(INFO, "Connection to database successfull: user:%s, db:%s, host:%s.", PQuser(conn), PQdb(conn), PQhost(conn));
 	}
+	if (PQprotocolVersion(conn) < 3) {
+		fd_log_debug("Database protocol version is too old, version 3 is required for prepared statements.\n");
+		acct_db_free();
+		return EINVAL;
+	}
+	
+	TRACE_DEBUG(FULL, "Connection to database successful, server version %d.", PQserverVersion(conn));
 	
 	/* Now, prepare the request object */
 	
@@ -173,21 +182,105 @@
         }
 	PQclear(res);
 	
+	free(sql);
+	acct_rec_empty(&emptyrecords);
 	
-
+	/* Ok, ready */
 	return 0;
 }
 
+/* Terminate the connection to the DB */
 void acct_db_free(void)
-{
-	if (conn)
+{	
+	if (conn) {
+		/* Note: the prepared statement is automatically freed when the session terminates */
 		PQfinish(conn);
-	conn = NULL;
+		conn = NULL;
+	}
 }
 
+/* When a new message has been received, insert the content of the parsed mapping into the DB (using prepared statement) */
 int acct_db_insert(struct acct_record_list * records)
 {
-	return ENOTSUP;
+	char 	**val;
+	int	 *val_len;
+	int 	 *val_isbin;
+	int	  idx = 0;
+	PGresult *res;
+	struct fd_list *li;
+	
+	TRACE_ENTRY("%p", records);
+	CHECK_PARAMS( conn && records );
+	
+	/* First, check if the connection with the DB has not staled, and eventually try to fix it */
+	if (PQstatus(conn) != CONNECTION_OK) {
+		/* Attempt a reset */
+		PQreset(conn);
+		if (PQstatus(conn) != CONNECTION_OK) {
+			TRACE_DEBUG(INFO, "Lost connection to the database server, and attempt to reestablish it failed");
+			TODO("Terminate the freeDiameter instance completly?");
+			return ENOTCONN;
+		}
+	}
+	
+	/* Alloc the arrays of parameters */
+	CHECK_MALLOC( val       = calloc(records->nball, sizeof(const char *)) );
+	CHECK_MALLOC( val_len   = calloc(records->nball, sizeof(const int)) );
+	CHECK_MALLOC( val_isbin = calloc(records->nball, sizeof(const int)) );
+	
+	/* Now write all the map'd records in these arrays */
+	for (li = records->all.next; li != &records->all; li = li->next) {
+		struct acct_record_item * r = (struct acct_record_item *)(li->o);
+		if (r->value) {
+			val_isbin[idx] = 1; /* We always pass binary parameters */
+			switch (r->param->avptype) {
+				case AVP_TYPE_OCTETSTRING:
+					val[idx] = (void *)(r->value->os.data);
+					val_len[idx] = r->value->os.len;
+					break;
+					
+				case AVP_TYPE_INTEGER32:
+				case AVP_TYPE_UNSIGNED32:
+				case AVP_TYPE_FLOAT32:
+					r->scalar.v32 = htonl(r->value->u32);
+					val[idx] = &r->scalar.c;
+					val_len[idx] = sizeof(uint32_t);
+					break;
+					
+				case AVP_TYPE_INTEGER64:
+				case AVP_TYPE_UNSIGNED64:
+				case AVP_TYPE_FLOAT64:
+					r->scalar.v64 = htonll(r->value->u64);
+					val[idx] = &r->scalar.c;
+					val_len[idx] = sizeof(uint64_t);
+					break;
+				
+				default:
+					ASSERT(0); /* detect bugs */
+			}
+		}
+		
+		idx++;
+	}
+	
+	/* OK, now execute the SQL statement */
+	res = PQexecPrepared(conn, stmt, records->nball, (const char * const *)val, val_len, val_isbin, 1 /* We actually don't care here */);
+	
+	/* Done with the parameters */
+	free(val);
+	free(val_len);
+	free(val_isbin);
+	
+	/* Now check the result code */
+	if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+		TRACE_DEBUG(INFO, "An error occurred while INSERTing in the database: %s", PQerrorMessage(conn));
+		PQclear(res);
+		return EINVAL; /* It was probably a mistake in configuration file... */
+        }
+	PQclear(res);
+	
+	/* Ok, we are done */
+	return 0;
 }
 
 
--- a/extensions/app_acct/acct_records.c	Wed Apr 28 18:54:08 2010 +0900
+++ b/extensions/app_acct/acct_records.c	Fri Apr 30 17:55:16 2010 +0900
@@ -73,11 +73,89 @@
 	return 0;
 }
 
+/* Find the AVPs from configuration inside a received message */
 int acct_rec_map(struct acct_record_list * records, struct msg * msg)
 {
+	struct avp * avp;
+	
 	TRACE_ENTRY("%p %p", records, msg);
 	
 	/* For each AVP in the message, search if we have a corresponding unmap'd record */
+	CHECK_FCT(  fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL)  );
+	while (avp) {
+		struct fd_list * li;
+		struct dict_object * model;
+		
+		CHECK_FCT( fd_msg_model(avp, &model) );
+		if (model != NULL) {	/* we ignore the AVPs we don't recognize */
+		
+			/* Search this model in the list */
+			for (li = records->unmaped.next; li != &records->unmaped; li = li->next) {
+				struct acct_record_item * r = (struct acct_record_item *)(li->o);
+				if (r->param->avpobj == model) {
+					/* It matches: save the AVP value and unlink this record from the unmap'd list */
+					struct avp_hdr * h;
+					CHECK_FCT( fd_msg_avp_hdr( avp, &h ) );
+					r->value = h->avp_value;
+					fd_list_unlink(&r->unmapd);
+					records->nbunmap -= 1;
+					break;
+				}
+			}
+
+			/* Continue only while there are some AVPs to map */
+			if (FD_IS_LIST_EMPTY(&records->unmaped))
+				break;
+		}
+		
+		/* Go to next AVP in the message */
+		CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
+	}
 	
-	return ENOTSUP;
+	/* Done */
+	return 0;
 }
+
+/* Check that a mapped list is not empty and no required AVP is missing. Free the record list in case of error */
+int acct_rec_validate(struct acct_record_list * records)
+{
+	struct fd_list * li;
+	TRACE_ENTRY("%p", records);
+	CHECK_PARAMS( records );
+	
+	/* Check at least one AVP was mapped */
+	if (records->nball == records->nbunmap) {
+		fd_log_debug("The received ACR does not contain any AVP from the configuration file.\n"
+				"This is an invalid situation. Please fix your configuration file.\n"
+				"One way to ensure this does not happen is to include Session-Id in the database.\n");
+		acct_rec_empty(records);
+		return EINVAL;
+	}
+	
+	/* Now, check there is no required AVP unmap'd */
+	for (li = records->unmaped.next; li != &records->unmaped; li = li->next) {
+		struct acct_record_item * r = (struct acct_record_item *)(li->o);
+		if (r->param->required && (r->index <= 1)) {
+			fd_log_debug("The received ACR does not contain the required AVP '%s'.\n", r->param->avpname);
+			acct_rec_empty(records);
+			return EINVAL;
+		}
+	}
+	
+	/* The record list is OK */
+	return 0;
+}
+
+/* Free all the items in an acct_record_list returned by acct_rec_prepare */
+void acct_rec_empty(struct acct_record_list * records)
+{
+	TRACE_ENTRY("%p", records);
+	CHECK_PARAMS_DO( records, return );
+	
+	while (!FD_IS_LIST_EMPTY(&records->all)) {
+		struct acct_record_item * r = (struct acct_record_item *)(records->all.next);
+		fd_list_unlink( &r->chain );
+		fd_list_unlink( &r->unmapd );
+		free(r);
+	}
+}
--- a/extensions/app_acct/app_acct.c	Wed Apr 28 18:54:08 2010 +0900
+++ b/extensions/app_acct/app_acct.c	Fri Apr 30 17:55:16 2010 +0900
@@ -37,51 +37,76 @@
 
 #include "app_acct.h"
 
-/* Default callback for the Accounting application. */
-static int acct_fallback( struct msg ** msg, struct avp * avp, struct session * sess, enum disp_action * act)
-{
-	/* This CB should never be called */
-	TRACE_ENTRY("%p %p %p %p", msg, avp, sess, act);
-	
-	fd_log_debug("Unexpected message received!\n");
-	
-	return ENOTSUP;
-}
+/* Mandatory AVPs for the Accounting-Answer */
+static struct {
+	struct dict_object * Accounting_Record_Number;
+	struct dict_object * Accounting_Record_Type;
+} acct_dict;
 
 
 /* Callback for incoming Base Accounting Accounting-Request messages */
 static int acct_cb( struct msg ** msg, struct avp * avp, struct session * sess, enum disp_action * act)
 {
 	struct msg_hdr *hdr = NULL;
-	struct msg *ans, *qry;
+	struct msg * m;
 	struct avp * a = NULL;
-	struct avp_hdr * h = NULL;
+	struct avp_hdr * art=NULL, *arn=NULL; /* We keep a pointer on the Accounting-Record-{Type, Number} AVPs from the query */
 	char * s;
+	struct acct_record_list rl;
 	
 	TRACE_ENTRY("%p %p %p %p", msg, avp, sess, act);
 	if (msg == NULL)
 		return EINVAL;
 	
-	qry = *msg;
-	/* Create the answer message, including the Session-Id AVP */
+	m = *msg;
+	
+	/* Prepare a new record list */
+	CHECK_FCT( acct_rec_prepare( &rl ) );
+	
+	/* Maps the AVPs from the query with this record list */
+	CHECK_FCT( acct_rec_map( &rl, m ) );
+	
+	/* Check that at least one AVP was mapped */
+	CHECK_FCT( acct_rec_validate( &rl ) );
+	
+	/* Now, save these mapped AVPs in the database */
+	CHECK_FCT( acct_db_insert( &rl ) );
+	
+	acct_rec_empty( &rl );
+	
+	/* OK, we can send a positive reply now */
+	
+	/* Get Accounting-Record-{Number,Type} values */
+	CHECK_FCT( fd_msg_search_avp ( m, acct_dict.Accounting_Record_Type, &a) );
+	if (a) {
+		CHECK_FCT( fd_msg_avp_hdr( a, &art )  );
+	}
+	CHECK_FCT( fd_msg_search_avp ( m, acct_dict.Accounting_Record_Number, &a) );
+	if (a) {
+		CHECK_FCT( fd_msg_avp_hdr( a, &arn )  );
+	}
+	
+	/* Create the answer message */
 	CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, msg, 0 ) );
-	ans = *msg;
+	m = *msg;
 
 	/* Set the Origin-Host, Origin-Realm, Result-Code AVPs */
-	CHECK_FCT( fd_msg_rescode_set( ans, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
-
-	fd_log_debug("--------------Received the following Accounting message:--------------\n");
-
-	CHECK_FCT( fd_sess_getsid ( sess, &s ) );
-	fd_log_debug("Session: %s\n", s);
-
-	/* We may also dump other data from the message, such as Accounting session Id, number of packets, ...  */
-
-	fd_log_debug("----------------------------------------------------------------------\n");
-
+	CHECK_FCT( fd_msg_rescode_set( m, "DIAMETER_SUCCESS", NULL, NULL, 1 ) );
+	
+	/* Add the mandatory AVPs in the ACA */
+	if (art) {
+		CHECK_FCT( fd_msg_avp_new ( acct_dict.Accounting_Record_Type, 0, &a ) );
+		CHECK_FCT( fd_msg_avp_setvalue( a, art->avp_value ) );
+		CHECK_FCT( fd_msg_avp_add( m, MSG_BRW_LAST_CHILD, a ) );
+	}
+	if (arn) {
+		CHECK_FCT( fd_msg_avp_new ( acct_dict.Accounting_Record_Number, 0, &a ) );
+		CHECK_FCT( fd_msg_avp_setvalue( a, arn->avp_value ) );
+		CHECK_FCT( fd_msg_avp_add( m, MSG_BRW_LAST_CHILD, a ) );
+	}
+	
 	/* Send the answer */
-	CHECK_FCT( fd_msg_send( msg, NULL, NULL ) );
-		
+	*act = DISP_ACT_SEND;
 	return 0;
 }
 	
@@ -93,18 +118,23 @@
 	
 	TRACE_ENTRY("%p", conffile);
 	
+#ifndef TEST_DEBUG /* We do this differently in the test scenario */
 	/* Initialize the configuration and parse the file */
 	CHECK_FCT( acct_conf_init() );
 	CHECK_FCT( acct_conf_parse(conffile) );
 	CHECK_FCT( acct_conf_check(conffile) );
+#endif /* TEST_DEBUG */
 	
 	/* Now initialize the database module */
 	CHECK_FCT( acct_db_init() );
 	
+	/* Search the AVPs we will need in this file */
+	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Number", &acct_dict.Accounting_Record_Number, ENOENT) );
+	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Type", &acct_dict.Accounting_Record_Type, ENOENT) );
+	
 	/* Register the dispatch callbacks */
 	memset(&data, 0, sizeof(data));
 	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_APPLICATION, APPLICATION_BY_NAME, "Diameter Base Accounting", &data.app, ENOENT) );
-	CHECK_FCT( fd_disp_register( acct_fallback, DISP_HOW_APPID, &data, NULL ) );
 	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Accounting-Request", &data.command, ENOENT) );
 	CHECK_FCT( fd_disp_register( acct_cb, DISP_HOW_CC, &data, NULL ) );
 	
--- a/extensions/app_acct/app_acct.h	Wed Apr 28 18:54:08 2010 +0900
+++ b/extensions/app_acct/app_acct.h	Fri Apr 30 17:55:16 2010 +0900
@@ -83,6 +83,11 @@
 	struct acct_conf_avp 	*param;	/* the AVP entry this refers to. */
 	unsigned		 index;	/* in case of multi */
 	union avp_value		*value; /* If the AVP was found in the message, this points to its value. Otherwise, NULL */
+	union {
+		uint32_t v32	/* Storage area for network byte-order copy of the AVP value */;
+		uint64_t v64;
+		char	 c;	/* pointer that is passed to the database */
+	} 			 scalar;/* for scalar AVP (all types except OCTETSTRING) we copy in this area the value in network byte order */
 };
 
 /* The sentinel for a list of acct_record_items */
@@ -111,3 +116,5 @@
 /* In acct_records.c */
 int acct_rec_prepare(struct acct_record_list * records);
 int acct_rec_map(struct acct_record_list * records, struct msg * msg);
+int acct_rec_validate(struct acct_record_list * records);
+void acct_rec_empty(struct acct_record_list * records);
--- a/freeDiameter/extensions.c	Wed Apr 28 18:54:08 2010 +0900
+++ b/freeDiameter/extensions.c	Fri Apr 30 17:55:16 2010 +0900
@@ -152,7 +152,7 @@
 }
 
 /* Now unload the extensions and free the memory */
-int fd_ext_fini( void ) 
+int fd_ext_term( void ) 
 {
 	TRACE_ENTRY();
 	
--- a/freeDiameter/fD.h	Wed Apr 28 18:54:08 2010 +0900
+++ b/freeDiameter/fD.h	Fri Apr 30 17:55:16 2010 +0900
@@ -83,7 +83,7 @@
 int fd_ext_add( char * filename, char * conffile );
 int fd_ext_load();
 void fd_ext_dump(void);
-int fd_ext_fini(void);
+int fd_ext_term(void);
 
 /* Messages */
 int fd_msg_init(void);
--- a/freeDiameter/main.c	Wed Apr 28 18:54:08 2010 +0900
+++ b/freeDiameter/main.c	Fri Apr 30 17:55:16 2010 +0900
@@ -164,7 +164,7 @@
 	CHECK_FCT_DO( fd_peer_fini(), /* Stop all connections */ );
 	CHECK_FCT_DO( fd_rtdisp_fini(), /* Stop routing threads and destroy routing queues */ );
 	
-	CHECK_FCT_DO( fd_ext_fini(), /* Cleanup all extensions */ );
+	CHECK_FCT_DO( fd_ext_term(), /* Cleanup all extensions */ );
 	CHECK_FCT_DO( fd_rtdisp_cleanup(), /* destroy remaining handlers */ );
 	
 	GNUTLS_TRACE( gnutls_global_deinit() );
--- a/freeDiameter/tests/CMakeLists.txt	Wed Apr 28 18:54:08 2010 +0900
+++ b/freeDiameter/tests/CMakeLists.txt	Fri Apr 30 17:55:16 2010 +0900
@@ -49,11 +49,59 @@
 # Create an archive with the daemon common files (all but main)
 ADD_LIBRARY(fDcore STATIC ${TEST_COMMON_SRC})
 
+##############################
+# App_acct test
+
+IF(BUILD_APP_ACCT)
+	OPTION(TEST_APP_ACCT "Test app_acct extension? (Requires a configured database, see testappacct.c for details)" OFF)
+	IF(TEST_APP_ACCT)
+	
+		OPTION(TEST_APP_ACCT_CONNINFO "The connection string to the database")
+		IF(TEST_APP_ACCT_CONNINFO)
+			ADD_DEFINITIONS(-DTEST_CONNINFO="${TEST_APP_ACCT_CONNINFO}")
+		ENDIF(TEST_APP_ACCT_CONNINFO)
+	
+		SET(TEST_LIST ${TEST_LIST} testappacct)
+
+		# Extension dependencies
+		FIND_PACKAGE(PostgreSQL REQUIRED)
+		INCLUDE_DIRECTORIES(${POSTGRESQL_INCLUDE_DIR})
+		SET(testappacct_ADDITIONAL_LIB ${POSTGRESQL_LIBRARIES})
+
+		# List of source files, copied from the extension CMakeLists.
+		SET( APP_ACCT_SRC
+			app_acct.h
+			app_acct.c
+			acct_db.c
+			acct_records.c
+		)
+		SET( APP_ACCT_SRC_GEN
+			lex.acct_conf.c
+			acct_conf.tab.c
+			acct_conf.tab.h
+		)
+
+		# The extension headers
+		INCLUDE_DIRECTORIES( "../../extensions/app_acct" )
+
+		SET(testappacct_ADDITIONAL "")
+
+		FOREACH( SRC_FILE ${APP_ACCT_SRC})
+		   SET(testappacct_ADDITIONAL ${testappacct_ADDITIONAL} "../../extensions/app_acct/${SRC_FILE}")
+		ENDFOREACH(SRC_FILE)
+
+		FOREACH( SRC_FILE ${APP_ACCT_SRC_GEN})
+		   SET(testappacct_ADDITIONAL ${testappacct_ADDITIONAL} "${CMAKE_CURRENT_BINARY_DIR}/../../extensions/app_acct/${SRC_FILE}")
+		ENDFOREACH(SRC_FILE)
+
+	ENDIF(TEST_APP_ACCT)
+ENDIF(BUILD_APP_ACCT)
+
 
 #############################
 # Compile each test
 FOREACH( TEST ${TEST_LIST} )
-   ADD_EXECUTABLE(${TEST} ${TEST}.c tests.h)
-   TARGET_LINK_LIBRARIES(${TEST} fDcore ${FD_LIBS})
+   ADD_EXECUTABLE(${TEST} ${TEST}.c tests.h ${${TEST}_ADDITIONAL})
+   TARGET_LINK_LIBRARIES(${TEST} fDcore ${FD_LIBS} ${${TEST}_ADDITIONAL_LIB})
    ADD_TEST(${TEST} ${EXECUTABLE_OUTPUT_PATH}/${TEST})
 ENDFOREACH( TEST )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/freeDiameter/tests/testappacct.c	Fri Apr 30 17:55:16 2010 +0900
@@ -0,0 +1,265 @@
+/*********************************************************************************************************
+* Software License Agreement (BSD License)                                                               *
+* Author: Sebastien Decugis <sdecugis@nict.go.jp>							 *
+*													 *
+* Copyright (c) 2009, WIDE Project and NICT								 *
+* All rights reserved.											 *
+* 													 *
+* Redistribution and use of this software in source and binary forms, with or without modification, are  *
+* permitted provided that the following conditions are met:						 *
+* 													 *
+* * Redistributions of source code must retain the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer.										 *
+*    													 *
+* * Redistributions in binary form must reproduce the above 						 *
+*   copyright notice, this list of conditions and the 							 *
+*   following disclaimer in the documentation and/or other						 *
+*   materials provided with the distribution.								 *
+* 													 *
+* * Neither the name of the WIDE Project or NICT nor the 						 *
+*   names of its contributors may be used to endorse or 						 *
+*   promote products derived from this software without 						 *
+*   specific prior written permission of WIDE Project and 						 *
+*   NICT.												 *
+* 													 *
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
+* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
+* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 	 *
+* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 	 *
+* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF   *
+* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.								 *
+*********************************************************************************************************/
+
+#include "tests.h"
+
+/* The connection string to the database */
+#ifndef TEST_CONNINFO
+#error "Please specify the conninfo information"
+#endif /* TEST_CONNINFO */
+
+/* The table used for tests. This table will receive the following instructions:
+DROP TABLE <table>;
+CREATE TABLE <table>
+(
+  ts timestamp with time zone NOT NULL,
+  "Accounting-Record-Type" integer,
+  "Session-Id" bytea,
+  "Accounting-Record-Number" integer,
+  "Route-Record1" bytea,
+  "Route-Record2" bytea,
+  "Route-Record3" bytea,
+  "Route-Record4" bytea
+);
+*/
+#define TABLE "incoming_test"
+
+#include "app_acct.h"
+#include <libpq-fe.h>
+
+static int add_avp_in_conf(char * avpname, int multi) 
+{
+	struct acct_conf_avp *new;
+	struct dict_object * dict;
+	struct dict_avp_data dictdata;
+
+	/* Validate the avp name first */
+	CHECK_FCT( fd_dict_search( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, avpname, &dict, ENOENT) );
+	CHECK_FCT( fd_dict_getval( dict, &dictdata ));
+
+	/* Create a new entry */
+	CHECK_MALLOC( new = malloc(sizeof(struct acct_conf_avp)) );
+	memset(new, 0, sizeof(struct acct_conf_avp));
+	fd_list_init(&new->chain, NULL);
+	new->avpname = avpname;
+	new->avpobj = dict;
+	new->avptype = dictdata.avp_basetype;
+	new->multi = multi;
+
+	/* Add this new entry at the end of the list */
+	fd_list_insert_before( &acct_config->avps, &new->chain );
+	
+	return 0;
+}
+
+/* Main test routine */
+int main(int argc, char *argv[])
+{
+	extern PGconn *conn; /* in acct_db.c */
+	struct msg * msg;
+	char * sess_bkp;
+	struct dict_object * session_id = NULL;
+	
+	/* First, initialize the daemon modules */
+	INIT_FD();
+	fd_g_config->cnf_diamid = strdup("test.app.acct");
+	fd_g_config->cnf_diamid_len = strlen(fd_g_config->cnf_diamid);
+	fd_g_config->cnf_diamrlm = strdup("app.acct");
+	fd_g_config->cnf_diamrlm_len = strlen(fd_g_config->cnf_diamrlm);
+	
+	CHECK( 0, fd_queues_init()  );
+	CHECK( 0, fd_msg_init()  );
+	CHECK( 0, fd_rtdisp_init()  );
+	
+	/* Initialize the extension configuration for the test */
+	{
+		CHECK( 0, acct_conf_init() );
+		acct_config->conninfo = strdup(TEST_CONNINFO);
+		acct_config->tablename = strdup(TABLE);
+		acct_config->tsfield = strdup("ts");
+		CHECK( 0, add_avp_in_conf(strdup("Session-Id"), 0) );
+		CHECK( 0, add_avp_in_conf(strdup("Accounting-Record-Type"), 0) );
+		CHECK( 0, add_avp_in_conf(strdup("Accounting-Record-Number"), 0) );
+		CHECK( 0, add_avp_in_conf(strdup("Route-Record"), 4) );
+		
+		/* Now, call the one of the extension */
+		CHECK( 0, fd_ext_init(FD_PROJECT_VERSION_MAJOR, FD_PROJECT_VERSION_MINOR,NULL) );
+	}
+	
+	/* Drop and recreate the table for the test */
+	{
+		PGresult * res;
+		CHECK( CONNECTION_OK, PQstatus(conn) );
+		
+		res = PQexec(conn, "DROP TABLE " TABLE ";");
+		CHECK( PGRES_COMMAND_OK, PQresultStatus(res) );
+		PQclear(res);
+		
+		res = PQexec(conn, "CREATE TABLE " TABLE " ( "
+					"  ts timestamp with time zone NOT NULL, "
+					"  \"Accounting-Record-Type\" integer, "
+					"  \"Session-Id\" bytea, "
+					"  \"Accounting-Record-Number\" integer, "
+					"  \"Route-Record1\" bytea, "
+					"  \"Route-Record2\" bytea, "
+					"  \"Route-Record3\" bytea, "
+					"  \"Route-Record4\" bytea "
+					");"
+				);
+		CHECK( PGRES_COMMAND_OK, PQresultStatus(res) );
+		PQclear(res);
+	}
+	
+	/* OK, we are ready to test now. Create an ACR message that will pass the ABNF check */
+	{
+		struct dict_object * d = NULL;
+		struct avp *avp = NULL;
+		union avp_value avp_val;
+
+		/* Now find the ACR dictionary object */
+		CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_COMMAND, CMD_BY_NAME, "Accounting-Request", &d, ENOENT ) );
+
+		/* Create the instance */
+		CHECK( 0, fd_msg_new ( d, MSGFL_ALLOC_ETEID, &msg ) );
+		
+		/* App id */
+		{
+			struct msg_hdr * h;
+			CHECK( 0, fd_msg_hdr( msg, &h ) );
+			h->msg_appl = 3;
+		}
+		
+		/* sid */
+		{
+			struct session * sess = NULL;
+			char * s;
+			CHECK( 0, fd_sess_new( &sess, fd_g_config->cnf_diamid, NULL, 0) );
+			CHECK( 0, fd_sess_getsid(sess, &s) );
+			sess_bkp = strdup(s);
+
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Session-Id", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)sess_bkp;
+			avp_val.os.len = strlen(sess_bkp);
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_FIRST_CHILD, avp) );
+		}
+		
+		/* Origin-* */
+		CHECK( 0, fd_msg_add_origin(msg, 1) );
+		
+		/* Destination-Realm */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Destination-Realm", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)fd_g_config->cnf_diamrlm;
+			avp_val.os.len = fd_g_config->cnf_diamrlm_len;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Accounting-Record-Type */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Type", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.u32 = 2;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Accounting-Record-Number */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Accounting-Record-Number", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.u32 = 2;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Route-Record */
+		{
+			CHECK( 0, fd_dict_search ( fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &d, ENOENT ) );
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)"peer1";
+			avp_val.os.len = strlen((char *)avp_val.os.data);
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+			
+			CHECK( 0, fd_msg_avp_new ( d, 0, &avp ) );
+			memset(&avp_val, 0, sizeof(avp_val));
+			avp_val.os.data = (unsigned char *)"peer2";
+			avp_val.os.len = strlen((char *)avp_val.os.data);
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &avp_val ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp) );
+		}
+		
+		/* Source */
+		CHECK( 0, fd_msg_source_set( msg, "peer3", 1, fd_g_config->cnf_dict ) );
+	}
+	
+	/* Now, have the daemon handle this */
+	CHECK( 0, fd_fifo_post(fd_g_incoming, &msg) );
+	
+	/* It is picked by the dispatch module, the extension handles the query, inserts the records in the DB, send creates the answer.
+	   Once the answer is ready, it is sent to "peer3" which is not available of course; then the message is simply destroyed.
+	   We wait 1 second for this to happen... */
+	sleep(1);
+	
+	/* Now, check the record was actually registered properly */
+	{
+		PGresult * res;
+		char * s;
+		res = PQexec(conn, "SELECT \"Session-Id\" from " TABLE ";");
+		CHECK( PGRES_TUPLES_OK, PQresultStatus(res) );
+		
+		/* We also check that the Session-Id we retrieve is the same as what we generated earlier (not trashed in the process) */
+		s = PQgetvalue(res, 0, 0);
+		CHECK( 0, strcmp(s, sess_bkp) );
+		
+		PQclear(res);
+	}  
+
+	/* That's all for the tests yet */
+	free(sess_bkp);
+	fd_ext_fini();
+	
+	PASSTEST();
+} 
+	
--- a/freeDiameter/tests/tests.h	Wed Apr 28 18:54:08 2010 +0900
+++ b/freeDiameter/tests/tests.h	Fri Apr 30 17:55:16 2010 +0900
@@ -124,7 +124,7 @@
 static inline void parse_cmdline(int argc, char * argv[]) {
 	int c;
 	int no_timeout = 0;
-	while ((c = getopt (argc, argv, "dqn")) != -1) {
+	while ((c = getopt (argc, argv, "dqnf:")) != -1) {
 		switch (c) {
 			case 'd':	/* Increase verbosity of debug messages.  */
 				test_verbo++;
@@ -138,6 +138,15 @@
 				no_timeout = 1;
 				break;
 			
+			case 'f':	/* Full debug for the function with this name.  */
+				#ifdef DEBUG
+				fd_debug_one_function = optarg;
+				#else /* DEBUG */
+				TRACE_DEBUG(INFO, "Error: must compile with DEBUG support to use this feature");
+				return EINVAL;
+				#endif /* DEBUG */
+				break;
+				
 			default:	/* bug: option not considered.  */
 				return;
 		}
"Welcome to our mercurial repository"