view waaad/peer-psm.c @ 416:5c5d72fd7f2a

Added more verbose information on connection message
author Sebastien Decugis <sdecugis@nict.go.jp>
date Tue, 16 Jun 2009 13:55:52 +0900
parents e86dba02630a
children
line wrap: on
line source

/*********************************************************************************************************
* 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.								 *
*********************************************************************************************************/

/* Peers facility.
 *
 * This file contains the code of the peer state machine.
 *
 */

#include "waaad-internal.h"
#include "peer-internal.h"

STATESTR_DEFINITION;

#ifndef IN_WAAAD_TEST
static 
#endif /* IN_WAAAD_TEST */
void _psm_change_state(_peer_t * peer, _peer_state_t new);
static void _psm_reset_ts(_peer_t * peer, int add_random, int delay);
static void _psm_cleanup( _peer_t * peer );
static int _psm_answer_CEA_simple( _peer_t * peer, _pevent_t event, void * ev_data );
static int _psm_do_watchdog( _peer_t * peer, _pevent_t event, void * ev_data );


/* Election */
static int _psm_election( _peer_t * peer )
{
	TRACE_ENTRY("%p(%s/%s)", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state));
	
	/* Sanity check */
	ASSERT( peer && peer->p_electdata );
	
	/* Now compare the Diameter-Ids according to RFC:
		   The election is performed on the responder.  The responder compares
		   the Origin-Host received in the CER with its own Origin-Host as two
		   streams of octets.  If the local Origin-Host lexicographically
		   succeeds the received Origin-Host a Win-Election event is issued
		   locally.  Diameter identities are in ASCII form therefore the lexical
		   comparison is consistent with DNS case insensitivity where octets
		   that fall in the ASCII range 'a' through 'z' MUST compare equally to
		   their upper-case counterparts between 'A' and 'Z'.  See Appendix D
		   for interactions between the Diameter protocol and Internationalized
		   Domain Name (IDNs).

		   The winner of the election MUST close the connection it initiated.
		   Historically, maintaining the responder side of a connection was more
		   efficient than maintaining the initiator side.  However, current
		   practices makes this distinction irrelevant.
	*/
	
	if (strcasecmp(peer->p_electdata->peer->p_diamid, g_pconf->diameter_identity) < 0) {
	
		/* WIN ELECTION */
		_ev_incoming_t * inc = peer->p_electdata;
		peer->p_electdata = NULL;
		
		TRACE_DEBUG(FULL, "Election to peer '%s' was won, closing initiated connection...", peer->p_diamid);
		
		/* Then process the receiver connection, as if it was a new one */
		return _psm_answer_CEA_simple( peer, PEVENT_INCNX, inc );
	}
	
	TRACE_DEBUG(FULL, "Election to peer '%s' was lost, waiting for next events...", peer->p_diamid);
	
	/* Election is lost, just wait for further events */
	return 0;
}

/******************************************************************************/
/*  The callbacks that are called when an event is received                   */
/******************************************************************************/

/* For events that we don't handle yet */
static int _psm_NOTSUP( _peer_t * peer, _pevent_t event, void * ev_data )
{
	TRACE_DEBUG(INFO, "'%s' in state '%s', HANDLER FOR '%s' NOT IMPLEMENTED", peer->p_diamid, STATESTR(peer->p_state), EVENTSTR(event));
	_psm_reset_ts(peer, 0, 5);
	return 0;
}

/* callback that should never be called */
static int _psm_assert( _peer_t * peer, _pevent_t event, void * ev_data )
{
	TRACE_DEBUG(INFO, "Bad situation on peer state machine '%s'!!!", peer->p_diamid);
	ASSERT(0);
	return 2;
}

/* For events that are ignored */
static int _psm_ignore( _peer_t * peer, _pevent_t event, void * ev_data )
{
	TRACE_DEBUG(FULL, "'%s' in state '%s', ignoring event '%s'...", peer->p_diamid, STATESTR(peer->p_state), EVENTSTR(event));
	return 0;
}


/* Restart the PSM from a clean state */
static int _psm_reset( _peer_t * peer, _pevent_t event, void * ev_data )
{
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* Handle some simple events */
	if (event == PEVENT_SHUTDOWN)
		peer->p_flags |= PEERFL_DISABLE_AFTER_SHUTDOWN;
	
	if (event == PEVENT_DESTROY)
		peer->p_flags |= PEERFL_DESTROY_AFTER_SHUTDOWN;
	
	
	if (((peer->p_state == STATE_SUSPECT) && (event == PEVENT_TIMEOUT))
	  || (event == PEVENT_DISCONNECTED)
	  || (event == PEVENT_SND_FAILED)
	  || (event == PEVENT_TH_TERM_IN)
	  || (event == PEVENT_TH_TERM_OUT)
	   )
		peer->p_flags |= PEERFL_CNX_PB;
	
	/* All is done by just returning 1 here */
	return 1;
}

/* The lifetime of the peer entry is terminated, we must expire it or refresh it */
static int _psm_expire( _peer_t * peer, _pevent_t event, void * ev_data )
{
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* @@ TODO */
	TRACE_DEBUG(INFO, "Dynamic peers: Not implemented yet, will just destroy the peer now...");
	return 2;
}


/* Attempt a new connection */
static int _psm_connect( _peer_t * peer, _pevent_t event, void * ev_data )
{
	uint16_t port;
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	CHECK_PARAMS_DO( peer->p_diamid, return 2 );
	
	if (peer->p_addinfo.pa_ss.ss_family == AF_INET) {
		port = ntohs( ((sSA4 *)(&peer->p_addinfo.pa_ss))->sin_port );
	} else {
		port = ntohs( ((sSA6 *)(&peer->p_addinfo.pa_ss))->sin6_port );
	}
	
	TRACE_DEBUG(INFO, "Connecting to peer '%s' on port '%s:%hd'...", peer->p_diamid, peer->p_addinfo.pa_proto == IPPROTO_SCTP ? "STCP" : "TCP", port);
	
	/* go to STATE_WAITCNXACK state */
	_psm_change_state(peer, STATE_WAITCNXACK);
	
	/* Start a thread to attempt new connection. That thread will send an event upon success, or be canceled on timeout. */
	CHECK_FCT_DO(  _peer_client_start(peer), return 1  );
	
	/* and re-arm the timer */
	_psm_reset_ts(peer, 0, CNX_TIMEOUT);

	return 0;
}

/* Send a CER message */
static int _psm_send_CER( _peer_t * peer, _pevent_t event, void * ev_data )
{
	msg_t * cer = NULL;
	_ev_connected_t * con_data = (_ev_connected_t *)ev_data;
	int in_elec = 0;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* We are called when we have been connected. do some sanity checks for debug */
	ASSERT( event == PEVENT_CONNECTED );
	ASSERT( con_data );
	ASSERT( (peer->p_state == STATE_WAITCNXACK) || (peer->p_state == STATE_WAITCNXACK_ELEC) );
	
	/* Initialize the connection data */
	peer->p_sock = con_data->sock;
	peer->p_ostr = con_data->ostr;
	peer->p_istr = con_data->istr;
	peer->p_curstr = 0;
	SEC_SESS_INIT( &peer->p_sec_session, con_data->proto, peer, &peer->p_sock );
	
	/* we don't need the event data anymore, free it */
	free(ev_data);
	
	/* Start the communication threads */
	CHECK_FCT_DO( _peer_in_start(peer),  return 1 );
	CHECK_FCT_DO( _peer_out_start(peer),  return 1 );
	
	/* Create the CER message */
	CHECK_FCT_DO( _peer_cer_create(peer, &cer), return 2 );
	
	/* Send the CER */
	CHECK_FCT_DO(  meq_post( peer->p_out_q, cer ),  return 1  ); 
	
	in_elec = (peer->p_state == STATE_WAITCNXACK_ELEC);

	/* go to next state */
	_psm_change_state(peer, STATE_WAITCEA);
	
	/* and re-arm the timer */
	_psm_reset_ts(peer, 0, CEA_TIMEOUT);

	if (in_elec) {
		/* DO election; we now have all needed information to proceed */
		return _psm_election(peer);
	}
	
	return 0;
}

