changeset 7:e5af94b04946

Added dispatch module and tests
author Sebastien Decugis <sdecugis@nict.go.jp>
date Fri, 04 Sep 2009 18:05:25 +0900
parents b0d377c79d80
children 3e143f047f78
files freeDiameter/tests/CMakeLists.txt freeDiameter/tests/testdisp.c include/freeDiameter/freeDiameter.h include/freeDiameter/libfreeDiameter.h libfreeDiameter/CMakeLists.txt libfreeDiameter/dictionary.c libfreeDiameter/dispatch.c libfreeDiameter/init.c libfreeDiameter/libfD.h libfreeDiameter/messages.c libfreeDiameter/sessions.c
diffstat 11 files changed, 1219 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/freeDiameter/tests/CMakeLists.txt	Thu Sep 03 16:03:25 2009 +0900
+++ b/freeDiameter/tests/CMakeLists.txt	Fri Sep 04 18:05:25 2009 +0900
@@ -16,6 +16,7 @@
 	testmesg
 	testmq
 	testsess
+	testdisp
 )
 
 #############################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/freeDiameter/tests/testdisp.c	Fri Sep 04 18:05:25 2009 +0900
@@ -0,0 +1,674 @@
+/*********************************************************************************************************
+* 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"
+	
+#define Define_cb( __int, __extra )										\
+int cb_##__int( struct msg ** msg, struct avp * avp, struct session * session, enum disp_action * action )	\
+{														\
+	CHECK( 1, msg ? 1 : 0 );										\
+	CHECK( 1, action ? 1 : 0 );										\
+	CHECK( sess, session );											\
+	*action = DISP_ACT_CONT;										\
+	cbcalled[__int] += 1;											\
+	do {													\
+		__extra ;											\
+	} while (0);												\
+	return 0;												\
+}
+
+#define NB_CB	10
+char cbcalled[NB_CB];
+struct session * sess;
+
+/* cb_0 */  Define_cb( 0, );
+/* cb_1 */  Define_cb( 1, );
+/* cb_2 */  Define_cb( 2, );
+/* cb_3 */  Define_cb( 3, );
+/* cb_4 */  Define_cb( 4, );
+/* cb_5 */  Define_cb( 5, );
+/* cb_6 */  Define_cb( 6, return 12345 );
+/* cb_7 */  Define_cb( 7, { CHECK( 1, avp ? 1 : 0 ); } );
+/* cb_8 */  Define_cb( 8, { CHECK( 0, fd_msg_free( *msg ) ); *msg = NULL; } );
+/* cb_9 */  Define_cb( 9, *action = DISP_ACT_SEND );
+/* max: cb_<NB_CB - 1> */
+
+/* Create a new message containing what we want */
+struct msg * new_msg(int appid, struct dict_object * cmd, struct dict_object * avp1, struct dict_object * avp2, int val)
+{
+	struct msg *new;
+	struct avp *avp;
+	union avp_value value;
+	struct msg_hdr * msg_hdr = NULL;
+	
+	CHECK( 0, fd_msg_new ( cmd, 0, &new ) );
+	CHECK( 0, fd_msg_hdr ( new, &msg_hdr ) );
+	msg_hdr->msg_appl = appid;
+	
+	if (avp1) {
+		CHECK( 0, fd_msg_avp_new ( avp1, 0, &avp ) );
+		value.u32 = 0;
+		CHECK( 0, fd_msg_avp_setvalue ( avp, &value ) );
+		CHECK( 0, fd_msg_avp_add ( new, MSG_BRW_LAST_CHILD, avp ) );
+	}
+	
+	if (avp2) {
+		CHECK( 0, fd_msg_avp_new ( avp2, 0, &avp ) );
+		value.u32 = val;
+		CHECK( 0, fd_msg_avp_setvalue ( avp, &value ) );
+		CHECK( 0, fd_msg_avp_add ( new, MSG_BRW_LAST_CHILD, avp ) );
+	}
+	
+	return new;	
+}
+
+/* Main test routine */
+int main(int argc, char *argv[])
+{
+	struct dict_object * app1, * app2;
+	struct dict_object * cmd1, * cmd2;
+	struct dict_object * avp1, * avp2; /* avp2 is enumerated; they are both unsigned32 types */
+	struct dict_object * enu1, * enu2;
+	struct msg * msg = NULL;
+	enum disp_action action;
+	struct disp_hdl * hdl[NB_CB];
+	struct disp_when when;
+	
+	/* First, initialize the daemon modules */
+	INIT_FD();
+	
+	/* Create a dummy session, we don't use it anyway */
+	CHECK( 0, fd_sess_new( &sess, "test.disp", NULL, 0 ) );
+	
+	memset(&when, 0xff, sizeof(when)); /* check that we don't use un-initialized parts */
+	
+	/* Initialize dictionary objects */
+	{
+		struct dict_object * enutype;
+		struct dict_application_data app1_data = { 1, "Application test 1" };
+		struct dict_application_data app2_data = { 2, "Application test 2" };
+		struct dict_cmd_data cmd1_data = { 1, "Command test 1 (req)", CMD_FLAG_REQUEST,	CMD_FLAG_REQUEST };
+		struct dict_cmd_data cmd2_data = { 1, "Command test 2 (ans)", CMD_FLAG_REQUEST,	0 };
+		struct dict_type_data type_data = { AVP_TYPE_UNSIGNED32, "Type test", NULL, NULL };
+		struct dict_avp_data avp1_data = { 10001, 0, "AVP test 1", 0, 0, AVP_TYPE_UNSIGNED32 };
+		struct dict_avp_data avp2_data = { 10002, 0, "AVP test 2", 0, 0, AVP_TYPE_UNSIGNED32 };
+		struct dict_enumval_data enu1_data = { "ENU test 1", { .u32 = 1 }};
+		struct dict_enumval_data enu2_data = { "ENU test 2", { .u32 = 2 }};
+		
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_APPLICATION, &app1_data, NULL, &app1 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_APPLICATION, &app2_data, NULL, &app2 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_COMMAND, &cmd1_data, NULL, &cmd1 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_COMMAND, &cmd2_data, NULL, &cmd2 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_TYPE, &type_data, NULL, &enutype ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_AVP, &avp1_data, NULL,    &avp1 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_AVP, &avp2_data, enutype, &avp2 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_ENUMVAL, &enu1_data, enutype, &enu1 ) );
+		CHECK( 0, fd_dict_new ( fd_g_dict, DICT_ENUMVAL, &enu2_data, enutype, &enu2 ) );
+	}
+	
+	/* Register first handler, very simple test */
+	{
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, NULL, &hdl[0] ) );
+		CHECK( 1, hdl[0] ? 1 : 0 );
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( NULL, hdl[0] );
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, NULL, &hdl[0] ) );
+	
+		/* Check this handler is called for a message */
+		msg = new_msg( 0, cmd1, avp1, NULL, 0 );
+		memset(cbcalled, 0, sizeof(cbcalled));
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( DISP_ACT_CONT, action );
+		
+		/* Delete the message */
+		CHECK( 0, fd_msg_free( msg ) );
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+	}
+	
+	/* Handlers for applications */
+	{
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) );
+		when.app = app1;
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_APPID, &when, &hdl[1] ) );
+		when.app = app2;
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_APPID, &when, &hdl[2] ) );
+		when.avp = avp2;
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_APPID, &when, &hdl[3] ) );
+		when.avp = avp1;
+		CHECK( 0, fd_disp_register( cb_4, DISP_HOW_APPID, &when, &hdl[4] ) );
+	
+		/* Check the callbacks are called as appropriate */
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 0, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 1, cbcalled[4] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[4] ) );
+	}
+	
+	/* Handlers for commands */
+	{
+		when.app = NULL;
+		when.command = NULL;
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) );
+		CHECK( EINVAL, fd_disp_register( cb_1, DISP_HOW_CC, &when, &hdl[1] ) );
+		when.command = cmd1;
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_CC, &when, &hdl[1] ) ); /* cmd1 */
+		when.app = app2;
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_CC, &when, &hdl[2] ) ); /* app2 + cmd1 */
+		when.command = cmd2;
+		when.app = NULL;
+		when.avp = avp1;
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_CC, &when, &hdl[3] ) ); /* cmd2 (avp1 ignored) */
+		
+		/* Check the callbacks are called as appropriate */
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 0, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd2, NULL, avp2, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+	}
+	
+	/* Handlers for AVPs */
+	{
+		when.app = NULL;
+		when.command = NULL;
+		when.avp = NULL;
+	
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) ); /* all */
+		CHECK( EINVAL, fd_disp_register( cb_1, DISP_HOW_AVP, &when, &hdl[1] ) );
+		
+		when.avp = avp1;
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_AVP, &when, &hdl[1] ) ); /* avp1 */
+		
+		when.command = cmd1;
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_AVP, &when, &hdl[2] ) ); /* avp1 + cmd1 */
+		
+		when.command = NULL;
+		when.app = app1;
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_AVP, &when, &hdl[3] ) ); /* avp1 + app1 */
+		
+		when.command = cmd1;
+		CHECK( 0, fd_disp_register( cb_4, DISP_HOW_AVP, &when, &hdl[4] ) ); /* avp1 + cmd1 + app1 */
+		
+		when.app = NULL;
+		when.command = NULL;
+		when.avp = avp2;
+		when.value = enu1;
+		CHECK( 0, fd_disp_register( cb_5, DISP_HOW_AVP, &when, &hdl[5] ) ); /* avp2 */
+		
+		when.value = enu2;
+		CHECK( 0, fd_disp_register( cb_7, DISP_HOW_AVP, &when, &hdl[6] ) ); /* avp2 */
+		
+		
+		
+		/* Check the callbacks are called as appropriate */
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 0, cmd1, NULL, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 0, cbcalled[5] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 0, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 0, cbcalled[5] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd2, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 0, cbcalled[5] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 1, cbcalled[4] );
+		CHECK( 0, cbcalled[5] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, avp2, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 1, cbcalled[4] );
+		CHECK( 1, cbcalled[5] );
+		CHECK( 1, cbcalled[7] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, NULL, avp2, 1 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 1, cbcalled[5] );
+		CHECK( 1, cbcalled[7] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, NULL, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 1, cbcalled[5] );
+		CHECK( 1, cbcalled[7] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[4] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[5] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[6] ) );
+	}
+		
+	/* Handlers for enum values */
+	{
+		when.app = NULL;
+		when.command = NULL;
+		when.avp = NULL;
+		when.value = NULL;
+		
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) ); /* all */
+		CHECK( EINVAL, fd_disp_register( cb_1, DISP_HOW_AVP_ENUMVAL, &when, &hdl[1] ) );
+		when.value = enu1;
+		CHECK( EINVAL, fd_disp_register( cb_1, DISP_HOW_AVP_ENUMVAL, &when, &hdl[1] ) );
+		when.avp = avp1;
+		CHECK( EINVAL, fd_disp_register( cb_1, DISP_HOW_AVP_ENUMVAL, &when, &hdl[1] ) );
+		when.avp = avp2;
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_AVP_ENUMVAL, &when, &hdl[1] ) ); /* avp2, enu1 */
+		
+		when.command = cmd1;
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_AVP_ENUMVAL, &when, &hdl[2] ) ); /* avp2, enu1 + cmd1 */
+		
+		when.command = NULL;
+		when.app = app1;
+		when.value = enu2;
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_AVP_ENUMVAL, &when, &hdl[3] ) ); /* avp2, enu2 + app1 */
+		
+		/* Check the callbacks are called as appropriate */
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 0, cmd1, avp1, NULL, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd2, avp1, avp2, 0 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd2, avp1, avp2, 1 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, avp2, 1 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd2, avp1, avp2, 1 );
+		{
+			struct avp *avp;
+			union avp_value value;
+			CHECK( 0, fd_msg_avp_new ( avp2, 0, &avp ) );
+			value.u32 = 2;
+			CHECK( 0, fd_msg_avp_setvalue ( avp, &value ) );
+			CHECK( 0, fd_msg_avp_add ( msg, MSG_BRW_LAST_CHILD, avp ) );
+		}
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( DISP_ACT_CONT, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+	}
+	
+	/* Test behavior of handlers */
+	{
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) );
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_ANY, &when, &hdl[1] ) );
+		CHECK( 0, fd_disp_register( cb_6, DISP_HOW_ANY, &when, &hdl[2] ) );
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_ANY, &when, &hdl[3] ) );
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_ANY, &when, &hdl[4] ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, avp2, 1 );
+		CHECK( 12345, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[6] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[4] ) );
+		
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) );
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_ANY, &when, &hdl[1] ) );
+		CHECK( 0, fd_disp_register( cb_8, DISP_HOW_ANY, &when, &hdl[2] ) );
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_ANY, &when, &hdl[3] ) );
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_ANY, &when, &hdl[4] ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, avp2, 1 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[8] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( NULL, msg );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[4] ) );
+		
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) );
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_ANY, &when, &hdl[1] ) );
+		CHECK( 0, fd_disp_register( cb_9, DISP_HOW_ANY, &when, &hdl[2] ) );
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_ANY, &when, &hdl[3] ) );
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_ANY, &when, &hdl[4] ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 1, cmd1, avp1, avp2, 1 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[9] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( DISP_ACT_SEND, action );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[4] ) );
+	}
+		
+	/* Test order of handlers */
+	{
+		when.app = app2;
+		when.command = cmd2;
+		when.avp = avp2;
+		when.value = enu2;
+		
+		CHECK( 0, fd_disp_register( cb_0, DISP_HOW_ANY, &when, &hdl[0] ) );
+		CHECK( 0, fd_disp_register( cb_1, DISP_HOW_AVP_ENUMVAL, &when, &hdl[1] ) );
+		CHECK( 0, fd_disp_register( cb_2, DISP_HOW_AVP, &when, &hdl[2] ) );
+		CHECK( 0, fd_disp_register( cb_3, DISP_HOW_CC, &when, &hdl[3] ) );
+		CHECK( 0, fd_disp_register( cb_4, DISP_HOW_APPID, &when, &hdl[4] ) );
+		
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 1, cbcalled[4] );
+		CHECK( 0, cbcalled[9] );
+		CHECK( 0, fd_msg_free( msg ) );
+		
+		CHECK( 0, fd_disp_register( cb_9, DISP_HOW_ANY, &when, &hdl[5] ) );
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 0, cbcalled[1] );
+		CHECK( 0, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 1, cbcalled[9] );
+		CHECK( 0, fd_msg_free( msg ) );
+		CHECK( 0, fd_disp_unregister( &hdl[5] ) );
+		
+		CHECK( 0, fd_disp_register( cb_9, DISP_HOW_AVP_ENUMVAL, &when, &hdl[5] ) );
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 1, cbcalled[9] );
+		CHECK( 0, fd_msg_free( msg ) );
+		CHECK( 0, fd_disp_unregister( &hdl[5] ) );
+		
+		CHECK( 0, fd_disp_register( cb_9, DISP_HOW_AVP, &when, &hdl[5] ) );
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 0, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 1, cbcalled[9] );
+		CHECK( 0, fd_msg_free( msg ) );
+		CHECK( 0, fd_disp_unregister( &hdl[5] ) );
+		
+		CHECK( 0, fd_disp_register( cb_9, DISP_HOW_CC, &when, &hdl[5] ) );
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 0, cbcalled[4] );
+		CHECK( 1, cbcalled[9] );
+		CHECK( 0, fd_msg_free( msg ) );
+		CHECK( 0, fd_disp_unregister( &hdl[5] ) );
+		
+		CHECK( 0, fd_disp_register( cb_9, DISP_HOW_APPID, &when, &hdl[5] ) );
+		memset(cbcalled, 0, sizeof(cbcalled));
+		msg = new_msg( 2, cmd2, avp1, avp2, 2 );
+		CHECK( 0, fd_msg_dispatch ( &msg, sess, &action ) );
+		CHECK( 1, cbcalled[0] );
+		CHECK( 1, cbcalled[1] );
+		CHECK( 1, cbcalled[2] );
+		CHECK( 1, cbcalled[3] );
+		CHECK( 1, cbcalled[4] );
+		CHECK( 1, cbcalled[9] );
+		CHECK( 0, fd_msg_free( msg ) );
+		CHECK( 0, fd_disp_unregister( &hdl[5] ) );
+		
+		CHECK( 0, fd_disp_unregister( &hdl[0] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[1] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[2] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[3] ) );
+		CHECK( 0, fd_disp_unregister( &hdl[4] ) );
+	}			
+	
+	/* That's all for the tests yet */
+	PASSTEST();
+} 
+	
--- a/include/freeDiameter/freeDiameter.h	Thu Sep 03 16:03:25 2009 +0900
+++ b/include/freeDiameter/freeDiameter.h	Fri Sep 04 18:05:25 2009 +0900
@@ -110,4 +110,35 @@
 int fd_msg_add_origin ( struct msg * msg, struct dictionary * dict, int osi ); /* Add Origin-Host, Origin-Realm, (if osi) Origin-State-Id AVPS at the end of the message */
 
 
