/* BTP library - Banana Tree Protocol
* Copyright (C) 1999-2001 The Regents of the University of Michigan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <stdlib.h>
#include <gnet/gnet.h>
#include "b_peer.h"
#include "b_conn.h"
#include "btp_debug.h"
struct _BPeerHandler
{
BPeerHandlerFunc func;
gpointer data;
};
static void server_func (GServer* gserver, GServerStatus status,
GConn* conn, gpointer user_data);
static void receive_hello (BConn* conn, BPacket* packet, gpointer user_data);
static void receive_hello_timeout (BConn* conn, gint type, gint subtype,
gpointer user_data);
static void get_handler_data_hfunc (gpointer key, gpointer value,
gpointer user_data);
/* ************************************************************ */
BPeer*
b_peer_new (const gchar* hostname, const GInetAddr* iface, gboolean force_port)
{
GServer* server;
BPeer* peer;
BTPP (1, "b_peer_new\n");
g_return_val_if_fail (hostname, NULL);
server = gnet_server_new (iface, force_port, server_func, NULL);
if (!server)
return NULL;
peer = g_new0 (BPeer, 1);
server->user_data = peer;
peer->server = server;
peer->hostname = g_strdup (hostname);
peer->port = server->port;
peer->handlers = g_hash_table_new (g_str_hash, g_str_equal);
return peer;
}
static void
peer_delete_hfunc (gpointer key, gpointer value, gpointer user_data)
{
g_free (key); /* string */
g_free (value); /* handler */
}
void
b_peer_delete (BPeer* peer)
{
GSList* i;
if (!peer)
return;
g_free (peer->hostname);
if (peer->server)
gnet_server_delete (peer->server);
g_hash_table_foreach (peer->handlers, peer_delete_hfunc, NULL);
g_hash_table_destroy (peer->handlers);
for (i = peer->conns; i != NULL; i = i->next)
b_conn_delete ((BConn*) i->data);
g_slist_free (peer->conns);
g_free (peer);
}
static void
peer_print_hfunc (gpointer key, gpointer value, gpointer user_data)
{
fprintf ((FILE*) user_data, "\t%s\n", (gchar*) key);
}
void
b_peer_print (FILE* file, BPeer* peer)
{
if (!peer)
{
fprintf (file, "(null)");
return;
}
fprintf (file, "%s:%d [%d pending conns]\n",
peer->hostname, peer->port,
g_slist_length (peer->conns));
fprintf (file, " handlers:\n");
g_hash_table_foreach (peer->handlers, peer_print_hfunc, file);
}
/* ************************************************************ */
/**
Called when someone connects to us.
We get the conn, create a new BConn, add watches for read and
error, then set a timer for receiving the HELLO.
Last checked: 2001-4-30 DAH
*/
static void
server_func (GServer* gserver, GServerStatus status, GConn* conn, gpointer user_data)
{
BPeer* peer = (BPeer*) user_data;
BConn* bconn = NULL;
BTPP (1, "server_func %d\n", status);
g_assert (status != GNET_SERVER_STATUS_ERROR);
/* Create a new conn. */
bconn = b_conn_new_accepted (conn);
/* Watch read and err */
b_conn_watch_read (bconn);
b_conn_watch_error (bconn);
/* Handle a HELLO packet */
b_conn_handler_add_full (bconn, B_PACKET_TYPE_HELLO, -1,
receive_hello, receive_hello_timeout,
B_RECEIVE_HELLO_TIME, peer);
/* Save connection */
peer->conns = g_slist_prepend (peer->conns, bconn);
/* We should now receive a HELLO and will send back an OK or ERROR.
If we don't receive a HELLO, the timer will expire and the
connection will be closed. */
}
/* ************************************************************ */
/**
Handle a HELLO packet.
Last checked: 2001-4-30 DAH
*/
static void
receive_hello (BConn* conn, BPacket* packet, gpointer user_data)
{
BPeer* peer;
GURL* url = NULL;
gchar* hostname = NULL;
gint port = 0;
GSList* i;
BPeerHandler* handler;
BPacket* new_packet;
BTPP (1, "receive_hello\n");
peer = (BPeer*) user_data;
g_return_if_fail (peer);
g_return_if_fail (conn);
g_return_if_fail (packet);
/* Remove from conn list */
peer->conns = g_slist_remove (peer->conns, conn);
/* Remove HELLO handler */
b_conn_handler_remove (conn, B_PACKET_TYPE_HELLO, -1, NULL);
/* Parse the packet */
if (b_packet_parse_hello (packet, &url, &hostname, &port) ||
!url->resource)
{
g_warning ("Recieved bad HELLO\n");
goto drop;
}
/* Make sure the sender isn't us */
if (!strcmp(hostname, peer->hostname) && port == peer->port)
{
g_warning ("Received HELLO packet from myself\n");
g_free (hostname);
goto drop;
}
/* Fix the hostname, port, and address of the connection */
g_free (conn->conn->hostname);
conn->conn->hostname = hostname;
conn->conn->port = port;
gnet_inetaddr_delete (conn->conn->inetaddr);
conn->conn->inetaddr = NULL;
/* Save the out packet ID */
conn->out_conn_id = packet->id;
BTPP (0, "conn->out_conn_id = %d\n", conn->out_conn_id);
/* Choose an in packet ID for us */
conn->in_conn_id = rand();
BTPP (0,"conn->in_conn_id = %d\n", conn->in_conn_id);
/* Fix the ID of any packets in our packet queue */
for (i = conn->pkt_queue; i != NULL; i = i->next)
((BPacket*) i->data)->id = conn->out_conn_id;
/* Dub it connected */
conn->status = B_CONN_STATUS_CONNECTED;
/* Find a peer handler for it */
BTPP (0, "find handler for %s\n", url->resource);
handler = (BPeerHandler*) g_hash_table_lookup (peer->handlers, url->resource);
BTPP (0, "handler is %p\n", handler);
if (!handler) goto drop;
if (handler->func(conn, handler->data))
{
BTPP (1, "receive_hello_timeout: handler not found\n");
goto drop;
}
/* TODO: Send an ERROR packet. This requires additional
bookkeepping, so we don't bother doing it. */
/* Send an OK packet */
new_packet = b_packet_new_ok_hello (conn);
b_conn_send_packet (conn, new_packet);
/* Handle BYE and PING */
b_conn_ping_start (conn);
b_conn_ping_handle (conn);
b_conn_bye_handle (conn);
/* Call connect */
b_conn_func_connect (conn);
done:
BTPP (1, "receive_hello_timeout: connection complete\n");
/* Delete everything */
gnet_url_delete (url);
return;
drop:
BTPP (1, "receive_hello_timeout: connection dropped\n");
/* Delete the connection immediately */
b_conn_delete (conn);
goto done;
}
static void
receive_hello_timeout (BConn* conn, gint type, gint subtype, gpointer user_data)
{
BPeer* peer = (BPeer*) user_data;
g_return_if_fail (type == B_PACKET_TYPE_HELLO);
BTPP (1, "receive_hello_timeout\n");
/* Remove from conn list */
peer->conns = g_slist_remove (peer->conns, conn);
/* Delete */
b_conn_delete (conn);
}
/* ************************************************************ */
void
b_peer_add_handler (BPeer* peer, const gchar* name, BPeerHandlerFunc func,
gpointer user_data)
{
BPeerHandler* handler;
g_return_if_fail (peer);
g_return_if_fail (func);
BTPP (1, "b_peer_add_handler %s\n", name);
handler = g_new0 (BPeerHandler, 1);
handler->func = func;
handler->data = user_data;
g_hash_table_insert (peer->handlers, g_strdup (name), handler);
}
void
b_peer_remove_handler (BPeer* peer, const gchar* name)
{
gchar* orig_key;
BPeerHandler* handler;
g_return_if_fail (peer);
g_return_if_fail (name);
if (!g_hash_table_lookup_extended(peer->handlers, name,
(gpointer) &orig_key, (gpointer) &handler))
return;
g_hash_table_remove (peer->handlers, name);
g_free (orig_key);
g_free (handler);
}
gboolean
b_peer_has_handler (BPeer* peer, const gchar* name)
{
return (g_hash_table_lookup (peer->handlers, name) != NULL);
}
GSList*
b_peer_get_handler_data (BPeer* peer)
{
GSList* list;
list = NULL;
g_hash_table_foreach (peer->handlers, get_handler_data_hfunc, &list);
return list;
}
static void
get_handler_data_hfunc (gpointer key, gpointer value, gpointer user_data)
{
BPeerHandler* handler;
GSList** listp;
handler = (BPeerHandler*) value;
listp = (GSList**) user_data;
*listp = g_slist_prepend (*listp, handler->data);
}
syntax highlighted by Code2HTML, v. 0.9.1