diff src/protocols/qq/recv_core.c @ 13870:983fd420e86b

[gaim-migrate @ 16340] Performed minor cleanup of the OpenQ codebase and patched it into the Gaim trunk as a prpl, providing basic QQ functionality. committer: Tailor Script <tailor@pidgin.im>
author Mark Huetsch <markhuetsch>
date Mon, 26 Jun 2006 02:58:54 +0000
parents
children ef8490f9e823
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/qq/recv_core.c	Mon Jun 26 02:58:54 2006 +0000
@@ -0,0 +1,321 @@
+/**
+* The QQ2003C protocol plugin
+ *
+ * for gaim
+ *
+ * Copyright (C) 2004 Puzzlebird
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+// START OF FILE
+/*****************************************************************************/
+#include "debug.h"		// gaim_debug
+#include "internal.h"		// _("get_text")
+
+#include "utils.h"		// hex_dump_to_str
+#include "packet_parse.h"	// MAX_PACKET_SIZE
+#include "buddy_info.h"		// qq_process_modify_info_reply
+#include "buddy_list.h"		// qq_process_get_buddies_list_reply
+#include "buddy_opt.h"		// qq_process_add_buddy_reply
+#include "buddy_status.h"	// qq_process_friend_change_status
+#include "char_conv.h"		// qq_to_utf8
+#include "crypt.h"		// qq_crypt
+#include "group_network.h"	// qq_process_group_cmd_reply
+#include "header_info.h"	// cmd alias
+#include "keep_alive.h"		// qq_process_keep_alive_reply
+#include "im.h"			// qq_process_send_im_reply
+#include "login_logout.h"	// qq_process_login_reply
+#include "qq_proxy.h"		// qq_proxy_read
+#include "recv_core.h"
+#include "sendqueue.h"		// qq_sendqueue_remove
+#include "sys_msg.h"		// qq_process_msg_sys
+
+typedef struct _packet_before_login packet_before_login;
+typedef struct _qq_recv_msg_header qq_recv_msg_header;
+
+struct _packet_before_login {
+	guint8 *buf;
+	gint len;
+};
+
+struct _qq_recv_msg_header {
+	guint8 header_tag;
+	guint16 source_tag;
+	guint16 cmd;
+	guint16 seq;		// can be ack_seq or send_seq, depends on cmd
+};
+
+/*****************************************************************************/
+// check whether one sequence number is duplicated or not
+// return TRUE if it is duplicated, otherwise FALSE
+static gboolean _qq_check_packet_set_window(guint16 seq, GaimConnection * gc)
+{
+	qq_data *qd;
+	gchar *byte, mask;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
+	qd = (qq_data *) gc->proto_data;
+	byte = &(qd->window[seq / 8]);
+	mask = (1 << (seq % 8));
+
+	if ((*byte) & mask)
+		return TRUE;	// check mask
+	(*byte) |= mask;
+	return FALSE;		// set mask
+}				// _qq_check_packet_set_window
+
+/*****************************************************************************/
+// default process, decrypt and dump
+static void _qq_process_packet_default(guint8 * buf, gint buf_len, guint16 cmd, guint16 seq, GaimConnection * gc) {
+
+	qq_data *qd;
+	guint8 *data;
+	gchar *msg_utf8;
+	gint len;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len != 0);
+
+	qd = (qq_data *) gc->proto_data;
+	len = buf_len;
+	data = g_newa(guint8, len);
+	msg_utf8 = NULL;
+
+	if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+		gaim_debug(GAIM_DEBUG_WARNING, "QQ",
+			   ">>> [%d] %s, %d bytes -> [default] decrypt and dump\n%s",
+			   seq, qq_get_cmd_desc(cmd), buf_len, hex_dump_to_str(data, len));
+		try_dump_as_gbk(data, len);
+	} else
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail decrypt packet with default process\n");
+
+}				// _qq_process_packet_default
+
+/*****************************************************************************/
+// process the incoming packet from qq_pending
+static void _qq_packet_process(guint8 * buf, gint buf_len, GaimConnection * gc)
+{
+	qq_data *qd;
+	gint len, bytes_expected, bytes_read;
+	guint16 buf_len_read;	// two bytes in the begining of TCP packet
+	guint8 *cursor;
+	qq_recv_msg_header header;
+	packet_before_login *b4_packet;
+
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	g_return_if_fail(buf != NULL && buf_len > 0);
+
+	qd = (qq_data *) gc->proto_data;
+	bytes_expected = qd->use_tcp ? QQ_TCP_HEADER_LENGTH : QQ_UDP_HEADER_LENGTH;
+
+	if (buf_len < bytes_expected) {
+		gaim_debug(GAIM_DEBUG_ERROR,
+			   "QQ", "Received packet is too short, dump and drop\n%s", hex_dump_to_str(buf, buf_len));
+		return;
+	}
+	// initialize
+	cursor = buf;
+	bytes_read = 0;
+
+	// QQ TCP packet returns first 2 bytes the length of this packet
+	if (qd->use_tcp) {
+		bytes_read += read_packet_w(buf, &cursor, buf_len, &buf_len_read);
+		if (buf_len_read != buf_len) {	// wrong
+			gaim_debug
+			    (GAIM_DEBUG_ERROR,
+			     "QQ",
+			     "TCP read %d bytes, header says %d bytes, use header anyway\n", buf_len, buf_len_read);
+			buf_len = buf_len_read;	// we believe header is more accurate
+		}		// if buf_len_read
+	}			// if use_tcp
+
+	// now goes the normal QQ packet as UDP packet
+	bytes_read += read_packet_b(buf, &cursor, buf_len, &header.header_tag);
+	bytes_read += read_packet_w(buf, &cursor, buf_len, &header.source_tag);
+	bytes_read += read_packet_w(buf, &cursor, buf_len, &header.cmd);
+	bytes_read += read_packet_w(buf, &cursor, buf_len, &header.seq);
+
+	if (bytes_read != bytes_expected) {	// read error
+		gaim_debug(GAIM_DEBUG_ERROR, "QQ",
+			   "Fail reading packet header, expect %d bytes, read %d bytes\n", bytes_expected, bytes_read);
+		return;
+	}			// if bytes_read
+
+	if ((buf[buf_len - 1] != QQ_PACKET_TAIL) || (header.header_tag != QQ_PACKET_TAG)) {
+		gaim_debug(GAIM_DEBUG_ERROR,
+			   "QQ", "Unknown QQ proctocol, dump and drop\n%s", hex_dump_to_str(buf, buf_len));
+		return;
+	}			// if header_tag
+
+	if (QQ_DEBUG)
+		gaim_debug(GAIM_DEBUG_INFO, "QQ",
+			   "==> [%05d] %s, from (%s)\n",
+			   header.seq, qq_get_cmd_desc(header.cmd), qq_get_source_str(header.source_tag));
+
+	if (header.cmd != QQ_CMD_LOGIN && header.cmd != QQ_CMD_REQUEST_LOGIN_TOKEN /* gfhuang */) {
+		if (!qd->logged_in) {	// packets before login
+			b4_packet = g_new0(packet_before_login, 1);
+			// must duplicate, buffer will be freed after exiting this function
+			b4_packet->buf = g_memdup(buf, buf_len);
+			b4_packet->len = buf_len;
+			if (qd->before_login_packets == NULL)
+				qd->before_login_packets = g_queue_new();
+			g_queue_push_head(qd->before_login_packets, b4_packet);
+			return;	// do not process it now
+		} else if (!g_queue_is_empty(qd->before_login_packets)) {
+			// logged_in, but we have packets before login
+			b4_packet = (packet_before_login *)
+			    g_queue_pop_head(qd->before_login_packets);
+			_qq_packet_process(b4_packet->buf, b4_packet->len, gc);
+			// in fact this is a recursive call, 
+			// all packets before login will be processed before goes on
+			g_free(b4_packet->buf);	// the buf is duplicated, need to be freed
+			g_free(b4_packet);
+		}		// if logged_in
+	}			//if header.cmd != QQ_CMD_LOGIN
+
+	// this is the length of all the encrypted data (also remove tail tag
+	len = buf_len - (bytes_read) - 1;
+
+	// whether it is an ack
+	switch (header.cmd) {
+	case QQ_CMD_RECV_IM:
+	case QQ_CMD_RECV_MSG_SYS:
+	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
+		// server intiated packet, we need to send ack and check duplicaion
+		// this must be put after processing b4_packet
+		// as these packets will be passed in twice
+		if (_qq_check_packet_set_window(header.seq, gc)) {
+			gaim_debug(GAIM_DEBUG_WARNING,
+				   "QQ", "dup [%05d] %s, discard...\n", header.seq, qq_get_cmd_desc(header.cmd));
+			return;
+		}
+		break;
+	default:{		// ack packet, we need to update sendqueue
+			// we do not check duplication for server ack
+			qq_sendqueue_remove(qd, header.seq);
+			if (QQ_DEBUG)
+				gaim_debug(GAIM_DEBUG_INFO, "QQ",
+					   "ack [%05d] %s, remove from sendqueue\n",
+					   header.seq, qq_get_cmd_desc(header.cmd));
+		}		// default
+	}			// switch header.cmd
+
+	// now process the packet
+	switch (header.cmd) {
+	case QQ_CMD_KEEP_ALIVE:
+		qq_process_keep_alive_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_UPDATE_INFO:
+		qq_process_modify_info_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_ADD_FRIEND_WO_AUTH:
+		qq_process_add_buddy_reply(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_DEL_FRIEND:
+		qq_process_remove_buddy_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_REMOVE_SELF:
+		qq_process_remove_self_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_BUDDY_AUTH:
+		qq_process_add_buddy_auth_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GET_USER_INFO:
+		qq_process_get_info_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_CHANGE_ONLINE_STATUS:
+		qq_process_change_status_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_SEND_IM:
+		qq_process_send_im_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_RECV_IM:
+		qq_process_recv_im(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_LOGIN:
+		qq_process_login_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GET_FRIENDS_LIST:
+		qq_process_get_buddies_list_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GET_FRIENDS_ONLINE:
+		qq_process_get_buddies_online_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_GROUP_CMD:
+		qq_process_group_cmd_reply(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_GET_ALL_LIST_WITH_GROUP:  //added by gfhuang
+		qq_process_get_all_list_with_group_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_REQUEST_LOGIN_TOKEN:	//added by gfhuang
+		qq_process_request_login_token_reply(cursor, len, gc);
+		break;
+	case QQ_CMD_RECV_MSG_SYS:
+		qq_process_msg_sys(cursor, len, header.seq, gc);
+		break;
+	case QQ_CMD_RECV_MSG_FRIEND_CHANGE_STATUS:
+		qq_process_friend_change_status(cursor, len, gc);
+		break;
+	default:
+		_qq_process_packet_default(cursor, len, header.cmd, header.seq, gc);
+		break;
+	}			// switch header.cmd
+}				// _qq_packet_process
+
+/*****************************************************************************/
+// clean up the packets before login
+void qq_b4_packets_free(qq_data * qd)
+{
+	packet_before_login *b4_packet;
+	g_return_if_fail(qd != NULL);
+	// now clean up my own data structures
+	if (qd->before_login_packets != NULL) {
+		while (NULL != (b4_packet = g_queue_pop_tail(qd->before_login_packets))) {
+			g_free(b4_packet->buf);
+			g_free(b4_packet);
+		}
+		g_queue_free(qd->before_login_packets);
+	}			// if 
+}				// qq_b4_packets_free
+
+/*****************************************************************************/
+void qq_input_pending(gpointer data, gint source, GaimInputCondition cond)
+{
+	GaimConnection *gc;
+	qq_data *qd;;
+	guint8 *buf;
+	gint len;
+
+	gc = (GaimConnection *) data;
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL && cond == GAIM_INPUT_READ);
+
+	qd = (qq_data *) gc->proto_data;
+	// according to glib manual memory allocated by g_newa could be 
+	// automatically freed when the current stack frame is cleaned up
+	buf = g_newa(guint8, MAX_PACKET_SIZE);
+
+	// here we have UDP proxy suppport
+	len = qq_proxy_read(qd, buf, MAX_PACKET_SIZE);
+	if (len <= 0) {
+		gaim_connection_error(gc, _("Unable to read from socket"));
+		return;
+	} else
+		_qq_packet_process(buf, len, gc);
+}				// qq_input_pending
+
+/*****************************************************************************/
+// END OF FILE