/* Received a CER, must check for common security and applications and answer */
static int _psm_answer_CEA_simple( _peer_t * peer, _pevent_t event, void * ev_data )
{
	_ev_incoming_t * inc = (_ev_incoming_t *)ev_data;
	msg_t * cea = NULL;
	uint32_t hbhid, eteid;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* We are called when we have been connected. do some sanity checks for debug */
	ASSERT( inc && VALIDATE_PEER(inc->peer) );
	ASSERT( peer->p_sec_hdl == NULL );
	
	/* Cleanup the peer */
	_psm_cleanup( peer );
	peer->p_flags |= PEERFL_RESPONDER;
	
	/* Push two cancelation handlers to free the event data in case of we are cancelled */
	pthread_cleanup_push( cleanup_buffer, inc );
	pthread_cleanup_push( (void (*)(void *))_peer_struct_destroy, &inc->peer );
	
	/* The inc->peer object already contains all parsed data from the received CER, copy this data into the main peer structure */
	free(peer->p_realm); peer->p_realm = inc->peer->p_realm; inc->peer->p_realm = NULL;
	free(peer->p_peeraddr); peer->p_peeraddr = inc->peer->p_peeraddr;
		peer->p_peeraddr_sz = inc->peer->p_peeraddr_sz;
		inc->peer->p_peeraddr_sz = 0; inc->peer->p_peeraddr = NULL;
	free(peer->p_app_list); peer->p_app_list = inc->peer->p_app_list;
		peer->p_app_size = inc->peer->p_app_size;
		peer->p_app_relay = inc->peer->p_app_relay;
		inc->peer->p_app_size = 0; inc->peer->p_app_list = NULL;
		inc->peer->p_app_relay = 0;
		
	peer->p_vendor = inc->peer->p_vendor;
	free(peer->p_prodname); peer->p_prodname = inc->peer->p_prodname; inc->peer->p_prodname = NULL;
	peer->p_firmrev = inc->peer->p_firmrev;
	peer->p_orstate = inc->peer->p_orstate;
	
	peer->p_sock = inc->peer->p_sock; inc->peer->p_sock = 0;
	peer->p_ostr = inc->peer->p_ostr;
	peer->p_istr = inc->peer->p_istr;
	peer->p_addinfo.pa_proto = inc->peer->p_addinfo.pa_proto;
	peer->p_secmod = &_peer_secini_mod;
	SEC_SESS_INIT( &peer->p_sec_session, peer->p_addinfo.pa_proto, peer, &peer->p_sock );
	
	hbhid = inc->hbh;
	eteid = inc->ete;
	
	/* Get a list of security modules that can handle the remote peer */
	CHECK_FCT_DO( sec_getmodules(peer->p_diamid, (sSA *)&(inc->peer->p_addinfo.pa_ss), &inc->peer->p_sec_list), goto error );
	while (! IS_LIST_EMPTY(&inc->peer->p_sec_list)) {
		_sec_item_t * si = (_sec_item_t *)(inc->peer->p_sec_list.next);
		uti_list_unlink(_LIST(si));
		
		/* Was this module asked by the remote peer? */
		if ((peer->p_sec_hdl == NULL) && ( _peer_struct_secid_check(inc->peer, ((sec_module_t *)(si->chain.o))->sec_insecid)) ) {
			peer->p_sec_hdl = si->hdl;
			/* peer->p_secmod will be changed *after* the CEA is sent */
		} else {
			CHECK_FCT_DO( sec_modunlink(si->hdl), /* continue */ );
		}
		
		free(si);	
	}
	/* also free the saved security list stored locally, if any */
	while ( ! IS_LIST_EMPTY( &peer->p_sec_list ) ) {
		uti_list_t * next = peer->p_sec_list.next;
		uti_list_unlink(next);
		free(next);
	}
	
	/* From this point we don't need the inc anymore */
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);

	/* Create a CEA message */
	CHECK_FCT_DO( _peer_cea_create( peer, &cea, eteid, hbhid ), goto error );
	pthread_cleanup_push( cleanup_msg, cea );
	
	/* At this point, if peer->p_sec_hdl is NULL, it means we have no common security, otherwise it points to our most prioritary common module */
	if (peer->p_sec_hdl == NULL) {
		
		CHECK_FCT_DO( msg_rescode_set( cea, "DIAMETER_NO_COMMON_SECURITY", NULL, NULL, 0 ), goto error );
		
		/* send the message (the out thread is not running at this time) */
		CHECK_FCT_DO( _peer_out_sendmsg( peer, cea ), goto error );
		
		/* Free the message and the parsed data */
		goto error;
	}
	
	/* Now check for a common application, or send NO_COMMON_APPLICATION message */
	if (g_pconf->disable_relay && !peer->p_app_relay) {
		int found = 0;
		int ix_loc = 0;
		int ix_rem = 0;
		
		/* Search at least one common application. Both arrays are ordered. */
		while ((ix_loc < g_conf->supported_apps_nb) && (ix_rem < peer->p_app_size)) {
			if (g_conf->supported_apps_list[ ix_loc ].a == peer->p_app_list[ ix_rem ].a) {
				TRACE_DEBUG(INFO, "Found at least one common application with peer '%s': %u", peer->p_diamid, peer->p_app_list[ ix_rem ].a);
				found = 1;
				break;
			}
			if (g_conf->supported_apps_list[ ix_loc ].a < peer->p_app_list[ ix_rem ].a)
				ix_loc++;
			else
				ix_rem++;
		}
		
		if (!found) {
			CHECK_FCT_DO( msg_rescode_set( cea, "DIAMETER_NO_COMMON_APPLICATION", NULL, NULL, 0 ), goto error );

			/* send the message (the out thread is not running at this time) */
			CHECK_FCT_DO( _peer_out_sendmsg( peer, cea ), goto error );

			/* Free the message and the parsed data */
			goto error;
		}
	}
	
	/* The CE is successful */
	CHECK_FCT_DO( msg_rescode_set( cea, "DIAMETER_SUCCESS", NULL, NULL, 0 ), goto error );
	
	/* Send the CEA */
	TRACE_DEBUG(FULL, "Sending CEA to '%s', status success", peer->p_diamid);
	CHECK_FCT_DO( _peer_out_sendmsg( peer, cea ), goto error );
	
	/* Free the CEA */
	pthread_cleanup_pop(1);
	
	/* now activate the security module and start the in/out threads */
	CHECK_FCT(  sec_getmodfromhdl(peer->p_sec_hdl, &peer->p_secmod)  );
	
	CHECK_FCT( _peer_in_start(peer) );
	CHECK_FCT( _peer_out_start(peer) );
	
	/* go to OPEN or REOPEN state */
	/* This will also trig the security initialization. If TLS negotiation fails, an event will be trigged. */
	if (peer->p_flags & PEERFL_CNX_PB) {
		
		_psm_change_state(peer, STATE_REOPEN);
		
		/* Now send a DWR */
		return _psm_do_watchdog(peer, event, ev_data );
		
	}
	
	_psm_reset_ts(peer, 1, g_pconf->twtimer);
	
	_psm_change_state(peer, STATE_OPEN);
	
	return 0;
	