+
+/***************************************/
+/*   Dispatch module, daemon's part    */
+/***************************************/
+
+enum {
+	DISP_APP_AUTH	= 1,
+	DISP_APP_ACCT	= 2
+};
+/*
+ * FUNCTION:	fd_disp_app_support
+ *
+ * PARAMETERS:
+ *  app		: The dictionary object corresponding to the Application.
+ *  vendor	: (Optional) the dictionary object of a Vendor to claim support in Vendor-Specific-Application-Id
+ *  flags	: Combination of DISP_APP_* flags.
+ *
+ * DESCRIPTION: 
+ *   Registers an application to be advertized in CER/CEA exchanges.
+ *  Messages with an application-id matching a registered value are passed to the dispatch module,
+ * while other messages are simply relayed or an error is returned (if local node does not relay)
+ *
+ * RETURN VALUE:
+ *  0      	: The application support is registered.
+ *  EINVAL 	: A parameter is invalid.
+ */
+int fd_disp_app_support ( struct dict_object * app, struct dict_object * vendor, int flags );
+
+/* Note: if we want to support capabilities updates, we'll have to add possibility to remove an app as well... */
+
+
 #endif /* _FREEDIAMETER_H */
--- a/include/freeDiameter/libfreeDiameter.h	Thu Sep 03 16:03:25 2009 +0900
+++ b/include/freeDiameter/libfreeDiameter.h	Fri Sep 04 18:05:25 2009 +0900
@@ -1507,124 +1507,6 @@
 
 
 /*============================================================*/
