Mercurial > pidgin
diff plugins/crazychat/cc_network.c @ 11218:ed017b9c532d
[gaim-migrate @ 13350]
crazychat commit, first one.
committer: Tailor Script <tailor@pidgin.im>
author | Charlie Stockman <chuckleberry> |
---|---|
date | Tue, 09 Aug 2005 07:10:23 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/plugins/crazychat/cc_network.c Tue Aug 09 07:10:23 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(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(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(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(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; +}