error:
	if (inc) {
		if (inc->peer)
			_peer_struct_destroy(&inc->peer);
		free(inc);
		inc = NULL;
	}
	if (cea) {
		CHECK_FCT_DO( msg_free(cea, 1), /* continue */ );
	}
	return 1;
}

/* Received a CER on new connection that we reject */
static int _psm_reject_incoming_CER( _peer_t * peer, _pevent_t event, void * ev_data )
{
	_ev_incoming_t * inc = (_ev_incoming_t *)ev_data;
	msg_t * cea = NULL;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	ASSERT( event == PEVENT_INCNX );
	ASSERT( inc && VALIDATE_PEER(inc->peer) );
	
	/* Push two cancelation handlers to free the event data in case of we are cancelled */
	pthread_cleanup_push( cleanup_buffer, inc );
	pthread_cleanup_push( (void (*)(void *))_peer_struct_destroy, &inc->peer );
	
	/* The inc->peer object already contains all parsed data from the received CER, copy this data into the main peer structure */
	
	/* Create a CEA message */
	CHECK_FCT_DO( _peer_cea_create( inc->peer, &cea, inc->ete, inc->hbh ), goto error );
	pthread_cleanup_push( cleanup_msg, cea );
	
	/* Now set the error code */
	CHECK_FCT_DO( msg_rescode_set( cea, "DIAMETER_UNABLE_TO_COMPLY", NULL, NULL, 0 ), goto error );
		
	/* send the message (the out thread is not running at this time) */
	CHECK_FCT_DO( _peer_out_sendmsg( inc->peer, cea ), goto error );
	
	/* Free everything */
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	pthread_cleanup_pop(1);
	
	/* If we were in SUSPECT state, we may want to reset the peer now? Ignore for now ... */
	
	return 0;
	
error:
	if (inc) {
		if (inc->peer)
			_peer_struct_destroy(&inc->peer);
		free(inc);
		inc = NULL;
	}
	if (cea) {
		CHECK_FCT_DO( msg_free(cea, 1), /* continue */ );
	}
	return 1;
}

/* Save incoming connection data for the upcoming election */
static int _psm_incon_elect( _peer_t * peer, _pevent_t event, void * ev_data )
{
	_ev_incoming_t * inc = (_ev_incoming_t *)ev_data;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	ASSERT( inc && VALIDATE_PEER(inc->peer) );
	
	/* For now, just save the event data in the peer structure */
	ASSERT( peer->p_electdata == NULL );
	peer->p_electdata = inc;
	
	/* And change state accordingly to previous state */
	ASSERT( (peer->p_state == STATE_WAITCNXACK) || (peer->p_state == STATE_WAITCEA) );
	
	if (peer->p_state == STATE_WAITCEA) {
		return _psm_election(peer);
	}
	
	_psm_change_state(peer, STATE_WAITCNXACK_ELEC);
	
	return 0;
}

/* Timeout during election process, fallback to the responder side */
static int _psm_elect_timeout( _peer_t * peer, _pevent_t event, void * ev_data )
{
	_ev_incoming_t * inc = peer->p_electdata;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	if (inc == NULL) {
		/* We have no responder-side connection, just reset the peer */
		return _psm_reset(peer, event, ev_data);
	}
	
	peer->p_electdata = NULL;
	
	/* Process as with a fresh incoming connection event */
	return _psm_answer_CEA_simple( peer, PEVENT_INCNX, inc );
}

/* A message has been received while we were waiting for a CEA */
static int _psm_receive_CEA( _peer_t * peer, _pevent_t event, void * ev_data )
{
	msg_t * cea = NULL;

	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* Do some sanity checks for debug */
	ASSERT( event == PEVENT_MSGRCVD );
	ASSERT( peer->p_state == STATE_WAITCEA );
	ASSERT( peer->p_sec_hdl == NULL );
	
	/* Retrieve the CEA from the incoming queue */
	CHECK_FCT_DO(  meq_tryget(peer->p_in_q, &cea),  return 1  );
	pthread_cleanup_push( cleanup_msg, cea );
	
	TRACE_DEBUG(FULL, "Received this message from '%s':", peer->p_diamid);
	msg_dump_walk(FULL, cea);
	
	/* Check we can understand this message */
	CHECK_FCT_DO(  msg_parse_dict(cea), goto no_cea  );

	/* Check it is really a CEA */
	{
		msg_data_t *mdata;

		CHECK_FCT_DO(  msg_data( cea, &mdata ), goto no_cea  );

		if (mdata->msg_appl != 0) {
			log_error("Received message with application %u while waiting for CEA, closing...\n", mdata->msg_appl);
			goto no_cea;
		}
		if (mdata->msg_code != CC_CAPABILITIES_EXCHANGE) {
			log_error("Received (%u) instead of CEA, closing...\n", mdata->msg_code);
			goto no_cea;
		}

		/* Now check we support all mandatory AVPs */
		CHECK_FCT_DO(  msg_parse_rules(cea, NULL), goto no_cea  );
	}
	
	/* Now parse the CEA. If an error occurs, the function returns an error and we will reset the PSM */
	CHECK_FCT_DO( _peer_cea_parse( cea, peer ), goto no_cea  );
	
	/* Free the CEA */
	pthread_cleanup_pop(1);
	
	TRACE_DEBUG(FULL, "Stopping in/out threads with default sec module");
	CHECK_FCT( _peer_in_stop(peer) );
	CHECK_FCT( _peer_out_stop(peer) );
	
	/* now activate the security module and start the in/out threads */
	CHECK_FCT(  sec_getmodfromhdl(peer->p_sec_hdl, &peer->p_secmod)  );
	
	TRACE_DEBUG(FULL, "Starting in/out threads with correct sec module");
	CHECK_FCT( _peer_in_start(peer) );
	CHECK_FCT( _peer_out_start(peer) );
	
	/* go to OPEN state */
	_psm_change_state(peer, STATE_OPEN);
	/* This will also trig the security initialization. If TLS negotiation fails, an event will be trigged. */
	
	/* reset the timer for DWR: twtimer */
	_psm_reset_ts(peer, 1, g_pconf->twtimer);
	
	return 0;
	
no_cea:
	TRACE_DEBUG(INFO, "Did not receive a CEA on initiated connection, resetting");
	
	if (cea) {
		CHECK_FCT_DO( msg_free(cea, 1), /* continue */ );
	}
	
	return 1; /* will reset the peer */
}