-/*                         DISPATCH                           */
-/*============================================================*/
-
-/* Dispatch module (passing incoming messages to extensions registered callbacks)
- * is split between the library and the daemon.
- *
- * The library provides the support for associating dispatch callbacks with
- * dictionary objects.
- *
- * The daemon is responsible for calling the callbacks for a message when appropriate.
- *
- *
- * The dispatch module has two main roles:
- *  - help determine if a message can be handled locally (during the routing step)
- *        This decision involves only the application-id of the message.
- *  - pass the message to the callback(s) that will handle it (during the dispatch step)
- *
- * These are the possibilities for registering a so-called dispatch callback:
- *
- * -> For All messages.
- *  This callback is called for all messages that are handled locally. This should be used only
- *  for debug purpose.
- *
- * -> by AVP value (constants only).
- *  This callback will be called when a message is received and contains an AVP with a specified enumerated value.
- *
- * -> by AVP.
- *  This callback will be called when the received message contains a certain AVP.
- *
- * -> by command-code.
- *  This callback will be called when the message is a specific command (and 'R' flag).
- *
- * -> by application.
- *  This callback will be called when the message has a specific application-id.
- *
- * ( by vendor: would this be useful? it may be added later)
- */
-enum disp_how {
-	DISP_HOW_ANY = 1,		/* Any message. This should be only used for debug. */
-	DISP_HOW_APPID,			/* Any message with the specified application-id */
-	DISP_HOW_CC,			/* Messages of the specified command-code (request or answer). App id may be specified. */
-	DISP_HOW_AVP,			/* Messages containing a specific AVP. Command-code and App id may be specified. */
-	DISP_HOW_AVP_ENUMVAL		/* Messages containing a specific AVP with a specific enumerated value. Command-code and App id may be specified. */
-};
-/*
- * Several criteria may be selected at the same time, for example command-code AND application id.
- *
- * If several callbacks are registered for the same object, their order is unspecified.
- * The order in which the callbacks are called is:
- *  DISP_HOW_ANY
- *  DISP_HOW_AVP_ENUMVAL & DISP_HOW_AVP
- *  DISP_HOW_CC
- *  DISP_HOW_APPID
- */
-
-/* When a callback is registered, a "when" argument is passed in addition to the disp_how value,
- * to specify which values the criteria must match. */
-struct disp_when {
-	struct dict_object *	app_id;
-	struct dict_object *	command;
-	struct dict_object *	avp;
-	struct dict_object *	value;
-};
-
-/* Here is the details on this "when" argument, depending on the disp_how value.
- *
- * DISP_HOW_ANY.
- *  In this case, "when" must be NULL.
- *
- * DISP_HOW_APPID.
- *  Only the "app_id" field must be set, other fields are ignored. It points to a dictionary object of type DICT_APPLICATION.
- *
- * DISP_HOW_CC.
- *  The "command" field must be defined and point to a dictionary object of type DICT_COMMAND.
- *  The "app_id" may be also set. In the case it is set, it restricts the callback to be called only with this command-code and app id.
- *  The other fields are ignored.
- *
- * DISP_HOW_AVP.
- *  The "avp" field of the structure must be set and point to a dictionary object of type DICT_AVP.
- *  The "app_id" field may be set to restrict the messages matching to a specific app id.
- *  The "command" field may also be set to a valid DICT_COMMAND object.
- *  The content of the "value" field is ignored.
- *
- * DISP_HOW_AVP_ENUMVAL.
- *  All fields have the same constraints and meaning as in DISP_REG_AVP. In addition, the "value" field must be set
- *  and points to a valid DICT_ENUMVAL object. 
- *
- * Here is a sumary of the fields: ( M : must be set; m : may be set; 0 : ignored )
- *  field:     app_id    command     avp    value
- * APPID :       M          0         0       0
- * CC    :       m          M         0       0
- * AVP   :       m          m         M       0
- * ENUMVA:       m          m         M       M
- */
-
-/* The callbacks that are registered have the following prototype:
- *
- *  int dispatch_callback( struct msg ** msg, struct avp * avp, struct session * session, int * is_answer );
- *
- * FUNCTION:	dispatch_callback
- *
- * PARAMETERS:
- *  msg 	: the received message on function entry. may be updated to answer on return (see description)
- *  avp 	: for callbacks registered with DISP_HOW_AVP or DISP_HOW_AVP_ENUMVAL, direct link to the triggering AVP.
- *  session	: if the message contains a Session-Id AVP, the corresponding session object.
- *
- * DESCRIPTION: 
- *   Create a new AVP instance.
- *
- * RETURN VALUE:
- *  0      	: The AVP is created.
- *  EINVAL 	: A parameter is invalid.
- *  (other standard errors may be returned, too, with their standard meaning. Example:
- *    ENOMEM 	: Memory allocation for the new avp failed.)
- */
-
-
-/*============================================================*/
 /*                         MESSAGES                           */
 /*============================================================*/
 
