# HG changeset patch # User Sebastien Decugis # Date 1272617716 -32400 # Node ID 0daf6fc2b7517adc320ddf318b4ab60c6c44b16a # Parent 397cdcd41f53f5755d35298581b8caf2e6007d51 Added a test case for the app_acct extension diff -r 397cdcd41f53 -r 0daf6fc2b751 extensions/app_acct/CMakeLists.txt --- 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 diff -r 397cdcd41f53 -r 0daf6fc2b751 extensions/app_acct/acct_db.c --- 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; } diff -r 397cdcd41f53 -r 0daf6fc2b751 extensions/app_acct/acct_records.c --- 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); + } +} diff -r 397cdcd41f53 -r 0daf6fc2b751 extensions/app_acct/app_acct.c --- 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 ) ); diff -r 397cdcd41f53 -r 0daf6fc2b751 extensions/app_acct/app_acct.h --- 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); diff -r 397cdcd41f53 -r 0daf6fc2b751 freeDiameter/extensions.c --- 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(); diff -r 397cdcd41f53 -r 0daf6fc2b751 freeDiameter/fD.h --- 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); diff -r 397cdcd41f53 -r 0daf6fc2b751 freeDiameter/main.c --- 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() ); diff -r 397cdcd41f53 -r 0daf6fc2b751 freeDiameter/tests/CMakeLists.txt --- 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 ) diff -r 397cdcd41f53 -r 0daf6fc2b751 freeDiameter/tests/testappacct.c --- /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 * +* * +* 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 ; +CREATE 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 + +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(); +} + diff -r 397cdcd41f53 -r 0daf6fc2b751 freeDiameter/tests/tests.h --- 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; }