/* 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 "b_conn_io.h" #include "b_packet.h" #include "btp_debug.h" #include "util.h" static gchar* flags2str (guint flags); static gboolean demux_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data); static gboolean read_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data); static gboolean write_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data); static gboolean error_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data); /* ************************************************************ */ static gchar* flags2str (guint flags) { gchar* str = NULL; str = g_strdup (""); if (flags & G_IO_IN) { gchar* oldstr = str; str = g_strconcat (oldstr, "IN|", NULL); g_free (oldstr); } if (flags & G_IO_OUT) { gchar* oldstr = str; str = g_strconcat (oldstr, "OUT|", NULL); g_free (oldstr); } if (flags & G_IO_PRI) { gchar* oldstr = str; str = g_strconcat (oldstr, "PRI|", NULL); g_free (oldstr); } if (flags & G_IO_ERR) { gchar* oldstr = str; str = g_strconcat (oldstr, "ERR|", NULL); g_free (oldstr); } if (flags & G_IO_HUP) { gchar* oldstr = str; str = g_strconcat (oldstr, "HUP|", NULL); g_free (oldstr); } if (flags & G_IO_NVAL) { gchar* oldstr = str; str = g_strconcat (oldstr, "NVAL|", NULL); g_free (oldstr); } if (*str != '\0') str[strlen(str) - 1] = '\0'; /* wipe out last | */ return str; } gboolean b_conn_watch_update (BConn* conn, guint new_flags) { g_return_val_if_fail (conn, FALSE); { gchar* str = flags2str(conn->watch_flags); gchar* str2 = flags2str(new_flags); BTPP (1, "b_conn_watch_update: %s -> %s (%d)\n", str, str2, conn->watch); g_free (str); g_free (str2); } if (conn->watch_flags == new_flags) return FALSE; conn->watch_changed = 1; if (conn->watch) { g_source_remove (conn->watch); conn->watch = 0; } if (new_flags && conn->conn->iochannel) conn->watch = g_io_add_watch (conn->conn->iochannel, new_flags, demux_cb, conn); conn->watch_flags = new_flags; return TRUE; } /* ************************************************************ */ /** demux_cb Called when any IO activity. This is a work around Glib's poll problem. */ static gboolean demux_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data) { BConn* conn = (BConn*) data; gboolean rv; g_return_val_if_fail (conn, FALSE); g_return_val_if_fail (!conn->in_upcall, FALSE); g_return_val_if_fail (condition & conn->watch_flags, FALSE); { gchar* str; str = flags2str (condition); BTPP (1, "demux_cb %s\n", str); g_free (str); } conn->in_upcall = 1; conn->watch_changed = 0; if (condition & conn->watch_flags & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { rv = error_cb (iochannel, condition, data); if (conn->should_destroy) goto destroy; if (!rv) b_conn_watch_update (conn, conn->watch_flags & ~(G_IO_ERR | G_IO_HUP | G_IO_NVAL)); } if (condition & conn->watch_flags & G_IO_OUT) { rv = write_cb (iochannel, condition, data); if (conn->should_destroy) goto destroy; if (!rv) b_conn_watch_update (conn, conn->watch_flags & ~G_IO_OUT); } if (condition & conn->watch_flags & G_IO_IN) { rv = read_cb (iochannel, condition, data); if (conn->should_destroy) goto destroy; if (!rv) b_conn_watch_update (conn, conn->watch_flags & ~G_IO_IN); } conn->in_upcall = 0; /* Update flags */ if (conn->watch_changed) { conn->watch_changed = 0; BTPP (0, "demux_cb: return FALSE\n"); return FALSE; } BTPP (0, "demux_cb: return TRUE\n"); return TRUE; /* otherwise, watch did not change */ destroy: conn->in_upcall = 0; b_conn_delete (conn); return FALSE; } /** read_cb Called when an iochannel is readable. Read from the iochannel. Loop parsing the packet and sending the packet up. Last checked: 2001-4-30 DAH */ static gboolean read_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data) { BConn* conn = (BConn*) data; gchar* buf; gint len; GIOError error; guint bytes_read; guint bytes_parsed = 0; BPacket* packet; g_return_val_if_fail (conn, FALSE); BTPP (1, "read_cb: buffer_offset = %d\n", conn->buffer_offset); /* Read stuff into our buffer */ buf = &conn->buffer[conn->buffer_offset]; len = sizeof(conn->buffer) - conn->buffer_offset; error = g_io_channel_read (iochannel, buf, len, &bytes_read); /* If we read EOF or got an error, then the connection has failed (we should have received a BYE) */ if ((error == G_IO_ERROR_NONE && bytes_read == 0) || (error != G_IO_ERROR_NONE && error != G_IO_ERROR_AGAIN)) { BTPP (0, "read_cb: error %d, bytes_read %d\n", error, bytes_read); goto error; } /* Update buffer length */ conn->buffer_offset += bytes_read; BTPP (0, "read_cb: bytes_read = %d\n", bytes_read); /* Parse the packets and pass them up to the host */ while (conn->buffer_offset && (bytes_parsed = b_packet_read (conn, conn->buffer, conn->buffer_offset, &packet)) > 0) { /* Move data over based on how much was read */ g_memmove(conn->buffer, &conn->buffer[bytes_parsed], conn->buffer_offset - bytes_parsed); conn->buffer_offset -= bytes_parsed; BTPP (0, "read_cb: bytes_parsed = %d\n", bytes_parsed); /* If there is no packet (it may only be part of a packet, break */ if (!packet) break; BTPP (2, "READ %s\n", b_packet_type_to_string(packet->type)); /* The packet should have the right ID (unless we just accepted it) */ if (!b_conn_is_accepted(conn) && packet->id != conn->in_conn_id) { g_warning ("Packet has wrong id (is %d, should be %d)\n", packet->id, conn->in_conn_id); } /* Dispatch the packet */ b_conn_handler_dispatch (conn, packet); b_packet_delete (packet); /* If the connection has been destroyed, then return FALSE. */ if (conn->should_destroy) return FALSE; } /* Dump the connection if there is no more buffer space */ if (sizeof(conn->buffer) == conn->buffer_offset) { g_warning ("Maximum buffer length reached for connection\n"); goto error; } /* Dump the connection if there was a parsing error. */ if (bytes_parsed < 0) { BTPP (0, "read_cb: parse failed\n"); goto error; } return TRUE; error: b_conn_func_fail (conn); return FALSE; } /** write_cb Called when an iochannel is writable. If there are no packets to send, do an upcall. If there's a packet to send, send it. Otherwise, we don't want any more callbacks. Last checked: 2001-4-30 DAH */ gboolean write_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data) { BConn* conn = (BConn*) data; BPacket* packet; g_return_val_if_fail (conn, FALSE); BTPP (1, "write_cb %p\n", conn); /* If there are no data packets to send, call the write callbacks - they may give us a data packet. */ if (b_conn_is_connected (conn) && !(b_conn_has_packet (conn, B_PACKET_TYPE_MCAST) || b_conn_has_packet (conn, B_PACKET_TYPE_UCAST)) ) { b_conn_func_writeable (conn); } /* If we have a packet, send it */ if (conn->pkt_queue && (packet = b_conn_pop_packet (conn)) != NULL) { gchar* buffer; guint length; guint bytes_writen; GIOError error; /* Get the next packet from the conn. We may not be ready to send a packet because we're awaiting a response. */ if (!packet) return FALSE; BTPP (3, "WRITE %s\n", b_packet_type_to_string(packet->type)); /* Convert it to bytes */ length = b_packet_write (packet, &buffer); g_return_val_if_fail (buffer, FALSE); g_return_val_if_fail (length > 0, FALSE); /* Write it */ error = gnet_io_channel_writen (conn->conn->iochannel, buffer, length, &bytes_writen); BTPP (1, "write_cb: bytes_writen = %d, length = %d\n", bytes_writen, length); if (error != G_IO_ERROR_NONE || length != bytes_writen) { b_conn_func_fail (conn); return FALSE; } /* Delete the packet and buffer */ b_packet_delete (packet); g_free (buffer); /* Return TRUE. If there were no more packets, this would result in a call to writeable, which may generate more packets. */ return TRUE; } /* Otherwise, if the host is being closed, close it */ else if (b_conn_is_closing (conn)) b_conn_func_close (conn); /* Otherwise, we just don't have any packets to send */ return FALSE; } /* error_cb Called when a socket has an ERR or HUP. Last checked: 2001-4-30 DAH */ gboolean error_cb (GIOChannel* iochannel, GIOCondition condition, gpointer data) { BConn* conn = (BConn*) data; BTPP (4, "ERROR %d\n", condition); b_conn_func_fail (conn); return FALSE; }