@@ -2195,6 +2077,201 @@
 int fd_msg_update_length ( msg_or_avp * object );
 
 
+/*============================================================*/
+/*                         DISPATCH                           */
+/*============================================================*/
+
+/* Dispatch module (passing incoming messages to extensions registered callbacks)
+ * is split between the library and the daemon.
+ *
+ * The library provides the support for associating dispatch callbacks with
+ * dictionary objects.
+ *
+ * The daemon is responsible for calling the callbacks for a message when appropriate.
+ *
+ *
+ * The dispatch module has two main roles:
+ *  - help determine if a message can be handled locally (during the routing step)
+ *        This decision involves only the application-id of the message.
+ *  - pass the message to the callback(s) that will handle it (during the dispatch step)
+ *
+ * The first role is handled by the daemon.
+ *
+ * About the second, these are the possibilities for registering a dispatch callback:
+ *
+ * -> For All messages.
+ *  This callback is called for all messages that are handled locally. This should be used only
+ *  for debug purpose.
+ *
+ * -> by AVP value (constants only).
+ *  This callback will be called when a message is received and contains an AVP with a specified enumerated value.
+ *
+ * -> by AVP.
+ *  This callback will be called when the received message contains a certain AVP.
+ *
+ * -> by command-code.
+ *  This callback will be called when the message is a specific command (and 'R' flag).
+ *
+ * -> by application.
+ *  This callback will be called when the message has a specific application-id.
+ *
+ * ( by vendor: would this be useful? it may be added later)
+ */
+enum disp_how {
+	DISP_HOW_ANY = 1,		/* Any message. This should be only used for debug. */
+	DISP_HOW_APPID,			/* Any message with the specified application-id */
+	DISP_HOW_CC,			/* Messages of the specified command-code (request or answer). App id may be specified. */
+	DISP_HOW_AVP,			/* Messages containing a specific AVP. Command-code and App id may be specified. */
+	DISP_HOW_AVP_ENUMVAL		/* Messages containing a specific AVP with a specific enumerated value. Command-code and App id may be specified. */
+};
+/*
+ * Several criteria may be selected at the same time, for example command-code AND application id.
+ *
+ * If several callbacks are registered for the same object, they are called in the order they were registered.
+ * The order in which the callbacks are called is:
+ *  DISP_HOW_ANY
+ *  DISP_HOW_AVP_ENUMVAL & DISP_HOW_AVP
+ *  DISP_HOW_CC
+ *  DISP_HOW_APPID
+ */
+
+/* When a callback is registered, a "when" argument is passed in addition to the disp_how value,
+ * to specify which values the criteria must match. */
+struct disp_when {
+	struct dict_object *	app;
+	struct dict_object *	command;
+	struct dict_object *	avp;
+	struct dict_object *	value;
+};
+
+/* Note that all the dictionary objects should really belong to the same dictionary!
+ *
+ * Here is the details on this "when" argument, depending on the disp_how value.
+ *
+ * DISP_HOW_ANY.
+ *  In this case, "when" must be NULL.
+ *
+ * DISP_HOW_APPID.
+ *  Only the "app_id" field must be set, other fields are ignored. It points to a dictionary object of type DICT_APPLICATION.
+ *
+ * DISP_HOW_CC.
+ *  The "command" field must be defined and point to a dictionary object of type DICT_COMMAND.
+ *  The "app_id" may be also set. In the case it is set, it restricts the callback to be called only with this command-code and app id.
+ *  The other fields are ignored.
+ *
+ * DISP_HOW_AVP.
+ *  The "avp" field of the structure must be set and point to a dictionary object of type DICT_AVP.
+ *  The "app_id" field may be set to restrict the messages matching to a specific app id.
+ *  The "command" field may also be set to a valid DICT_COMMAND object.
+ *  The content of the "value" field is ignored.
+ *
+ * DISP_HOW_AVP_ENUMVAL.
+ *  All fields have the same constraints and meaning as in DISP_REG_AVP. In addition, the "value" field must be set
+ *  and points to a valid DICT_ENUMVAL object. 
+ *
+ * Here is a sumary of the fields: ( M : must be set; m : may be set; 0 : ignored )
+ *  field:     app_id    command     avp    value
+ * APPID :       M          0         0       0
+ * CC    :       m          M         0       0
+ * AVP   :       m          m         M       0
+ * ENUMVA:       m          m         M       M
+ */
+
+enum disp_action {
+	DISP_ACT_CONT,	/* The next handler should be called, unless *msg == NULL. */
+	DISP_ACT_SEND	/* The updated message must be sent. No further callback is called. */
+};
+/* The callbacks that are registered have the following prototype:
+ *  	int dispatch_callback( struct msg ** msg, struct avp * avp, struct session * session, enum disp_action * action );
+ *
+ * CALLBACK:	dispatch_callback
+ *
+ * PARAMETERS:
+ *  msg 	: the received message on function entry. may be updated to answer on return (see description)
+ *  avp 	: for callbacks registered with DISP_HOW_AVP or DISP_HOW_AVP_ENUMVAL, direct link to the triggering AVP.
+ *  session	: if the message contains a Session-Id AVP, the corresponding session object, NULL otherwise.
+ *  action	: upon return, this tells the daemon what to do next.
+ *
+ * DESCRIPTION: 
+ *   Called when a received message matchs the condition for which the callback was registered.
+ * This callback may do any kind of processing on the message, including:
+ *  - create an answer for a request.
+ *  - proxy a request or message, add / remove the Proxy-Info AVP, then forward the message.
+ *  - update a routing table or start a connection with a new peer, then forward the message.
+ *  - ...
+ * 
+ * When *action == DISP_ACT_SEND on callback return, the msg pointed by *msg is passed to the routing module for sending.
+ * When *action == DISP_ACT_CONT, the next registered callback is called.
+ *  When the last callback gives also DISP_ACT_CONT action value, a default handler is called. It's behavior is as follow:
+ *   - if the message is an answer, it is discarded.
+ *   - if the message is a request, it is passed again to the routing stack, and marked as non-local handling.
+ *
+ * RETURN VALUE:
+ *  0      	: The callback executed successfully and updated *action appropriately.
+ *  !0		: standard errors. In case of error, the message is discarded.
+ */
+
+/* This structure represents a handler for a registered callback, allowing its de-registration */
+struct disp_hdl;
+
+/*
+ * FUNCTION:	fd_disp_register
+ *
+ * PARAMETERS:
+ *  cb 		  : The callback function to register (see dispatch_callback description above).
+ *  how	  	  : How the callback must be registered.
+ *  when          : Values that must match, depending on the how argument.
+ *  handle        : On success, a handler to the registered callback is stored here if not NULL. 
+ *		   This handler can be used to unregister the cb.
+ *
+ * DESCRIPTION: 
+ *   Register a new callback to handle messages delivered locally.
+ *
+ * RETURN VALUE:
+ *  0      	: The callback is registered.
+ *  EINVAL 	: A parameter is invalid.
+ *  ENOMEM	: Not enough memory to complete the operation
+ */
+int fd_disp_register ( int (*cb)( struct msg **, struct avp *, struct session *, enum disp_action *), 
+			enum disp_how how, struct disp_when * when, struct disp_hdl ** handle );
+
+/*
+ * FUNCTION:	fd_disp_unregister
+ *
+ * PARAMETERS:
+ *  handle       : Location of the handle of the callback that must be unregistered.
+ *
+ * DESCRIPTION: 
+ *   Removes a callback previously registered by fd_disp_register.
+ *
+ * RETURN VALUE:
+ *  0      	: The callback is unregistered.
+ *  EINVAL 	: A parameter is invalid.
+ */
+int fd_disp_unregister ( struct disp_hdl ** handle );
+
+/*
+ * FUNCTION:	fd_msg_dispatch
+ *
+ * PARAMETERS:
+ *  msg 	: A msg object that have already been fd_msg_parse_dict.
+ *  session	: The session corresponding to this object, if any.
+ *  action	: Upon return, the action that must be taken on the message
+ *
+ * DESCRIPTION: 
+ *   Call all handlers registered for a given message.
+ *  The session must have already been resolved on entry.
+ *  The msg pointed may be updated during this process.
+ *  Upon return, the action parameter points to what must be done next.
+ *
+ * RETURN VALUE:
+ *  0      	: Success.
+ *  EINVAL 	: A parameter is invalid.
+ *  (other errors)
+ */
+int fd_msg_dispatch ( struct msg ** msg, struct session * session, enum disp_action *action );
+
+
 
 /*============================================================*/
 /*                    MESSAGE QUEUES                          */