/* A message has been received, we must handle application 0 and forward others to the routing module (we are in open state) */
static int _psm_receive_message( _peer_t * peer, _pevent_t event, void * ev_data )
{
	msg_t * msg = NULL;
	msg_data_t *mdata = NULL;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* Do some sanity checks for debug */
	ASSERT( event == PEVENT_MSGRCVD );
	
	/* Retrieve the new message from the incoming queue */
	CHECK_FCT_DO(  meq_tryget(peer->p_in_q, &msg),  return 1  );
	pthread_cleanup_push( cleanup_msg, msg );
	
	TRACE_DEBUG(FULL, "Received this message from '%s':", peer->p_diamid);
	msg_dump_walk(FULL, msg);
	
	/* If the application id is not 0, or the message is proxyable, directly requeue in global incoming queue. */
	CHECK_FCT_DO(  msg_data( msg, &mdata ), goto error  );
	if ((mdata->msg_flags & CMD_FLAG_PROXIABLE) || (mdata->msg_appl != 0)) {
forward:
		
		/* Save the source information with the message */
		CHECK_FCT_DO(  msg_source_set( msg, peer->p_diamid, peer->p_hash, /* mdata->msg_flags & CMD_FLAG_REQUEST */ /* we actually always add the route-record, even for answers */ 1 ),
			{
				CHECK_FCT_DO(  msg_free(msg, 1), /* nothing */ );
				goto error;
			} );
		
		/* Requeue in global queue */
		CHECK_FCT_DO(  meq_post(g_meq_incoming, msg),  goto error  );
		
		goto end;
	}
	
	/* The application is Base Diameter Protocol, and the message is not proxyable, we must handle it in this PSM */
	
	/* Check we can understand this message */
	CHECK_FCT_DO(  msg_parse_dict(msg), 
		{
			log_error("A message with application 0 received from '%s' was not understood and dropped.\n", peer->p_diamid);
			msg_dump_walk(0, msg);
			goto error;
		}  );

	/* Check that the message is conformant to our ABNF */
	CHECK_FCT_DO(  msg_parse_rules(msg, NULL),
		{
			TRACE_DEBUG(INFO, "TODO: retrieve the conflicting AVP and generate an error in case msg is a query");
			log_error("A message with application 0 received from '%s' was not conforming to ABNF and dropped.\n", peer->p_diamid);
			msg_dump_walk(0, msg);
			msg_free(msg, 1);
			goto end;
		}  );
	
	switch (mdata->msg_code) {
		case CC_CAPABILITIES_EXCHANGE:
			/* We do not handle additional CER/CEA messages (no renegotiation). */
			log_error("CER/CEA message received from peer '%s' in state %s (not supported), resetting connection...", peer->p_diamid, STATESTR(peer->p_state));
			/* Note: the initial CER is received in server thread; the initial CEA is received in WAITCEA state in _psm_receive_CEA */
			msg_dump_walk(0, msg);
			goto error;
		
		case CC_DISCONNECT_PEER:
			if (mdata->msg_flags & CMD_FLAG_REQUEST) {
				msg_t * dpa = NULL;
				int qlen = 0;
				/* We have received a DPR, check if we have any pending message and reply */
				/* Actually, we should also check that any message was sent recently according to RFC:
				
					"					Upon receipt of the message, the
					   Disconnect-Peer-Answer is returned, which SHOULD contain an error if
					   messages have recently been forwarded, and are likely in flight,
					   which would otherwise cause a race condition."
					   
				   but checking we have no pending outgoing message is enough for the moment... */
				CHECK_FCT_DO( meq_length ( peer->p_out_q, &qlen ),
						{
							/* The queue must be corrupt, better to reset the peer */
							goto error;
						} );
				
				/* Check the received DPR is valid */
				CHECK_FCT_DO( _peer_dpr_parse(msg, peer), goto error );
				
				/* Now create the DPA */
				CHECK_FCT_DO( _peer_dpa_create(peer, &dpa, mdata->msg_eteid, mdata->msg_hbhid), goto error );
				
				/* We can discard the incoming DPR, we don't need it anymore */
				msg_free(msg, 1);
				
				/* Set the error code according to the length of the out queue */
				if (qlen == 0) {
					/* The queue was empty, ok to disconnect */
					CHECK_FCT_DO( msg_rescode_set( dpa, "DIAMETER_SUCCESS", NULL, NULL, 0 ),
						{ msg_free(dpa, 1); return 1; } );
				} else {
					/* The queue is not empty, refuse the disconnection */
					CHECK_FCT_DO( msg_rescode_set( dpa, "DIAMETER_UNABLE_TO_COMPLY", "Pending message(s)", NULL, 0 ),
						{ msg_free(dpa, 1); return 1; } );
				}
				
				/* and queue the message for delivery */
				CHECK_FCT_DO( meq_post( peer->p_out_q, dpa ),
					{ msg_free(dpa, 1); return 1; } );
				
				/* Now go to CLOSING state */
				_psm_change_state(peer, STATE_CLOSING);
				
			} else {
				/* We received a DPA. Did we really ask for disconnection? */
				if ( ! (peer->p_flags & PEERFL_SHUTTING_DOWN)) {
					log_error("Received DPA from '%s' but no DPR was sent, ignored", peer->p_diamid);
					msg_free(msg, 1);
				} else {
					/* The DPA was expected */
					int ret = 0;
					
					CHECK_FCT_DO(ret = _peer_dpa_parse(msg, peer), /* continue */);
					
					msg_free(msg, 1);

					if (ret == 0) {
						/* The disconnection was accepted */
						_psm_change_state(peer, STATE_CLOSING);
						return 1;
					} else {
						/* The remote peer refused the disconnection... What should we do? */
						
						/* For the moment, just leave the connection open... we might force killing the peer later with a timeout */
						return 0;
					}
				}
			}
			break;
			
		case CC_DEVICE_WATCHDOG:
			if (mdata->msg_flags & CMD_FLAG_REQUEST) {
				msg_t * dwa = NULL;
				/* We received a DWR, we must answer a DWA */
				
				/* Check the received DWR is valid */
				CHECK_FCT_DO( _peer_dwr_parse(msg, peer), goto error );
				
				/* Now create the DWA */
				CHECK_FCT_DO( _peer_dwa_create(peer, &dwa, mdata->msg_eteid, mdata->msg_hbhid), goto error );
				
				/* We can discard the incoming DWR, we don't need it anymore */
				msg_free(msg, 1);
				
				/* Set the error code to OK */
				CHECK_FCT_DO( msg_rescode_set( dwa, "DIAMETER_SUCCESS", NULL, NULL, 0 ),
					{ msg_free(dwa, 1); return 1; } );
				
				/* and queue the message for delivery */
				CHECK_FCT_DO( meq_post( peer->p_out_q, dwa ),
					{ msg_free(dwa, 1); return 1; } );
				
				TRACE_DEBUG(FULL, "DWA sent back");
				
			} else {
				/* If we received a DWA, clear PEERFL_DW_PENDING */
				
				CHECK_FCT_DO( _peer_dwa_parse(msg, peer), 
					{ msg_free(msg, 1); return 1; } );
				
				msg_free(msg, 1);
				
				peer->p_flags &= ~PEERFL_DW_PENDING;
				
				if ((peer->p_state == STATE_SUSPECT) || (peer->p_state == STATE_REOPEN)) {
					int cnt = 1;
					
					/* check the counter */
					cnt += (peer->p_flags & PEERFL_DW_COUNTER_Hi) ? 2 : 0;
					cnt += (peer->p_flags & PEERFL_DW_COUNTER_Lo) ? 1 : 0;
					
					if (cnt < 4) {
						/* Update the counter and send another DWR */
						peer->p_flags &= ~ ( PEERFL_DW_COUNTER_Lo | PEERFL_DW_COUNTER_Hi );
						
						if (cnt & 2)
							peer->p_flags |= PEERFL_DW_COUNTER_Hi;
						if (cnt & 1)
							peer->p_flags |= PEERFL_DW_COUNTER_Lo;
						
						return _psm_do_watchdog(peer, event, ev_data );
					}
					
					/* Ok, we exchanged 3 DWR/DWA without any timeout, the peer is responding properly */
					_psm_change_state(peer, STATE_OPEN);
				}
						
			}
			break;
			
			
		default:
			TRACE_DEBUG(INFO, "Not supported in PSM: command code %u, dealing with it in routing thread", mdata->msg_code);
			goto forward;
	}
end:	
	/* reset the timer for DWR if needed: twtimer */
	if (! (peer->p_flags & PEERFL_DW_PENDING)) {
		_psm_reset_ts(peer, 1, g_pconf->twtimer);
	}

	return 0;
error:
	/* empty line to make gcc happy... */;
	pthread_cleanup_pop(1);
	return 1;
}


