/* Connection-Reflection Protocol - Tests if listening socket is reachable
 * Copyright (C) 2000  David Helder
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <string.h>
#include <stdlib.h>

#include "crp.h"


#define MAX_BUFFER	80	/* Don't read more than 80 bytes */
#define	CONN_TIMEOUT	30000	/* 30 seconds to connect max */


typedef struct _CRPState
{
  gchar* hostname;
  gint port;
  GConn* conn;
  CRPCheckFunc func;
  gpointer user_data;

} CRPState;

static gboolean conn_func (GConn* conn, GConnStatus status, 
			   gchar* buffer, gint length, gpointer user_data);



CRPID* 		
crp_check (GURL* crpserver_url, gchar* hostname, gint port, 
	   CRPCheckFunc func, gpointer user_data)
{
  GConn* conn;
  CRPState* state;
  gchar* str;

  g_return_val_if_fail (crpserver_url, NULL);
  g_return_val_if_fail (crpserver_url->hostname, NULL);
  g_return_val_if_fail (hostname, NULL);
  g_return_val_if_fail (func, NULL);

  conn = gnet_conn_new (crpserver_url->hostname, 
			crpserver_url->port ? crpserver_url->port : CRP_PORT, 
			conn_func, NULL);
  g_return_val_if_fail (conn, NULL);

  state = g_new0 (CRPState, 1);
  conn->user_data = state;
  state->hostname = g_strdup (hostname);
  state->port = port;
  state->conn = conn;
  state->func = func;
  state->user_data = user_data;
  
  str = g_strdup_printf ("CHECK %s:%d\n", hostname, port);
  gnet_conn_write (conn, str, strlen(str), 0);

  gnet_conn_timeout (conn, CONN_TIMEOUT);
  gnet_conn_connect (conn, 0); /* This call may destroy conn */ 

  return (CRPID*) state;
}


void
crp_cancel (CRPID* crpid)
{
  CRPState* state = (CRPState*) crpid;

  g_return_if_fail (crpid);

  g_free (state->hostname);
  gnet_conn_delete (state->conn, TRUE);
  memset (state, 0, sizeof (*state));
  g_free (state);
}


static gboolean 
conn_func (GConn* conn, GConnStatus status, 
	   gchar* buffer, gint length, gpointer user_data)
{
  CRPState* state = (CRPState*) user_data;

  switch (status)
    {

    case GNET_CONN_STATUS_CONNECT:
      break;

    case GNET_CONN_STATUS_READ:
      {
	gchar* addr;
	gchar* port_str;
	gint port;
	GInetAddr* inetaddr;
	CRPStatus status;

	/* Get address string */
	if (!strncmp (buffer, "GOOD ", sizeof("GOOD ") - 1))
	  {
	    addr = &buffer[sizeof("GOOD ") - 1];
	    status = CRP_STATUS_GOOD;
	  }
	else if (!strncmp (buffer, "BAD_PAIR ", sizeof("BAD_PAIR ") - 1))
	  {
	    addr = &buffer[sizeof("BAD_PAIR ") - 1];
	    status = CRP_STATUS_BAD_PAIR;
	  }
	else if (!strncmp (buffer, "BAD_NAME ", sizeof("BAD_NAME ") - 1))
	  {
	    addr = &buffer[sizeof("BAD_NAME ") - 1];
	    status = CRP_STATUS_BAD_NAME;
	  }
	else if (!strncmp (buffer, "BAD_PORT ", sizeof("BAD_PORT ") - 1))
	  {
	    addr = &buffer[sizeof("BAD_PORT ") - 1];
	    status = CRP_STATUS_BAD_PORT;
	  }
	else
	  goto error;

	/* Get port string */
	port_str = strchr (addr, ':');
	if (!port_str)
	  goto error;

	*port_str = '\0';
	++port_str;
	port = atoi(port_str);

	inetaddr = gnet_inetaddr_new_nonblock (addr, port);
	if (!inetaddr)
	  goto error;

	(state->func)(state->hostname, state->port, status, inetaddr, state->user_data);
	crp_cancel ((CRPID*) state);

	break;

      error:
	(state->func)(state->hostname, state->port, CRP_STATUS_ERROR, NULL, state->user_data);
	crp_cancel ((CRPID*) state);

	break;
      }

    case GNET_CONN_STATUS_WRITE:
      {
	g_free (buffer);
	gnet_conn_readline (conn, NULL, MAX_BUFFER, 0);

	break;
      }

    case GNET_CONN_STATUS_CLOSE:
    case GNET_CONN_STATUS_TIMEOUT:
    case GNET_CONN_STATUS_ERROR:
      {
	(state->func)(state->hostname, state->port, CRP_STATUS_ERROR, NULL, state->user_data);
	crp_cancel ((CRPID*) state);

	break;
      }
    }

  return FALSE;
}


syntax highlighted by Code2HTML, v. 0.9.1