--- a/libfreeDiameter/CMakeLists.txt	Thu Sep 03 16:03:25 2009 +0900
+++ b/libfreeDiameter/CMakeLists.txt	Fri Sep 04 18:05:25 2009 +0900
@@ -6,10 +6,11 @@
 # List of source files for the library
 SET(LFD_SRC
 	libfD.h
+	dictionary.c
+	dispatch.c
 	init.c
+	lists.c
 	log.c
-	lists.c
-	dictionary.c
 	messages.c
 	mqueues.c
 	sessions.c
--- a/libfreeDiameter/dictionary.c	Thu Sep 03 16:03:25 2009 +0900
+++ b/libfreeDiameter/dictionary.c	Fri Sep 04 18:05:25 2009 +0900
@@ -409,9 +409,11 @@
 	}
 	
 	/* Unlink all elements from the dispatch list; they will be freed when callback is unregistered */
+	CHECK_POSIX_DO( pthread_rwlock_wrlock(&fd_disp_lock), /* continue */ );
 	while (!FD_IS_LIST_EMPTY(&obj->disp_cbs)) {
 		fd_list_unlink( obj->disp_cbs.next );
 	}
+	CHECK_POSIX_DO( pthread_rwlock_unlock(&fd_disp_lock), /* continue */ );
 	
 	/* Last, destroy the object */
 	free(obj);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libfreeDiameter/dispatch.c	Fri Sep 04 18:05:25 2009 +0900