/* long time since data was received */
static int _psm_do_watchdog( _peer_t * peer, _pevent_t event, void * ev_data )
{
	int timer = 0;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	/* Special case: if we are doing a shutdown of the peer, just really shutdown */
	if (peer->p_flags & PEERFL_SHUTTING_DOWN) {
		TRACE_DEBUG(FULL, "Timeout while shutting down, reset the peer");
		return 1;
	}

	/* Have we already sent a DWR? */
	if (peer->p_flags & PEERFL_DW_PENDING) {
		/* Ok, the remote peer did not answer in the given time, let's failover */
		
		/* Mark the peer as suspect. This also trigs a failover */
		_psm_change_state(peer, STATE_SUSPECT);
		
		timer = 2 * g_pconf->twtimer;
		
	} else {
		/* TwTimer has passed since last received message, so send a DWR to probe the cnx */
		msg_t * dwr = NULL;
		
		/* Create the DWR message */
		CHECK_FCT_DO( _peer_dwr_create(peer, &dwr), return 2 );

		/* Send the DWR */
		CHECK_FCT_DO(  meq_post( peer->p_out_q, dwr ),  return 1  ); 

		/* Then arm the flag */
		peer->p_flags |= PEERFL_DW_PENDING;
		
		timer = g_pconf->twtimer;
	}
	
	/* and re-arm the timer */
	_psm_reset_ts(peer, 1, timer);

	return 0;
}


/* When the peer is requested to shutdown, send a DPR message. The reason may be passed as event data */
static int _psm_send_DPR( _peer_t * peer, _pevent_t event, void * ev_data )
{
	uint32_t cause = ACV_DC_REBOOTING;
	msg_t * dpr = NULL;
	
	TRACE_ENTRY("%p(%s/%s) %d(%s) %p", peer, peer->p_diamid ?: "null", STATESTR(peer->p_state), event, EVENTSTR(event), ev_data);
	
	TRACE_DEBUG(FULL, "Requesting clean disconnection to '%s'", peer->p_diamid);
	
	/* Set the flag PEERFL_SHUTTING_DOWN */
	peer->p_flags |= PEERFL_SHUTTING_DOWN;
	
	/* Set the flag PEERFL_DISABLE_AFTER_SHUTDOWN or PEERFL_DESTROY_AFTER_SHUTDOWN according to the event */
	if (event == PEVENT_SHUTDOWN) {
		peer->p_flags |= PEERFL_DISABLE_AFTER_SHUTDOWN;
	}
	if (event == PEVENT_DESTROY) {
		peer->p_flags |= PEERFL_DESTROY_AFTER_SHUTDOWN;
	}
	
	if (ev_data != NULL) {
		/* set the value of "cause" according to the event data: ACV_DC_BUSY or ACV_DC_NOT_FRIEND */
		TRACE_DEBUG(INFO, "@@@Not supported yet");
	}
	
	/* Create the DPR message */
	CHECK_FCT_DO( _peer_dpr_create(peer, &dpr, cause), return 2 );
	
	/* Send the DPR */
	CHECK_FCT_DO(  meq_post( peer->p_out_q, dpr ),  return 1  ); 
	
	/* re-arm the timer */
	_psm_reset_ts(peer, 0, DPR_TIMEOUT);
	
	return 0;
}



