/* 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 #include #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); }