@@ -0,0 +1,209 @@
+/*********************************************************************************************************
+* 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 "libfD.h"
+
+/* The dispatch module in the library is quite simple: callbacks are saved in a global list
+ * in no particular order. In addition, they are also linked from the dictionary objects they
+ * refer to. */
+
+/* Protection for the lists managed in this module. */
+pthread_rwlock_t fd_disp_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* List of all registered handlers -- useful if we want to cleanup properly at some point... */
+static struct fd_list all_handlers;
+
+/* List of handlers registered for DISP_HOW_ANY. Other handlers are stored in the dictionary */
+static struct fd_list any_handlers;
+
+/* The structure to store a callback */
+struct disp_hdl {
+	int		 eyec;	/* Eye catcher, DISP_EYEC */
+	struct fd_list	 all;	/* link in the all_handlers list */
+	struct fd_list	 parent;/* link in dictionary cb_list or in any_handlers */
+	enum disp_how	 how;	/* Copy of registration parameter */
+	struct disp_when when;	/* Copy of registration parameter */
+	int		(*cb)( struct msg **, struct avp *, struct session *, enum disp_action *);	/* The callback itself */
+};
+
+#define DISP_EYEC	0xD15241C1
+#define VALIDATE_HDL( _hdl ) \
+	( ( ( _hdl ) != NULL ) && ( ((struct disp_hdl *)( _hdl ))->eyec == DISP_EYEC ) )
+
+/**************************************************************************************/
+/* Initialize the module lists */
+void fd_disp_init(void)
+{
+	TRACE_ENTRY();
+	fd_list_init(&all_handlers, NULL);
+	fd_list_init(&any_handlers, NULL);
+	/* if PTHREAD_RWLOCK_INITIALIZER is not supported on all platforms, we may initialize the lock here */
+}
+
+/* Call CBs from a given list (any_handlers if cb_list is NULL) -- must have locked fd_disp_lock before */
+int fd_disp_call_cb_int( struct fd_list * cb_list, struct msg ** msg, struct avp *avp, struct session *sess, enum disp_action *action, 
+			struct dict_object * obj_app, struct dict_object * obj_cmd, struct dict_object * obj_avp, struct dict_object * obj_enu)
+{
+	struct fd_list * senti, *li;
+	TRACE_ENTRY("%p %p %p %p %p %p %p %p %p", cb_list, msg, avp, sess, action, obj_app, obj_cmd, obj_avp, obj_enu);
+	CHECK_PARAMS(msg && action);
+	
+	senti = cb_list;
+	if (!senti)
+		senti = &any_handlers;
+	
+	for (li = senti->next; li != senti; li = li->next) {
+		struct disp_hdl * hdl = (struct disp_hdl *)(li->o);
+		
+		TRACE_DEBUG(ANNOYING, "when: %p %p %p %p", hdl->when.app, hdl->when.command, hdl->when.avp, hdl->when.value);
+		
+		/* Check this handler matches this message / avp */
+		if (hdl->when.app     && (hdl->when.app     != obj_app))
+			continue;
+		if (hdl->when.command && (hdl->when.command != obj_cmd))
+			continue;
+		if (hdl->when.avp     && (hdl->when.avp     != obj_avp))
+			continue;
+		if (hdl->when.value   && (hdl->when.value   != obj_enu))
+			continue;
+		
+		/* We have a match, the cb must be called. */
+		CHECK_FCT( (*hdl->cb)(msg, avp, sess, action) );
+		
+		if (*action != DISP_ACT_CONT)
+			break;
+		
+		if ( *msg  == NULL )
+			break;
+	}
+	
+	/* We're done on this list */
+	return 0;
+}
+
+/**************************************************************************************/
+
+/* Create a new handler and link it */
+int fd_disp_register ( int (*cb)( struct msg **, struct avp *, struct session *, enum disp_action *), 
+			enum disp_how how, struct disp_when * when, struct disp_hdl ** handle )
+{
+	struct fd_list * cb_list = NULL;
+	struct disp_hdl * new;
+	struct dict_object * type_enum, * type_avp;
+	struct dictionary  * dict = NULL;
+	
+	TRACE_ENTRY("%p %d %p %p", cb, how, when, handle);
+	CHECK_PARAMS( cb && ( (how == DISP_HOW_ANY) || when ));
+	
+	switch (how) {
+		case DISP_HOW_ANY:
+			cb_list = &any_handlers;
+			break;
+		
+		case DISP_HOW_APPID:
+			CHECK_FCT( fd_dict_disp_cb(DICT_APPLICATION, when->app, &cb_list) );
+			break;
+		
+		case DISP_HOW_CC:
+			CHECK_FCT( fd_dict_disp_cb(DICT_COMMAND, when->command, &cb_list) );
+			break;
+		
+		case DISP_HOW_AVP_ENUMVAL:
+			CHECK_FCT( fd_dict_disp_cb(DICT_ENUMVAL, when->value, &cb_list) ); /* cb_list is then overwriten */
+			CHECK_FCT( fd_dict_getdict(when->value, &dict) );
+			CHECK_FCT( fd_dict_search(dict, DICT_TYPE, TYPE_OF_ENUMVAL, when->value, &type_enum, EINVAL) );
+		case DISP_HOW_AVP:
+			CHECK_FCT( fd_dict_disp_cb(DICT_AVP, when->avp, &cb_list) );
+			if (dict) {
+				CHECK_FCT( fd_dict_search(dict, DICT_TYPE, TYPE_OF_AVP, when->avp, &type_avp, EINVAL) );
+				CHECK_PARAMS( type_enum == type_avp );
+			}
+			break;
+		
+		default:
+			CHECK_PARAMS(how = 0);
+	}
+	/* We might further check optional fields, but we trust the caller ^^ */
+	
+	/* Create the new handler */
+	CHECK_MALLOC( new = malloc( sizeof(struct disp_hdl) ) );
+	memset(new, 0, sizeof(struct disp_hdl));
+	new->eyec = DISP_EYEC;
+	fd_list_init(&new->all, new);
+	fd_list_init(&new->parent, new);
+	new->how = how;
+	switch (how) {
+		case DISP_HOW_AVP_ENUMVAL:
+			new->when.value   = when->value;
+		case DISP_HOW_AVP:
+			new->when.avp     = when->avp;
+		case DISP_HOW_CC:
+			new->when.command = when->command;
+		case DISP_HOW_APPID:
+			new->when.app     = when->app;
+	}
+	new->cb = cb;
+	
+	/* Now, link this new element in the appropriate lists */
+	CHECK_POSIX( pthread_rwlock_wrlock(&fd_disp_lock) );
+	fd_list_insert_before(&all_handlers, &new->all);
+	fd_list_insert_before(cb_list, &new->parent);
+	CHECK_POSIX( pthread_rwlock_unlock(&fd_disp_lock) );
+	
+	/* We're done */
+	if (handle)
+		*handle = new;
+	
+	return 0;
+}
+
+/* Delete a handler */
+int fd_disp_unregister ( struct disp_hdl ** handle )
+{
+	struct disp_hdl * del;
+	TRACE_ENTRY("%p", handle);
+	CHECK_PARAMS( handle && VALIDATE_HDL(*handle) );
+	del = *handle;
+	*handle = NULL;
+	
+	CHECK_POSIX( pthread_rwlock_wrlock(&fd_disp_lock) );
+	fd_list_unlink(&del->all);
+	fd_list_unlink(&del->parent);
+	CHECK_POSIX( pthread_rwlock_unlock(&fd_disp_lock) );
+	
+	free(del);
+	return 0;
+}
+
--- a/libfreeDiameter/init.c	Thu Sep 03 16:03:25 2009 +0900
+++ b/libfreeDiameter/init.c	Fri Sep 04 18:05:25 2009 +0900
@@ -48,6 +48,7 @@
 	
 	/* Initialize the modules that need it */
 	fd_msg_eteid_init();
