diff plugins/crazychat/cc_network.c @ 11232:8bcd4d4ccef6

[gaim-migrate @ 13372] committing crazychat again. hopefully here to stay. committer: Tailor Script <tailor@pidgin.im>
author Charlie Stockman <chuckleberry>
date Thu, 11 Aug 2005 07:56:29 +0000
parents
children 80ee726755d4
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/crazychat/cc_network.c	Thu Aug 11 07:56:29 2005 +0000
@@ -0,0 +1,540 @@
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <gtk/gtk.h>
+#include "conversation.h"
+#include "network.h"
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "cc_network.h"
+#include "cc_interface.h"
+#include "util.h"
+
+/* --- begin constant definitions --- */
+
+#define NETWORK_TIMEOUT_DELAY	40		/* in ms */
+#define MAX_ACCEPT_CHECKS	1000
+
+/* --- begin type declarations --- */
+
+struct accept_args {
+	GaimAccount *account;
+	struct crazychat *cc;
+	char *name;
+	guint32 peer_ip;
+	guint16 peer_port;
+};
+
+struct sock_accept_args {
+	GaimAccount *account;
+	struct cc_session *session;
+};
+
+/* --- begin function prototypes --- */
+
+/**
+ * Creates a server socket and sends a response to the peer.
+ * @param account		the gaim account sending the ready msg
+ * @param session		the peer CrazyChat session
+ */
+static void cc_net_send_ready(GaimAccount *account, struct cc_session *session);
+
+/**
+ * Handles responses from the CrazyChat session invite dialog box.
+ * @param dialog		the dialog box
+ * @param response		the dialog box button response
+ * @param args			account, crazychat global data, peer name
+ */
+static void invite_handler(GtkDialog *dialog, gint response,
+		struct accept_args *args);
+
+/**
+ * Periodically checks the server socket for peer's connection.  Gives up
+ * after a set number of checks.
+ * @param args			peer session and account
+ * @return			TRUE to continue checking, FALSE to stop
+ */
+static gboolean accept_cb(struct sock_accept_args *args);
+
+/**
+ * Initialize CrazyChat network session.  Sets up the UDP socket and port.
+ * @param account		the account the session is part of
+ * @param session		the CrazyChat network session
+ */
+static void init_cc_net_session(GaimAccount *account,
+		struct cc_session *session);
+
+/**
+ * Handles checking the network for new feature data and sending out the 
+ * latest features.
+ * @param session		the session we're checking for network traffic
+ */
+static gboolean network_cb(struct cc_session *session);
+
+/**
+ * Generates random bytes in the user specified byte buffer.
+ * @param buf		the byte buffer
+ * @param len		length of the byte buffer
+ */
+static void generate_randomness(uint8_t buf[], unsigned int len);
+
+/**
+ * Sends data over a socket.
+ * @param s   socket file descriptor
+ * @param buf data buffer
+ * @param len data buffer length
+ * @return    number of bytes sent or -1 if an error occurred
+ */
+static int __send(int s, char *buf, int len);
+
+/* --- begin function definitions --- */
+
+void cc_net_send_invite(struct crazychat *cc, char *name, GaimAccount *account)
+{
+	struct cc_session *session;
+	GaimConversation *conv;
+	GaimConvIm *im;
+	char buf[BUFSIZ];
+	
+	session = cc_find_session(cc, name);
+	if (session) return; /* already have a session with this guy */
+	session = cc_add_session(cc, name);
+	session->state = INVITE;
+	conv = gaim_find_conversation_with_account(GAIM_CONV_ANY, name, account);
+	if (!conv) {
+		conv = gaim_conversation_new(GAIM_CONV_IM, account, name);
+	}
+	im = gaim_conversation_get_im_data(conv);
+	snprintf(buf, BUFSIZ, "%s%s!%d", CRAZYCHAT_INVITE_CODE,
+		gaim_network_get_ip_for_account(account, -1), cc->tcp_port);
+	Debug("Sent invite to %s for port: %d\n", name, cc->tcp_port);
+	gaim_conv_im_send(im, buf);
+}
+
+void cc_net_recv_invite(GaimAccount *account, struct crazychat *cc, char *name,
+		const char *peer_ip, const char *peer_port)
+{
+	struct cc_session *session;
+	GaimConversation *conv;
+	GaimConvWindow *convwin;
+	char buf[BUFSIZ];
+	struct accept_args *args;
+
+	assert(cc);
+	assert(name);
+	Debug("Received a CrazyChat session invite from %s on port %s!\n",
+			name, peer_port);
+	session = cc_find_session(cc, name);
+	if (!session) {
+		Debug("Creating a CrazyChat session invite dialog box!\n");
+		conv = gaim_find_conversation_with_account(GAIM_CONV_ANY, name, account);
+		if (conv) convwin = gaim_conversation_get_window(conv);
+		else convwin = NULL;
+		/* pop gtk window asking if want to accept */
+		GtkWidget *dialog =
+			gtk_dialog_new_with_buttons("CrazyChat Session Invite",
+			GTK_WINDOW(convwin),
+			GTK_DIALOG_MODAL |
+			GTK_DIALOG_DESTROY_WITH_PARENT,
+			GTK_STOCK_OK,
+			GTK_RESPONSE_ACCEPT,
+			GTK_STOCK_CANCEL,
+			GTK_RESPONSE_REJECT,
+			NULL);
+		snprintf(buf, BUFSIZ, "Would you like to CRaZYchAT with %s?", name);
+		GtkWidget *label = gtk_label_new(buf);
+		gtk_container_add(GTK_CONTAINER (GTK_DIALOG(dialog)->vbox),
+				label);
+		args = (struct accept_args*)malloc(sizeof(*args));
+		args->account = account;
+		args->cc = cc;
+		args->name = strdup(name);
+		assert(inet_aton(peer_ip, (struct in_addr*)&args->peer_ip));
+		args->peer_port = atoi(peer_port);
+
+		g_signal_connect(GTK_OBJECT(dialog), "response",
+				G_CALLBACK(invite_handler), args);
+		
+		gtk_widget_show_all(dialog);
+	}
+}
+
+void cc_net_recv_accept(GaimAccount *account, struct crazychat *cc, char *name,
+		const char *peer_ip)
+{
+	struct cc_session *session;
+	struct in_addr peer_addr;
+
+	assert(cc);
+	assert(name);
+	Debug("Received a CrazyChat session accept!\n");
+	session = cc_find_session(cc, name);
+	if (session && session->state == INVITE) {
+		session->state = ACCEPTED;
+		assert(inet_aton(peer_ip, &peer_addr));
+		session->peer_ip = peer_addr.s_addr;
+		cc_net_send_ready(account, session);
+	}
+}
+
+static void cc_net_send_ready(GaimAccount *account, struct cc_session *session)
+{
+	struct sock_accept_args *args;
+	
+	assert(session);
+	Debug("Initializing the server socket and sending ready message\n");
+	/* create the server socket */
+	session->tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
+	assert(session->tcp_sock != -1);
+	int reuse = 1;
+	assert(setsockopt(session->tcp_sock, SOL_SOCKET, SO_REUSEADDR,
+				&reuse, sizeof(int)) != -1);
+	struct sockaddr_in my_addr;
+	my_addr.sin_family = AF_INET;
+	my_addr.sin_port = htons(session->cc->tcp_port);
+	assert(inet_aton(gaim_network_get_ip_for_account(account, -1),
+			&my_addr.sin_addr));
+	memset(&my_addr.sin_zero, 0, sizeof(my_addr.sin_zero));
+	assert(bind(session->tcp_sock, (struct sockaddr*)&my_addr,
+				sizeof(my_addr)) != -1);
+	Debug("Listening on port: %d\n", my_addr.sin_port);
+	assert(listen(session->tcp_sock, 1) != -1);
+
+	/* socket created, send the ready message */
+	GaimConversation *conv;
+	GaimConvIm *im;
+
+	conv = gaim_find_conversation_with_account(GAIM_CONV_ANY, session->name, account);
+	if (!conv) {
+		conv = gaim_conversation_new(GAIM_CONV_IM, account,
+				session->name);
+	}
+	im = gaim_conversation_get_im_data(conv);
+	gaim_conv_im_send(im, CRAZYCHAT_READY_CODE);
+
+	/* register timer callback for checking socket connection */
+	args = (struct sock_accept_args*)malloc(sizeof(*args));
+	args->session = session;
+	args->account = account;
+	session->udp_sock = MAX_ACCEPT_CHECKS;
+	session->timer_id = g_timeout_add(NETWORK_TIMEOUT_DELAY,
+		(GSourceFunc)accept_cb, args);
+}
+
+void cc_net_recv_ready(GaimAccount *account, struct crazychat *cc, char *name)
+{
+	struct cc_session *session;
+	struct sockaddr_in server_addr, my_addr;
+	int sock;
+
+	assert(cc);
+	assert(name);
+	Debug("Received a CrazyChat session ready!\n");
+	session = cc_find_session(cc, name);
+	if (session && session->state == ACCEPTED) {
+		/* connect to peer */
+		session->tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
+		assert(session->tcp_sock != -1);
+		server_addr.sin_family = AF_INET;
+		server_addr.sin_port = session->peer_port;
+		server_addr.sin_addr.s_addr = session->peer_ip;
+		memset(&(server_addr.sin_zero), 0,
+				sizeof(server_addr.sin_zero));
+		assert(connect(session->tcp_sock,
+				(struct sockaddr*)&server_addr,
+				sizeof(server_addr)) != -1);
+		Debug("Connecting to peer on port %d\n", session->peer_port);
+
+		/* now set state */
+		session->state = CONNECTED;
+		init_cc_net_session(account, session);
+	}
+}
+
+static void invite_handler(GtkDialog *dialog, gint response, struct accept_args *args)
+{
+	struct cc_session *session;
+	char buf[BUFSIZ];
+	GaimConversation *conv;
+	GaimConvIm *im;
+	
+	if (response == GTK_RESPONSE_ACCEPT) {
+		assert(args);
+		session = cc_find_session(args->cc, args->name);
+		assert(!session);
+		session = cc_add_session(args->cc, args->name);
+		session->state = ACCEPTED;
+		session->peer_ip = args->peer_ip;
+		session->peer_port = args->peer_port;
+		snprintf(buf, BUFSIZ, "%s%s", CRAZYCHAT_ACCEPT_CODE,
+			gaim_network_get_ip_for_account(args->account, -1));
+		conv = gaim_find_conversation_with_account(GAIM_CONV_ANY, args->name,
+				args->account);
+		if (!conv) {
+			conv = gaim_conversation_new(GAIM_CONV_IM,
+				args->account, args->name);
+		}
+		im = gaim_conversation_get_im_data(conv);
+		gaim_conv_im_send(im, buf);
+	}
+	free(args->name);
+	free(args);
+	gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static gboolean accept_cb(struct sock_accept_args *args)
+{
+	fd_set fds;
+	struct timeval zero;
+	int ret;
+	GaimAccount *account;
+	struct cc_session *session;
+
+	assert(args);
+	account = args->account;
+	session = args->session;
+	assert(account);
+	assert(session);
+
+	/* set select to check on our tcp socket */
+	FD_ZERO(&fds);
+	FD_SET(session->tcp_sock, &fds);
+	memset(&zero, 0, sizeof(zero));
+
+	/* check socket */
+	ret = select(session->tcp_sock+1,&fds, NULL, NULL, &zero);
+	assert(ret != -1);
+
+	if (ret) { /* got something to check */
+		Debug("Checking pending connection\n");
+		int sock;
+		struct sockaddr_in client_addr;
+		socklen_t sin_size;
+
+		sin_size = sizeof(client_addr);
+		sock = accept(session->tcp_sock,
+				(struct sockaddr*)&client_addr, &sin_size);
+		assert(sock != -1);
+		
+		/* check if it's a match */
+		if (client_addr.sin_addr.s_addr == session->peer_ip) {
+			/* cool, we're set */
+			Debug("Accepted tcp connect from %s\n", session->name);
+			close(session->tcp_sock);
+			session->tcp_sock = sock;
+			session->state = CONNECTED;
+			session->timer_id = 0;
+			init_cc_net_session(account, session);
+			Debug("Will start sending to port %d\n",
+					session->peer_port);
+			free(args);
+			return FALSE;
+		}
+	}
+
+	session->udp_sock--;
+
+	if (!session->udp_sock) { /* timed out */
+		/* remove session from session list */
+		cc_remove_session(session->cc, session);
+		free(args);
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static void init_cc_net_session(GaimAccount *account,
+		struct cc_session *session)
+{
+	struct sockaddr_in my_addr;
+	struct sockaddr_in peer_addr;
+	int reuse;
+	
+	/* send/obtain the udp port information */
+	
+	assert(__send(session->tcp_sock, (char*)&session->cc->udp_port,
+			sizeof(session->cc->udp_port)) ==
+			sizeof(session->cc->udp_port));
+	assert(recv(session->tcp_sock, (char*)&session->peer_port,
+			sizeof(session->peer_port), 0) ==
+			sizeof(session->peer_port));
+
+	Debug("Established a CrazyChat session with %s!\n", session->name);
+	
+	/* connect the udp sockets */
+	
+	session->udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
+
+	assert(!setsockopt(session->udp_sock, SOL_SOCKET, SO_REUSEADDR,
+			&reuse, sizeof(reuse)));
+	
+	my_addr.sin_family = AF_INET;
+	my_addr.sin_port = htons(session->cc->udp_port);
+	assert(inet_aton(gaim_network_get_ip_for_account(account, -1),
+				&my_addr.sin_addr));
+	memset(my_addr.sin_zero, 0, sizeof(my_addr.sin_zero));
+	assert(!bind(session->udp_sock, (struct sockaddr*)&my_addr,
+			sizeof(my_addr)));
+	session->peer.sin_family = AF_INET;
+	session->peer.sin_port = htons(session->peer_port);
+	session->peer.sin_addr.s_addr = session->peer_ip;
+	memset(&session->peer.sin_zero, 0, sizeof(session->peer.sin_zero));
+
+	Debug("Bound udp sock to port %d, connecting to port %d\n",
+		session->cc->udp_port, session->peer_port);
+	
+	memset(&session->features, 0, sizeof(session->features));
+
+	session->output = init_output(&session->features, session);
+
+	session->filter = Filter_Initialize();
+
+	/* initialize timer callback */
+	session->timer_id = g_timeout_add(NETWORK_TIMEOUT_DELAY,
+		(GSourceFunc)network_cb, session);
+
+	/* initialize input subsystem if not initialized */
+	if (!session->cc->features_state) {
+		session->cc->input_data = init_input(session->cc);
+		session->cc->features_state = 1;
+	}
+}
+
+static gboolean network_cb(struct cc_session *session)
+{
+	fd_set fds;
+	struct timeval zero;
+	int ret;
+	int command;
+	struct cc_features *features;
+
+	assert(session);
+
+	Debug("Checking for data\n");
+
+	/* set select to check on our tcp socket */
+	FD_ZERO(&fds);
+	FD_SET(session->tcp_sock, &fds);
+	memset(&zero, 0, sizeof(zero));
+
+	/* check tcp socket */
+	ret = select(session->tcp_sock+1, &fds, NULL, NULL, &zero);
+	assert(ret != -1);
+
+	while (ret) {
+		ret = recv(session->tcp_sock, &command, sizeof(command), 0);
+		assert(ret != -1);
+		if (!ret) {
+			/* tcp connection closed, destroy connection */
+			gtk_widget_destroy(session->output->widget);
+			return FALSE;
+		}
+		assert(ret == sizeof(command));
+
+		FD_ZERO(&fds);
+		FD_SET(session->tcp_sock, &fds);
+		ret = select(session->tcp_sock+1, &fds, NULL, NULL, &zero);
+		assert(ret != -1);
+	}
+
+	/* set select to check on our udp socket */
+	FD_ZERO(&fds);
+	FD_SET(session->udp_sock, &fds);
+	memset(&zero, 0, sizeof(zero));
+
+	/* check udp socket */
+	ret = select(session->udp_sock+1, &fds, NULL, NULL, &zero);
+	assert(ret != -1);
+
+	features = &session->features;
+		
+	while (ret) { /* have data, let's copy it for output */
+		struct sockaddr_in from;
+		int fromlen;
+		ret = recvfrom(session->udp_sock, &session->features,
+				sizeof(session->features),
+				0, (struct sockaddr*)&from, &fromlen);
+		Debug("Received %d bytes from port %d\n", ret,
+				ntohs(from.sin_port));
+		filter(features, session->filter);
+		Debug("\thead size: %d\n", features->head_size);
+		Debug("\topen: left(%s), right(%s), mouth(%s)\n",
+				features->left_eye_open ? "yes" : "no",
+				features->right_eye_open ? "yes" : "no",
+				features->mouth_open ? "yes" : "no");
+		Debug("\thead rotation: x(%d), y(%d), z(%d)\n",
+			features->head_x_rot, features->head_y_rot,
+			features->head_z_rot);
+		Debug("\tx(%d), y(%d)\n", features->x, features->y);
+		if (ret == -1) {
+			perror("wtf:");
+		}
+		assert(ret != -1);
+
+		FD_ZERO(&fds);
+		FD_SET(session->udp_sock, &fds);
+		ret = select(session->udp_sock+1, &fds, NULL, NULL, &zero);
+		assert(ret != -1);
+	}
+
+#ifdef _DISABLE_QT_
+	struct cc_features bogus;
+	features = &bogus;
+	generate_randomness((uint8_t*)features, sizeof(*features));
+#else
+	features = &session->cc->input_data->face;
+#endif
+	assert(sendto(session->udp_sock, (char*)features,
+			sizeof(*features), 0, (struct sockaddr*)&session->peer,
+			sizeof(session->peer)) == sizeof(*features));
+	Debug("Sent %d bytes\n", sizeof(*features));
+	Debug("\thead size: %d\n", features->head_size);
+	Debug("\topen: left(%s), right(%s), mouth(%s)\n",
+			features->left_eye_open ? "yes" : "no",
+			features->right_eye_open ? "yes" : "no",
+			features->mouth_open ? "yes" : "no");
+	Debug("\thead rotation: x(%d), y(%d), z(%d)\n",
+		features->head_x_rot, features->head_y_rot,
+		features->head_z_rot);
+	Debug("\tx(%d), y(%d)\n", features->x, features->y);
+
+	/* clear easter egg */
+	features->mode = 0;
+
+	return TRUE;
+}
+
+static void generate_randomness(uint8_t buf[], unsigned int len)
+{
+	int fd;
+
+	fd = open("/dev/random", O_RDONLY);
+	assert(fd != -1);
+
+	assert(read(fd, buf, len) == len);
+	close(fd);
+}
+
+static int __send(int s, char *buf, int len)
+{
+	int total = 0;		/* how many bytes we've sent */
+	int bytesleft = len;	/* how many we have left to send */
+	int n;
+
+	while (total < len) {
+		n = send(s, buf + total, bytesleft, 0);
+		if (n == -1) {
+			Debug("ERROR: %s\n", strerror(errno));
+			return -1;
+		}
+		total += n;
+		bytesleft -= n;
+	}
+
+	return total;
+}