/* The following matrix is the state machine. See following function for more details */
static struct {
	int 		(*cb)(_peer_t * peer, _pevent_t event, void * ev_data);
} PSM_ARRAY [ /* previous state */ ] [ /* incoming event */ PEVENT_MAX ] = {
	/* STATE_DISABLED 	*/  {	  /* In this state, there is no PSM running, so any call here is erroneous */
		/* PEVENT_SHUTDOWN 	*/ {  _psm_assert		},
		/* PEVENT_DESTROY 	*/ {  _psm_assert		},
		/* PEVENT_EXPIRE 	*/ {  _psm_assert		},
		/* PEVENT_TIMEOUT 	*/ {  _psm_assert		},
		/* PEVENT_CONNECTED 	*/ {  _psm_assert		},
		/* PEVENT_MSGRCVD 	*/ {  _psm_assert		},
		/* PEVENT_INCNX 	*/ {  _psm_assert		},
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_assert		},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_assert		},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_assert		},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_assert		},
		/* PEVENT_SND_FAILED 	*/ {  _psm_assert		},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_assert		},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_assert		}
		},
	/* STATE_OPEN 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_send_DPR		}, /* set flag PEERFL_DISABLE_AFTER_SHUTDOWN then initiate disconnection procedure */
		/* PEVENT_DESTROY 	*/ {  _psm_send_DPR		}, /* set flag PEERFL_DESTROY_AFTER_SHUTDOWN then initiate disconnection procedure */
		/* PEVENT_EXPIRE 	*/ {  _psm_expire		}, /* Handle dynamic peers end-of-lifetime */
		/* PEVENT_TIMEOUT 	*/ {  _psm_do_watchdog		}, /* the handler may overwrite the next state to SUSPECT */
		/* PEVENT_CONNECTED 	*/ {  _psm_assert		}, /* Did we initiate two connections?? */
		/* PEVENT_MSGRCVD 	*/ {  _psm_receive_message	}, /* Handle received messages */
		/* PEVENT_INCNX 	*/ {  _psm_reject_incoming_CER	}, /* We must reject the new connection */
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_ignore		}, /* I guess it will depend on the change... ignore for now */
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP		}, /* Security issue? We may want to check the address change is compatible with advertised host-ip-addresses */
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP		}, /* should mark as SUSPECT and probe the cnx with DWR/DWA ? */
		/* PEVENT_DISCONNECTED 	*/ {  _psm_reset		}, /* Cleanup open resources and reset the PSM */
		/* PEVENT_SND_FAILED 	*/ {  _psm_reset		}, /* Cleanup open resources and reset the PSM */
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_reset		}, /* A serious error occurred in receiver thread: cleanup open resources and reset the PSM */
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_reset		}  /* A serious error occurred in sender   thread: cleanup open resources and reset the PSM */
		},
	/* STATE_CLOSED 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_reset 	 	}, /* May force stopping reconnections */
		/* PEVENT_DESTROY 	*/ {  _psm_reset 	 	},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire 	 	}, /* Handle dynamic peers end-of-lifetime */
		/* PEVENT_TIMEOUT 	*/ {  _psm_connect	 	}, /* Timeout reached, it's time to attempt new connection */
		/* PEVENT_CONNECTED 	*/ {  _psm_assert 	 	}, /* This should never happen, but anyway we can ignore it */
		/* PEVENT_MSGRCVD 	*/ {  _psm_assert 	 	}, /* Assert for the moment, anyway we should be able to handle the message */
		/* PEVENT_INCNX 	*/ {  _psm_answer_CEA_simple	}, /* Answer a CEA and go to OPEN, or anwer an error */
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_ignore 	 	}, 
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_ignore 	 	},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_ignore 	 	},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_ignore 	 	}, /* This should never happen, but anyway we can ignore it */
		/* PEVENT_SND_FAILED 	*/ {  _psm_assert 	 	}, /* Of course send would fail on a closed peer... */
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_assert 	 	},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_assert 	 	}
		},
	/* STATE_CLOSING 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_DESTROY 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire	 	},
		/* PEVENT_TIMEOUT 	*/ {  _psm_reset	 	},
		/* PEVENT_CONNECTED 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_MSGRCVD 	*/ {  _psm_receive_message 	},
		/* PEVENT_INCNX 	*/ {  _psm_reject_incoming_CER	},
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_SND_FAILED 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_NOTSUP	 	}
		},
	/* STATE_WAITCNXACK 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_NOTSUP	 	},
		/* PEVENT_DESTROY 	*/ {  _psm_NOTSUP		},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire		},
		/* PEVENT_TIMEOUT 	*/ {  _psm_reset		}, /* The attempt failed, reset the PSM */
		/* PEVENT_CONNECTED 	*/ {  _psm_send_CER		}, /* Send a CER */
		/* PEVENT_MSGRCVD 	*/ {  _psm_NOTSUP		},
		/* PEVENT_INCNX 	*/ {  _psm_incon_elect		}, /* We will have to handle an election */
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_SND_FAILED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_NOTSUP		}
		},
	/* STATE_WAITCNXACK_ELEC */  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DESTROY 	*/ {  _psm_NOTSUP		},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire		},
		/* PEVENT_TIMEOUT 	*/ {  _psm_elect_timeout	}, /* initiated connection attempt failed, fallback to responder */
		/* PEVENT_CONNECTED 	*/ {  _psm_send_CER		},
		/* PEVENT_MSGRCVD 	*/ {  _psm_NOTSUP		},
		/* PEVENT_INCNX 	*/ {  _psm_reject_incoming_CER	},
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_SND_FAILED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_NOTSUP		}
		},
	/* STATE_WAITCEA 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DESTROY 	*/ {  _psm_NOTSUP		},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire		},
		/* PEVENT_TIMEOUT 	*/ {  _psm_elect_timeout	}, /* Did not receive a CEA in the given time, reset the PSM or be responder */
		/* PEVENT_CONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_MSGRCVD 	*/ {  _psm_receive_CEA		},
		/* PEVENT_INCNX 	*/ {  _psm_incon_elect		}, /* We will have to handle an election */
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_SND_FAILED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_NOTSUP		}
		},
	/* STATE_SUSPECT 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DESTROY 	*/ {  _psm_NOTSUP		},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire		},
		/* PEVENT_TIMEOUT 	*/ {  _psm_reset		}, /* In this case, we must shutdown the connection */
		/* PEVENT_CONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_MSGRCVD 	*/ {  _psm_receive_message	}, /* handle the received message */
		/* PEVENT_INCNX 	*/ {  _psm_answer_CEA_simple	},
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_reset		},
		/* PEVENT_SND_FAILED 	*/ {  _psm_reset		},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_reset		},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_reset		}
		},
	/* STATE_REOPEN 	*/  {
		/* PEVENT_SHUTDOWN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DESTROY 	*/ {  _psm_NOTSUP		},
		/* PEVENT_EXPIRE 	*/ {  _psm_expire		},
		/* PEVENT_TIMEOUT 	*/ {  _psm_do_watchdog		},
		/* PEVENT_CONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_MSGRCVD 	*/ {  _psm_receive_message	},
		/* PEVENT_INCNX 	*/ {  _psm_reject_incoming_CER	},
		/* PEVENT_ASSOC_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_PEERADDR_CHG 	*/ {  _psm_NOTSUP		},
		/* PEVENT_REMOTE_ERR 	*/ {  _psm_NOTSUP		},
		/* PEVENT_DISCONNECTED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_SND_FAILED 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_IN 	*/ {  _psm_NOTSUP		},
		/* PEVENT_TH_TERM_OUT 	*/ {  _psm_NOTSUP		}
		}
};	



/******************************************************************************/
/*  Core of the state machine mechanism                                       */
/******************************************************************************/

/* Reset the timespec, optionally offset by random value */
static void _psm_reset_ts(_peer_t * peer, int add_random, int delay)
{
	/* Initialize the timer */
	CHECK_POSIX_DO(  clock_gettime( CLOCK_REALTIME,  &peer->p_ts ), ASSERT(0) );
	
	if (add_random) {
		if (delay > 2)
			delay -= 2;
		else
			delay = 0;

		/* Add a random value between 0 and 4sec */
		peer->p_ts.tv_sec += random() % 4;
		peer->p_ts.tv_nsec+= random() % 1000000000L;
		if (peer->p_ts.tv_nsec > 1000000000L) {
			peer->p_ts.tv_nsec -= 1000000000L;
			peer->p_ts.tv_sec ++;
		}
	}
	
	peer->p_ts.tv_sec += delay;
	
#if 0
	/* temporary for debug */
	peer->p_ts.tv_sec += 10;
#endif
}

/* Wait for next event or timeout */
static int _psm_wait_next_event(_peer_t * peer, _pevent_t * event, void ** ev_data)
{
	_pe_t * next;
	int ret = 0;
	
	*event = 0;
	if (ev_data)
		*ev_data = NULL;
	
spurious:
	CHECK_POSIX_DO2( ret = pthread_cond_timedwait( &peer->p_condvar, &peer->p_lock, &peer->p_ts ),
		ETIMEDOUT, *event = PEVENT_TIMEOUT, 
		return ret  );

	if (!IS_LIST_EMPTY(&peer->p_events)) {
		next = (_pe_t *) peer->p_events.next;
		uti_list_unlink(_LIST(next));
		*event = next->event;
		if (ev_data)
			*ev_data = next->list.o;
		free(next);
	}
	
	if (*event == 0)
		goto spurious;
	
	/* Very special case, it will actually probably never happen... */
	if ((peer->p_state == STATE_CLOSED) && (peer->p_flags & PEERFL_DISABLE_AFTER_SHUTDOWN) && (*event == PEVENT_TIMEOUT)) {
		/* Wow... should probably never happen */
		TRACE_DEBUG(NONE, "The peer '%s' has been unused for more than one year ^^ (NO_TIMEOUT)", peer->p_diamid);
		/* Go back to sleep until next event */
		CHECK_POSIX_DO( ret = pthread_cond_wait( &peer->p_condvar, &peer->p_lock ),
			return ret  );
		goto spurious;
	}
	
	return 0;
}