+	fd_disp_init();
 	CHECK_FCT( fd_sess_init() );
 	
 	return 0;
--- a/libfreeDiameter/libfD.h	Thu Sep 03 16:03:25 2009 +0900
+++ b/libfreeDiameter/libfD.h	Fri Sep 04 18:05:25 2009 +0900
@@ -44,11 +44,16 @@
 /* Internal to the library */
 extern const char * type_base_name[];
 void fd_msg_eteid_init(void);
+void fd_disp_init(void);
 int fd_sess_init(void);
 
 /* Iterator on the rules of a parent object */
 int fd_dict_iterate_rules ( struct dict_object *parent, void * data, int (*cb)(void *, struct dict_rule_data *) );
 
-
+/* Dispatch / messages / dictionary API */
+int fd_dict_disp_cb(enum dict_object_type type, struct dict_object *obj, struct fd_list ** cb_list);
+int fd_disp_call_cb_int( struct fd_list * cb_list, struct msg ** msg, struct avp *avp, struct session *sess, enum disp_action *action, 
+			struct dict_object * obj_app, struct dict_object * obj_cmd, struct dict_object * obj_avp, struct dict_object * obj_enu);
+extern pthread_rwlock_t fd_disp_lock;
 
 #endif /* _LIBFD_H */
--- a/libfreeDiameter/messages.c	Thu Sep 03 16:03:25 2009 +0900
+++ b/libfreeDiameter/messages.c	Fri Sep 04 18:05:25 2009 +0900
@@ -2137,3 +2137,97 @@
 }
 
 /***************************************************************************************************************/