/* Change the security state of a peer */
static void _psm_change_state_sec(_peer_t * peer, _peer_state_t new)
{
	TRACE_ENTRY();
	
	/* First, check if the peer is using the peer-sec-ini module or a real sec module */
	if (peer->p_sec_hdl == NULL)
		return;
	
	if (peer->p_secmod->sec_state_change == NULL)
		return;
	
	/* For clean initialization, PSS_CLOSED->PSS_CONNECTED */
	if ((
		(new == STATE_OPEN)
		|| (new == STATE_REOPEN)
	   ) && (
		( peer->p_state == STATE_CLOSED )
		|| ( peer->p_state == STATE_WAITCNXACK_ELEC )
		|| ( peer->p_state == STATE_WAITCEA )
	   )) {
		CHECK_FCT_DO( (*peer->p_secmod->sec_state_change)(
				/* new state */ PSS_CONNECTED,
				/* old state */ PSS_CLOSED,
				/* session */ &peer->p_sec_session,
				/* private data */ &peer->p_ext_session), 
			/* Do nothing else than logging if it fails */ );
	}
	
	if (new == STATE_CLOSED) {
		if ( peer->p_state == STATE_CLOSING ) {
			/* For clean termination, PSS_OPEN->PSS_CLOSING->PSS_CLOSED */
			CHECK_FCT_DO( (*peer->p_secmod->sec_state_change)(
					/* new state */ PSS_CLOSING,
					/* old state */ PSS_CONNECTED,
					/* session */ &peer->p_sec_session,
					/* private data */ &peer->p_ext_session), 
				/* Do nothing else than logging if it fails */ );
			CHECK_FCT_DO( (*peer->p_secmod->sec_state_change)(
					/* new state */ PSS_CLOSED,
					/* old state */ PSS_CLOSING,
					/* session */ &peer->p_sec_session,
					/* private data */ &peer->p_ext_session), 
				/* Do nothing else than logging if it fails */ );
		} else {
			/* For violent termination, PSS_OPEN->PSS_CLOSED */
			CHECK_FCT_DO( (*peer->p_secmod->sec_state_change)(
					/* new state */ PSS_CLOSED,
					/* old state */ PSS_CONNECTED,
					/* session */ &peer->p_sec_session,
					/* private data */ &peer->p_ext_session), 
				/* Do nothing else than logging if it fails */ );
		}
	}
	
	return;	
}

/* Cleanup the data when leaving a state */
static void _psm_cleanup_state(_peer_t * peer, _peer_state_t new)
{
	TRACE_ENTRY();
	
	/* Note: we are called only if the new state is different from the old one */
	
	switch (peer->p_state) {
		case STATE_DISABLED:
		case STATE_CLOSED:
			/* We have nothing to do */
			break;
			
		case STATE_REOPEN:
		case STATE_OPEN:
			/* We must failover any message */
			if (new != STATE_OPEN) {
				CHECK_FCT_DO( _peer_failover(peer, 0), /* continue */);
			}
			
			break;
			
		case STATE_WAITCNXACK:
		case STATE_WAITCNXACK_ELEC:
			/* When we leave this state, we must stop the connection attempt, unless we are doing election */
			if (new != STATE_WAITCNXACK_ELEC) {
				CHECK_FCT_DO( _peer_client_stop(peer), /* continue */ );
			}
			break;
			
		case STATE_WAITCEA:
			/* When leaving this state, kill any incoming connection pending because of an election */
			if (peer->p_electdata) {
				_peer_struct_destroy(&peer->p_electdata->peer);
				free(peer->p_electdata);
				peer->p_electdata=NULL;
			}
			break;
			
		case STATE_SUSPECT:
		case STATE_CLOSING:
			/* ??? */
			break;
	}
	
	return;	
}

/* Change the state of a peer */
#ifndef IN_WAAAD_TEST
static 
#endif /* IN_WAAAD_TEST */
void _psm_change_state(_peer_t * peer, _peer_state_t new)
{
	uti_list_t * prev;
	
	TRACE_DEBUG(FULL, "Peer '%s' state change: %s -> %s", peer->p_diamid, STATESTR(peer->p_state), STATESTR(new));
	
	/* Only proceed if the state actually changed */
	if (peer->p_state == new)
		return;
	
	/* Now change the state in security module */
	_psm_change_state_sec(peer, new);
	
	/* Do any required cleanup when leaving the state */
	_psm_cleanup_state(peer, new);
	
	/* change the state */
	peer->p_state = new;
	
	/* Ops on the flags */
	peer->p_flags &= ~ ( PEERFL_DW_COUNTER_Lo | PEERFL_DW_COUNTER_Hi );
	if (peer->p_state == STATE_OPEN) {
		peer->p_flags &= ~ PEERFL_CNX_PB;
	}
	
	/* Handle the "active" peers list */
	if (peer->p_state != STATE_OPEN) {
		if (!IS_LIST_EMPTY(&peer->p_active)) {
			CHECK_POSIX_DO(  pthread_mutex_lock( &_peers_lists_mtx ),  uti_event_send(WDE_ERROR)  );
			TRACE_DEBUG(FULL, "Peer '%s' removed from the list of active peers", peer->p_diamid);
			uti_list_unlink( &peer->p_active );
			CHECK_POSIX_DO(  pthread_mutex_unlock( &_peers_lists_mtx ),  uti_event_send(WDE_ERROR)  );
		}
		return;
	}
	
	if (!IS_LIST_EMPTY(&peer->p_active))
		return; /* we are already linked */
	
	/* Ok, we have to insert this peer into the active list */
	CHECK_POSIX_DO(  pthread_mutex_lock( &_peers_lists_mtx ),  uti_event_send(WDE_ERROR)  );
	
	for (prev = &_peers_actives; prev->next != &_peers_actives; prev = prev->next) {
		int cmp = 0;
		_peer_t * cur = _P(prev->next->o);
		
		if (cur->p_hash < peer->p_hash)
			continue;
		
		if (cur->p_hash > peer->p_hash)
			break;
		
		cmp = strcmp(cur->p_diamid, peer->p_diamid);
		
		if (cmp < 0)
			continue;
		
		if (cmp == 0) {
			ASSERT(0);
		}
		
		break;
	}
	
	uti_list_insert_after(prev, &peer->p_active);
	
	CHECK_POSIX_DO(  pthread_mutex_unlock( &_peers_lists_mtx ),  uti_event_send(WDE_ERROR)  );
	
	TRACE_DEBUG(FULL, "Peer '%s' inserted in the list of active peers", peer->p_diamid);
	
	return;
}

/* The peer state machine.

When this function is called, the peer is in a given state STATE_CUR and the timer p_ts is set.
The state machine waits for the next event (including expiry of p_ts) and handles this event.
It then eventually changes the state to STATE_NEW and resets the p_ts timer, then returns 0.
On important error, non-0 error code is returned.
 */
static int _psm_do(_peer_t * peer) 
{
	_pevent_t event;
	void * event_data;
	
	TRACE_DEBUG(FULL, "PSM for peer '%s' state '%s', waiting next event...", peer->p_diamid, STATESTR(peer->p_state));
	
	/* wait for the next event (or timeout). On error destroy the peer completly */
	CHECK_FCT_DO( _psm_wait_next_event(peer, &event, &event_data), return 2 );
	
	TRACE_DEBUG(FULL, "PSM for peer '%s' state '%s', received: '%s', processing...", peer->p_diamid, STATESTR(peer->p_state), EVENTSTR(event));

	/* now call the PSM callback */
	return (*PSM_ARRAY[peer->p_state - 1][event - 1].cb)(peer, event, event_data);
}