+/* Macro to check if further callbacks must be called */
+#define TEST_ACTION_STOP()					\
+	if ((*msg == NULL) || (*action != DISP_ACT_CONT))	\
+		goto no_error;
+
+/* Call all dispatch callbacks for a given message */
+int fd_msg_dispatch ( struct msg ** msg, struct session * session, enum disp_action *action)
+{
+	struct dictionary  * dict;
+	struct dict_object * app;
+	struct dict_object * cmd;
+	struct avp * avp;
+	struct fd_list * cb_list;
+	int ret = 0;
+	
+	TRACE_ENTRY("%p %p %p", msg, session, action);
+	CHECK_PARAMS( msg && CHECK_MSG(*msg) && action);
+	
+	/* Take the dispatch lock */
+	CHECK_FCT( pthread_rwlock_rdlock(&fd_disp_lock) );
+	pthread_cleanup_push( fd_cleanup_rwlock, &fd_disp_lock );
+	
+	/* First, call the DISP_HOW_ANY callbacks */
+	CHECK_FCT_DO( ret = fd_disp_call_cb_int( NULL, msg, NULL, session, action, NULL, NULL, NULL, NULL ), goto error );
+
+	TEST_ACTION_STOP();
+	
+	/* If we don't know the model at this point, we stop cause we cannot get the dictionary. It's invalid: an error should already have been trigged by ANY callbacks */
+	CHECK_PARAMS_DO(cmd = (*msg)->msg_model, { ret = EINVAL; goto error; } );
+	
+	/* Now resolve message application */
+	CHECK_FCT_DO( ret = fd_dict_getdict( cmd, &dict ), goto error );
+	CHECK_FCT_DO( ret = fd_dict_search( dict, DICT_APPLICATION, APPLICATION_BY_ID, &(*msg)->msg_public.msg_appl, &app, 0 ), goto error );
+	
+	if (app == NULL) {
+		/* In that case, maybe we should answer a DIAMETER_APPLICATION_UNSUPPORTED error ? Do we do this here ? */
+		TRACE_DEBUG(NONE, "Reply DIAMETER_APPLICATION_UNSUPPORTED if it's a request ?");
+	}
+	
+	/* So start browsing the message */
+	CHECK_FCT_DO( ret = fd_msg_browse( *msg, MSG_BRW_FIRST_CHILD, &avp, NULL ), goto error );
+	while (avp != NULL) {
+		/* Sanity */
+		ASSERT( avp->avp_public.avp_value );
+		
+		/* For unknown AVP, we don't have a callback registered, so just skip */
+		if (avp->avp_model) {
+			struct dict_object * type, * enumval;
+			
+			/* Get the list of callback for this AVP */
+			CHECK_FCT_DO( ret = fd_dict_disp_cb(DICT_AVP, avp->avp_model, &cb_list), goto error );
+			
+			/* Check if the AVP has a constant value */
+			CHECK_FCT_DO( ret = fd_dict_search(dict, DICT_TYPE, TYPE_OF_AVP, avp->avp_model, &type, 0), goto error );
+			if (type) {
+				struct dict_enumval_request req;
+				memset(&req, 0, sizeof(struct dict_enumval_request));
+				req.type_obj = type;
+				memcpy( &req.search.enum_value, avp->avp_public.avp_value, sizeof(union avp_value) );
+				CHECK_FCT_DO( ret = fd_dict_search(dict, DICT_ENUMVAL, ENUMVAL_BY_STRUCT, &req, &enumval, 0), goto error );
+			} else {
+				/* No enumerated value in this case */
+				enumval = NULL;
+			}
+			
+			/* Call the callbacks */
+			CHECK_FCT_DO( ret = fd_disp_call_cb_int( cb_list, msg, avp, session, action, app, cmd, avp->avp_model, enumval ), goto error );
+			TEST_ACTION_STOP();
+		}
+		/* Go to next AVP */
+		CHECK_FCT_DO(  ret = fd_msg_browse( avp, MSG_BRW_WALK, &avp, NULL ), goto error );
+	}
+		
+	/* Now call command and application callbacks */
+	CHECK_FCT_DO( ret = fd_dict_disp_cb(DICT_COMMAND, cmd, &cb_list), goto error );
+	CHECK_FCT_DO( ret = fd_disp_call_cb_int( cb_list, msg, NULL, session, action, app, cmd, NULL, NULL ), goto error );
+	TEST_ACTION_STOP();
+	
+	if (app) {
+		CHECK_FCT_DO( ret = fd_dict_disp_cb(DICT_APPLICATION, app, &cb_list), goto error );
+		CHECK_FCT_DO( ret = fd_disp_call_cb_int( cb_list, msg, NULL, session, action, app, cmd, NULL, NULL ), goto error );
+		TEST_ACTION_STOP();
+	}
+	
+	pthread_cleanup_pop(0);
+	
+no_error:
+	CHECK_POSIX(pthread_rwlock_unlock(&fd_disp_lock) );
+	return 0;
+	
+error:
+	CHECK_POSIX_DO(pthread_rwlock_unlock(&fd_disp_lock), /* ignore */ );
+	return ret;
+}
--- a/libfreeDiameter/sessions.c	Thu Sep 03 16:03:25 2009 +0900
+++ b/libfreeDiameter/sessions.c	Fri Sep 04 18:05:25 2009 +0900
@@ -104,7 +104,7 @@
 /* Sessions hash table, to allow fast sid to session retrieval */
 static struct {
 	struct fd_list	sentinel;	/* sentinel element for this sublist */
-	pthread_mutex_t lock;		/* the mutex for this sublist */
+	pthread_mutex_t lock;		/* the mutex for this sublist -- we might probably change it to rwlock for a little optimization */
 } sess_hash [ 1 << SESS_HASH_SIZE ] ;
 #define H_MASK( __hash ) ((__hash) & (( 1 << SESS_HASH_SIZE ) - 1))
 #define H_LIST( _hash ) (&(sess_hash[H_MASK(_hash)].sentinel))
@@ -112,7 +112,7 @@
 
 /* The following are used to generate sid values that are eternaly unique */
 static uint32_t   	sid_h;	/* initialized to the current time in fd_sess_init */
-static uint32_t   	sid_l;	/* incremented each time a session id is created -- could use atomic operation probably */
+static uint32_t   	sid_l;	/* incremented each time a session id is created */
 static pthread_mutex_t 	sid_lock = PTHREAD_MUTEX_INITIALIZER;
 
 /* Expiring sessions management */
@@ -129,7 +129,7 @@
 
 /********************************************************************************************************/
 
-/* Initialize a session object. It is not linked now. sid must be already alloc'ed. */
+/* Initialize a session object. It is not linked now. sid must be already malloc'ed. */
 static struct session * new_session(char * sid, size_t sidlen)
 {
 	struct session * sess;
@@ -156,9 +156,6 @@
 	return sess;
 }
 	
-
-
-
 /* The expiry thread */
 static void * exp_fct(void * arg)
 {
"Welcome to our mercurial repository"