static void _psm_cleanup( _peer_t * peer )
{
	/* Stop any children thread */
	CHECK_FCT_DO(  _peer_client_stop(peer), /* continue */  );
	CHECK_FCT_DO(  _peer_in_stop(peer), /* continue */  );
	CHECK_FCT_DO(  _peer_out_stop(peer), /* continue */  );
	
	/* Close the socket if any */
	if (peer->p_sock) {
		CHECK_SYS_DO( shutdown(peer->p_sock, SHUT_RDWR), /* continue */ );
		peer->p_sock = 0;
	}
	
	/* Eat all pending events */
	_peer_events_reset(peer);
	
	/* Failover: requeue all sent messages and pending messages to the global outgoing queue */
	CHECK_FCT_DO(  _peer_failover(peer, 0), /* continue */  );
	
	/* The timespec is erased */
	memset( &peer->p_ts, 0, sizeof(struct timespec) );
	
	/* Reset the security module */
	_peer_struct_secmod_reset(peer);
	
	/* Reset application list */
	_peer_struct_appl_reset(peer);
	
	/* keep only some of the flags */
	peer->p_flags &= PEERFL_CNX_PB;
	
	return;
}

/* In case of cancellation (i.e. PSM is terminated), go to DISABLED state, and unlock the peer */
static void cleanup_psm(void * arg)
{
	_peer_t * peer = _P(arg);
	
	TRACE_ENTRY("%p", arg);
	
	_psm_change_state(peer, STATE_DISABLED);
	
	_psm_cleanup(peer);
	
	CHECK_POSIX_DO( pthread_mutex_unlock( &peer->p_lock ), /* ignore error */ );
	
	return;
}
	

/* The code of the p_psm_th thread */
static void * _psm_th(void * arg)
{
	_peer_t * peer = (_peer_t *)arg;
	int ret = 0;
	uint32_t flags =0;
	
	THREAD_NAME_PEER( "PSM", peer );
	TRACE_ENTRY( "%p", peer );
	
	/* Lock the peer */
	CHECK_POSIX_DO(  pthread_mutex_lock(&peer->p_lock), goto destroy_me  );
	
	/* Go to closed state to signal that the PSM is running */
	_psm_change_state( peer, STATE_CLOSED );
	
	/* Check if the peer->p_sock is already defined, meaning we are receiver side */
	if ( peer->p_flags & PEERFL_RESPONDER ) {
		_psm_reset_ts(peer, 0, INCNX_TIMEOUT); /* This is just to check for errors, the event is generated before the PSM can acquire the lock */
		
		/* Just start the PSM then, the event will trig whatever behavior is expected */
	} else {
		/* We are initiating a connection to this peer */
		
		/* We are handling a new peer, initialize the timer to a random value -- would be better to do this only at daemon startup... */
		_psm_reset_ts(peer, 1, 0);
		
	}
	
psm:
	/* First ,check that we will actually be able to communicate with the peer: we must have at least one security mechanism available */
	CHECK_FCT_DO( sec_getmodules(peer->p_diamid, (sSA *)&(peer->p_addinfo.pa_ss),&peer->p_sec_list), goto destroy_me );

	if (IS_LIST_EMPTY(&peer->p_sec_list)) {
		log_error("No security extension is available to handle the peer '%s', peer is disabled.\n", peer->p_diamid);
		goto disable_me;
	}

	/* the peer state machine main loop */
	while (1) {
		pthread_cleanup_push( cleanup_psm, (void *)peer );
		ret = _psm_do(peer);
		pthread_cleanup_pop(0);
		switch (ret) {
			case 0:	/* no problem so far, continue to next event */
				continue;
			
			case 1: /* critical error, reset the peer state machine */
				goto reset;
				
			case 2: /* very critical error, destroy all peer data */
			default:
				goto destroy_me;
		}
	}
	
	
reset:
	/* The state is reinitialized to CLOSED */
	_psm_change_state(peer, STATE_CLOSED);

	/* Save the state of the flags */
	flags = peer->p_flags;
	
	/* Reset the peer object in a clean state */
	_psm_cleanup( peer );
	
	if (flags & PEERFL_DESTROY_AFTER_SHUTDOWN)
		goto disable_me;
	
	if (flags & PEERFL_DISABLE_AFTER_SHUTDOWN) {
		/* save this flag in the peer again */
		peer->p_flags |= PEERFL_DISABLE_AFTER_SHUTDOWN;
		
		/* Reset the timer with a very large value */
		_psm_reset_ts(peer, 0, NO_TIMEOUT);
	} else {
		/* We will re-attempt the connection regularly */
		
		/* Reset the timer */
		_psm_reset_ts(peer, 1, g_pconf->tctimer);
	}
	
	/* restart the PSM */
	goto psm;

disable_me:  /* the peer lock must be held */
	TRACE_DEBUG(INFO, "Disabling peer state machine for peer '%s'", peer->p_diamid);
	
	/* same as if the PSM is stopped from outside */
	cleanup_psm(peer);
	
	goto autokill;
	
	
destroy_me:  /* the peer lock must not be held */
	/* An unrecoverable error occurred on this peer, we disable the PSM totally */
	
	
autokill:
	peer->p_psm = (pthread_t)NULL;
	pthread_detach(pthread_self());
	
	TRACE_DEBUG(INFO, "PSM tread is terminated.");
	return NULL;
}

/* Start the PSM for a peer */
/* mode:
  1 - start as initiator.
  2 - start as responder. A PEVENT_INCNX event will be received soon.
 */
int _peer_psm_start(_peer_t * peer, int mode)
{
	CHECK_PARAMS( VALIDATE_PEER(peer) );
	CHECK_PARAMS( peer->p_state == STATE_DISABLED );
	CHECK_PARAMS( peer->p_psm == (pthread_t)NULL );
	
	switch (mode) {
		case 1:
			/* We are initiator */
			peer->p_flags &= ~PEERFL_RESPONDER;
			break;
		case 2:
			/* We are responder */
			peer->p_flags |= PEERFL_RESPONDER;
			break;
		
		default:
			CHECK_PARAMS( mode && 0 );
	}
	
	CHECK_POSIX( pthread_create( &peer->p_psm, NULL, _psm_th, peer ) );
	
	return 0;
}

/* Stop the PSM for a peer */
/* mode:
  0 - clean shutdown if possible (reason: Rebooting)
  1 - clean shutdown if possible (reason: busy)
  2 - clean shutdown if possible (reason: do not want to talk to you)
 -1 - violent shutdown (no message sent)
 */
int _peer_psm_stop(_peer_t * peer, int mode)
{
	if (peer->p_psm == (pthread_t)NULL)
		return 0; /* the PSM is already stopped */
	
	switch (mode) {
		case 0:
		case 1:
		case 2:
			/* Send a SHUTDOWN event to the peer with the reason asked */
			TRACE_DEBUG(INFO, "Clean shutdown not implemented yet...");
			
		case -1:
		default:
			TRACE_DEBUG(FULL, "Killing PSM %p", peer->p_psm);
			return _thread_term(&peer->p_psm);
	}
	
	return EINVAL;
}

/* Start the peers PSM */
int _peer_psm_start_all(void)
{
	int i;
	
	TRACE_ENTRY( );
	
	/* Start all peer's state machine threads */
	for (i = 0; i < sizeof(_peer_hash) / sizeof(_peer_hash[0]); i++) {
		uti_list_t * li;
		
		/* lock this line */
		CHECK_POSIX(  pthread_mutex_lock( &_peer_hash[i].lock )  );
		
		/* For each peer in this line */
		for (li = _peer_hash[i].all.next; li != &_peer_hash[i].all; li = li->next) {
			
			/* create the PSM */
			CHECK_FCT(  _peer_psm_start( _P(li->o), 1 )  );
		}
			
		/* unlock the line */
		CHECK_POSIX(  pthread_mutex_unlock( &_peer_hash[i].lock )  );
		
	}
	
	return 0;
}

"Welcome to our mercurial repository"