diff libpurple/protocols/qq/qq_network.c @ 23754:967344bc404d

applied changes from f12c8903079425d7850fa183df0b3f937b2952be through 8cebefbc6cd5d84acb69c74e69e8821f11dd225d Backport of 8cebefbc6cd5d84acb69c74e69e8821f11dd225d to avoid having other changes overwritten. 2008.08.16 - ccpaging <ecc_hy(at)hotmail.com> * Rename group to room. If you used pidginqq before, this may create a new room with same title, you may delete old one * Replace purple_debug with purple_debug_info, purple_debug_warning, purple_debug_error * Add server notice and server new, and two options to turn on/off * Minor modify for reducing transaction's debug infor * Minor modifies for system notice and QQ news. * Add 4 new strings need translate compare with p10. committer: Daniel Atallah <daniel.atallah@gmail.com>
author SHiNE CsyFeK <csyfek@gmail.com>
date Mon, 15 Sep 2008 02:59:23 +0000
parents 5f454b975a99
children 23cec4360d4a
line wrap: on
line diff
--- a/libpurple/protocols/qq/qq_network.c	Sun Aug 10 04:32:14 2008 +0000
+++ b/libpurple/protocols/qq/qq_network.c	Mon Sep 15 02:59:23 2008 +0000
@@ -26,11 +26,6 @@
 #include "debug.h"
 #include "internal.h"
 
-#ifdef _WIN32
-#define random rand
-#define srandom srand
-#endif
-
 #include "buddy_info.h"
 #include "group_info.h"
 #include "group_free.h"
@@ -44,63 +39,100 @@
 #include "utils.h"
 #include "qq_process.h"
 
-/* set QQ_RECONNECT_MAX to 1, when test reconnecting */
-#define QQ_RECONNECT_MAX					4
-#define QQ_RECONNECT_INTERVAL		5000
-#define QQ_KEEP_ALIVE_INTERVAL		60000
-#define QQ_TRANS_INTERVAL				10000
+#define QQ_DEFAULT_PORT					8000
+
+/* set QQ_CONNECT_MAX to 1, when test reconnecting */
+#define QQ_CONNECT_MAX						3
+#define QQ_CONNECT_INTERVAL			2
+#define QQ_CONNECT_CHECK					5
+#define QQ_KEEP_ALIVE_INTERVAL		60
+#define QQ_TRANS_INTERVAL				10
+
+gboolean connect_to_server(PurpleConnection *gc, gchar *server, gint port);
+
+static qq_connection *connection_find(qq_data *qd, int fd) {
+	qq_connection *ret = NULL;
+	GSList *entry = qd->openconns;
+	while(entry) {
+		ret = entry->data;
+		if(ret->fd == fd) return ret;
+		entry = entry->next;
+	}
+	return NULL;
+}
 
+static qq_connection *connection_create(qq_data *qd, int fd) {
+	qq_connection *ret = g_new0(qq_connection, 1);
+	ret->fd = fd;
+	qd->openconns = g_slist_append(qd->openconns, ret);
+	return ret;
+}
+
+static void connection_remove(qq_data *qd, int fd) {
+	qq_connection *conn = connection_find(qd, fd);
+	qd->openconns = g_slist_remove(qd->openconns, conn);
+
+	g_return_if_fail( conn != NULL );
+
+	purple_debug_info("QQ", "Close socket %d\n", conn->fd);
+	if(conn->input_handler > 0)	purple_input_remove(conn->input_handler);
+	if(conn->can_write_handler > 0)	purple_input_remove(conn->can_write_handler);
+
+	if (conn->fd >= 0)	close(conn->fd);
+	if(conn->tcp_txbuf != NULL) 	purple_circ_buffer_destroy(conn->tcp_txbuf);
+	if (conn->tcp_rxqueue != NULL)	g_free(conn->tcp_rxqueue);
+
+	g_free(conn);
+}
+
+static void connection_free_all(qq_data *qd) {
+	qq_connection *ret = NULL;
+	GSList *entry = qd->openconns;
+	while(entry) {
+		ret = entry->data;
+		connection_remove(qd, ret->fd);
+		entry = qd->openconns;
+	}
+}
 static gboolean set_new_server(qq_data *qd)
 {
 	gint count;
 	gint index;
 	GList *it = NULL;
-	
+
  	g_return_val_if_fail(qd != NULL, FALSE);
 
 	if (qd->servers == NULL) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Server list is NULL\n");
+		purple_debug_info("QQ", "Server list is NULL\n");
 		return FALSE;
 	}
 
-	if (qd->real_hostname) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free real_hostname\n");
-		g_free(qd->real_hostname);
-		qd->real_hostname = NULL;
-	}
+	/* remove server used before */
+	if (qd->curr_server != NULL) {
+		purple_debug_info("QQ",
+			"Remove current [%s] from server list\n", qd->curr_server);
+   		qd->servers = g_list_remove(qd->servers, qd->curr_server);
+   		qd->curr_server = NULL;
+    }
 
-	/* remove server used before */
-	if (qd->server_name != NULL) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ",
-			"Remove previous server [%s]\n", qd->server_name);
-   		qd->servers = g_list_remove(qd->servers, qd->server_name);
-   		qd->server_name = NULL;
-    }
-	
 	count = g_list_length(qd->servers);
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Server list has %d\n", count);
+	purple_debug_info("QQ", "Server list has %d\n", count);
 	if (count <= 0) {
 		/* no server left, disconnect when result is false */
 		qd->servers = NULL;
 		return FALSE;
 	}
-	
+
 	/* get new server */
-	index  = random() % count;
+	index  = rand() % count;
 	it = g_list_nth(qd->servers, index);
-    qd->server_name = it->data;		/* do not free server_name */
-    if (qd->server_name == NULL || strlen(qd->server_name) <= 0 ) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Server name at %d is empty\n", index);
+    qd->curr_server = it->data;		/* do not free server_name */
+    if (qd->curr_server == NULL || strlen(qd->curr_server) <= 0 ) {
+		purple_debug_info("QQ", "Server name at %d is empty\n", index);
 		return FALSE;
 	}
 
-	qd->real_hostname = g_strdup(qd->server_name);
-	qd->real_port = qd->user_port;
-	
- 	qd->reconnect_times = QQ_RECONNECT_MAX;
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ",
-		"set new server to %s:%d\n", qd->real_hostname, qd->real_port);
+	purple_debug_info("QQ", "set new server to %s\n", qd->curr_server);
 	return TRUE;
 }
 
@@ -115,152 +147,208 @@
 	return bytes;
 }
 
-static gboolean reconnect_later_cb(gpointer data)
+static gboolean connect_check(gpointer data)
 {
-	PurpleConnection *gc;
+	PurpleConnection *gc = (PurpleConnection *) data;
 	qq_data *qd;
 
-	gc = (PurpleConnection *) data;
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
+	qd = (qq_data *) gc->proto_data;
+
+	if (qd->connect_watcher > 0) {
+		purple_timeout_remove(qd->connect_watcher);
+		qd->connect_watcher = 0;
+	}
+
+	if (qd->fd >= 0 && qd->token != NULL && qd->token_len >= 0) {
+		purple_debug_info("QQ", "Connect ok\n");
+		return FALSE;
+	}
+
+	qd->connect_watcher = purple_timeout_add_seconds(0, qq_connect_later, gc);
+	return FALSE;
+}
+
+/* Warning: qq_connect_later destory all connection
+ *  Any function should be care of use qq_data after call this function
+ *  Please conside tcp_pending and udp_pending */
+gboolean qq_connect_later(gpointer data)
+{
+	PurpleConnection *gc = (PurpleConnection *) data;
+	qq_data *qd;
+	char *server;
+	int port;
+	gchar **segments;
+
 	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
 	qd = (qq_data *) gc->proto_data;
 
-	qd->reconnect_timeout = 0;
+	if (qd->check_watcher > 0) {
+		purple_timeout_remove(qd->check_watcher);
+		qd->check_watcher = 0;
+	}
+	qq_disconnect(gc);
+
+	if (qd->redirect_ip.s_addr != 0) {
+		/* redirect to new server */
+		server = g_strdup_printf("%s:%d", inet_ntoa(qd->redirect_ip), qd->redirect_port);
+		qd->servers = g_list_append(qd->servers, server);
+		qd->curr_server = server;
+
+		qd->redirect_ip.s_addr = 0;
+		qd->redirect_port = 0;
+		qd->connect_retry = QQ_CONNECT_MAX;
+	}
 
-	qq_connect(gc->account);
+	if (qd->curr_server == NULL || strlen (qd->curr_server) == 0 || qd->connect_retry <= 0) {
+		if ( set_new_server(qd) != TRUE) {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+					_("Failed to connect all servers"));
+			return FALSE;
+		}
+		qd->connect_retry = QQ_CONNECT_MAX;
+	}
+
+	segments = g_strsplit_set(qd->curr_server, ":", 0);
+	server = g_strdup(segments[0]);
+	port = atoi(segments[1]);
+	if (port <= 0) {
+		purple_debug_info("QQ", "Port not define in %s\n", qd->curr_server);
+		port = QQ_DEFAULT_PORT;
+	}
+	g_strfreev(segments);
+
+	qd->connect_retry--;
+	if ( !connect_to_server(gc, server, port) ) {
+			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Unable to connect."));
+	}
+
+	qd->check_watcher = purple_timeout_add_seconds(QQ_CONNECT_CHECK, connect_check, gc);
 	return FALSE;	/* timeout callback stops */
 }
 
-static void reconnect_later(PurpleConnection *gc)
-{
-	qq_data *qd;
-
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-	qd = (qq_data *) gc->proto_data;
-
-	qd->reconnect_times--;
-	if (qd->reconnect_times < 0) {
-		if ( set_new_server(qd) != TRUE) {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					_("Failed to connect server"));
-			return;
-		}
-	}
-
-	purple_debug(PURPLE_DEBUG_INFO, "QQ",
-		"Reconnect to server %s:%d next retries %d in %d ms\n",
-		qd->real_hostname, qd->real_port,
-		qd->reconnect_times, QQ_RECONNECT_INTERVAL);
-
-	qd->reconnect_timeout = purple_timeout_add(QQ_RECONNECT_INTERVAL,
-		reconnect_later_cb, gc);
-}
-
 /* process the incoming packet from qq_pending */
-static void packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len)
+static gboolean packet_process(PurpleConnection *gc, guint8 *buf, gint buf_len)
 {
 	qq_data *qd;
 	gint bytes, bytes_not_read;
 
-	gboolean prev_login_status;
-	
+	gboolean prev_update_status;
+
 	guint8 header_tag;
 	guint16 source_tag;
 	guint16 cmd;
 	guint16 seq;		/* May be ack_seq or send_seq, depends on cmd */
-	
 	guint8 room_cmd;
 	guint32 room_id;
+	gint update_class;
+	guint32 ship32;
 
 	qq_transaction *trans;
 
-	g_return_if_fail(buf != NULL && buf_len > 0);
+	g_return_val_if_fail(buf != NULL && buf_len > 0, TRUE);
 
 	qd = (qq_data *) gc->proto_data;
 
-	prev_login_status = qd->logged_in;
-
 	/* Len, header and tail tag have been checked before */
 	bytes = 0;
 	bytes += packet_get_header(&header_tag, &source_tag, &cmd, &seq, buf + bytes);
 
 #if 1
-		purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				"==> [%05d] 0x%04X %s, from (0x%04X %s) len %d\n",
-				seq, cmd, qq_get_cmd_desc(cmd), source_tag, qq_get_ver_desc(source_tag), buf_len);
-#endif	
+		purple_debug_info("QQ", "==> [%05d] 0x%04X %s, source tag 0x%04X len %d\n",
+				seq, cmd, qq_get_cmd_desc(cmd), source_tag, buf_len);
+#endif
+	/* this is the length of all the encrypted data (also remove tail tag) */
 	bytes_not_read = buf_len - bytes - 1;
 
 	/* ack packet, we need to update send tranactions */
 	/* we do not check duplication for server ack */
-	trans = qq_trans_find_rcved(qd, cmd, seq);
+	trans = qq_trans_find_rcved(gc, cmd, seq);
 	if (trans == NULL) {
 		/* new server command */
-		qq_trans_add_server_cmd(qd, cmd, seq, buf + bytes, bytes_not_read);
-		if ( qd->logged_in ) {
+		qq_trans_add_server_cmd(gc, cmd, seq, buf + bytes, bytes_not_read);
+		if ( qd->is_finish_update ) {
 			qq_proc_cmd_server(gc, cmd, seq, buf + bytes, bytes_not_read);
 		}
-		return;
+		return TRUE;
 	}
 
 	if (qq_trans_is_dup(trans)) {
-		purple_debug(PURPLE_DEBUG_WARNING,
-				"QQ", "dup [%05d] %s, discard...\n", seq, qq_get_cmd_desc(cmd));
-		return;
+		purple_debug_info("QQ", "dup [%05d] %s, discard...\n", seq, qq_get_cmd_desc(cmd));
+		return TRUE;
 	}
 
 	if (qq_trans_is_server(trans)) {
-		if ( qd->logged_in ) {
+		if ( qd->is_finish_update ) {
 			qq_proc_cmd_server(gc, cmd, seq, buf + bytes, bytes_not_read);
 		}
-		return;
+		return TRUE;
 	}
 
-	/* this is the length of all the encrypted data (also remove tail tag */
-	if (cmd == QQ_CMD_ROOM) {
-		room_cmd = qq_trans_get_room_cmd(trans);
-		room_id = qq_trans_get_room_id(trans);
+	update_class = qq_trans_get_class(trans);
+	ship32 = qq_trans_get_ship(trans);
+
+	prev_update_status = qd->is_finish_update;
+	switch (cmd) {
+		case QQ_CMD_TOKEN:
+			if (qq_process_token_reply(gc, buf + bytes, bytes_not_read) == QQ_TOKEN_REPLY_OK) {
+				qq_send_packet_login(gc);
+			}
+			break;
+		case QQ_CMD_LOGIN:
+			qq_proc_cmd_login(gc, buf + bytes, bytes_not_read);
+			/* check is redirect or not, and do it now */
+			if (qd->redirect_ip.s_addr != 0) {
+				if (qd->check_watcher > 0) {
+					purple_timeout_remove(qd->check_watcher);
+					qd->check_watcher = 0;
+				}
+				if (qd->connect_watcher > 0)	purple_timeout_remove(qd->connect_watcher);
+				qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc);
+				return FALSE;	/* do nothing after this function and return now */
+			}
+			break;
+		case QQ_CMD_ROOM:
+			room_cmd = qq_trans_get_room_cmd(trans);
+			room_id = qq_trans_get_room_id(trans);
 #if 1
-		purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				"%s (0x%02X ) for room %d, len %d\n",
-				qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
-#endif	
-		qq_proc_room_cmd_reply(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read);
-	} else {
-		qq_proc_cmd_reply(gc, cmd, seq, buf + bytes, bytes_not_read);
-	}
-	
-	/* check is redirect or not, and do it now */
-	if (qd->is_redirect) {
-	 	/* free resource except real_hostname and port */
-		qq_disconnect(gc);
-	 	qd->reconnect_times = QQ_RECONNECT_MAX;
-		reconnect_later(gc);
-		return;
+			purple_debug_info("QQ", "%s (0x%02X) for room %d, len %d\n",
+					qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
+#endif
+			qq_proc_room_cmd_reply(gc, seq, room_cmd, room_id, buf + bytes, bytes_not_read, update_class, ship32);
+			break;
+		default:
+			qq_proc_cmd_reply(gc, cmd, seq, buf + bytes, bytes_not_read, update_class, ship32);
+			break;
 	}
 
-	if (prev_login_status != qd->logged_in && qd->logged_in == TRUE) {
-		/* logged_in, but we have packets before login */
-		qq_trans_process_before_login(qd);
+	if (prev_update_status != qd->is_finish_update && qd->is_finish_update == TRUE) {
+		/* is_login, but we have packets before login */
+		qq_trans_process_before_login(gc);
+		return TRUE;
 	}
+	return TRUE;
 }
 
 static void tcp_pending(gpointer data, gint source, PurpleInputCondition cond)
 {
-	PurpleConnection *gc;
+	PurpleConnection *gc = (PurpleConnection *) data;
 	qq_data *qd;
+	qq_connection *conn;
 	guint8 buf[1024];		/* set to 16 when test  tcp_rxqueue */
 	gint buf_len;
 	gint bytes;
-	
+
 	guint8 *pkt;
 	guint16 pkt_len;
-	
+
 	gchar *error_msg;
 	guint8 *jump;
 	gint jump_len;
 
-	gc = (PurpleConnection *) data;
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
 
 	if(cond != PURPLE_INPUT_READ) {
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -268,8 +356,9 @@
 		return;
 	}
 
-	qd = (qq_data *) gc->proto_data;
-	
+	conn = connection_find(qd, source);
+	g_return_if_fail(conn != NULL);
+
 	/* test code, not using tcp_rxqueue
 	memset(pkt,0, sizeof(pkt));
 	buf_len = read(qd->fd, pkt, sizeof(pkt));
@@ -278,8 +367,8 @@
 	}
 	return;
 	*/
-	
-	buf_len = read(qd->fd, buf, sizeof(buf));
+
+	buf_len = read(source, buf, sizeof(buf));
 	if (buf_len < 0) {
 		if (errno == EAGAIN)
 			/* No worries */
@@ -299,93 +388,93 @@
 	 *  QQ need a keep alive packet in every 60 seconds
 	 gc->last_received = time(NULL);
 	*/
-	/*
-	purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
-			   "Read %d bytes from socket, rxlen is %d\n", buf_len, qd->tcp_rxlen);
-	*/
-	qd->tcp_rxqueue = g_realloc(qd->tcp_rxqueue, buf_len + qd->tcp_rxlen);
-	memcpy(qd->tcp_rxqueue + qd->tcp_rxlen, buf, buf_len);
-	qd->tcp_rxlen += buf_len;
-	
+	/* purple_debug_info("TCP_PENDING", "Read %d bytes, rxlen is %d\n", buf_len, conn->tcp_rxlen); */
+	conn->tcp_rxqueue = g_realloc(conn->tcp_rxqueue, buf_len + conn->tcp_rxlen);
+	memcpy(conn->tcp_rxqueue + conn->tcp_rxlen, buf, buf_len);
+	conn->tcp_rxlen += buf_len;
+
 	pkt = g_newa(guint8, MAX_PACKET_SIZE);
-	while (1) {
-		if (qd->tcp_rxlen < QQ_TCP_HEADER_LENGTH) {
+	while (PURPLE_CONNECTION_IS_VALID(gc)) {
+		if (qd->openconns == NULL) {
 			break;
 		}
-		
-		bytes = 0;
-		bytes += qq_get16(&pkt_len, qd->tcp_rxqueue + bytes);
-		if (qd->tcp_rxlen < pkt_len) {
+		if (conn->tcp_rxqueue == NULL) {
+			conn->tcp_rxlen = 0;
+			break;
+		}
+		if (conn->tcp_rxlen < QQ_TCP_HEADER_LENGTH) {
 			break;
 		}
 
-		/* 
-		purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
-				   "Packet len is %d bytes, rxlen is %d\n", pkt_len, qd->tcp_rxlen);
-		*/
-		if ( pkt_len < QQ_TCP_HEADER_LENGTH
-		    || *(qd->tcp_rxqueue + bytes) != QQ_PACKET_TAG
-			|| *(qd->tcp_rxqueue + pkt_len - 1) != QQ_PACKET_TAIL) {
-			/* HEY! This isn't even a QQ. What are you trying to pull? */
+		bytes = 0;
+		bytes += qq_get16(&pkt_len, conn->tcp_rxqueue + bytes);
+		if (conn->tcp_rxlen < pkt_len) {
+			break;
+		}
 
-			purple_debug(PURPLE_DEBUG_ERROR, "TCP_PENDING",
-				 "Packet error, failed to check header and tail tag\n");
+		/* purple_debug_info("TCP_PENDING", "Packet len=%d, rxlen=%d\n", pkt_len, conn->tcp_rxlen); */
+		if ( pkt_len < QQ_TCP_HEADER_LENGTH
+		    || *(conn->tcp_rxqueue + bytes) != QQ_PACKET_TAG
+			|| *(conn->tcp_rxqueue + pkt_len - 1) != QQ_PACKET_TAIL) {
+			/* HEY! This isn't even a QQ. What are you trying to pull? */
+			purple_debug_warning("TCP_PENDING", "Packet error, no header or tail tag\n");
 
-			jump = memchr(qd->tcp_rxqueue + 1, QQ_PACKET_TAIL, qd->tcp_rxlen - 1);
+			jump = memchr(conn->tcp_rxqueue + 1, QQ_PACKET_TAIL, conn->tcp_rxlen - 1);
 			if ( !jump ) {
-				purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
-				 	"Failed to find next QQ_PACKET_TAIL, clear receive buffer\n");
-				g_free(qd->tcp_rxqueue);
-				qd->tcp_rxqueue = NULL;
-				qd->tcp_rxlen = 0;
+				purple_debug_warning("TCP_PENDING", "Failed to find next tail, clear receive buffer\n");
+				g_free(conn->tcp_rxqueue);
+				conn->tcp_rxqueue = NULL;
+				conn->tcp_rxlen = 0;
 				return;
 			}
 
 			/* jump and over QQ_PACKET_TAIL */
-			jump_len = (jump - qd->tcp_rxqueue) + 1;
-			purple_debug(PURPLE_DEBUG_INFO, "TCP_PENDING",
-				"Find next QQ_PACKET_TAIL at %d, jump %d bytes\n", jump_len, jump_len + 1);
-			g_memmove(qd->tcp_rxqueue, jump, qd->tcp_rxlen - jump_len);
-			qd->tcp_rxlen -= jump_len;
+			jump_len = (jump - conn->tcp_rxqueue) + 1;
+			purple_debug_warning("TCP_PENDING", "Find next tail at %d, jump %d\n", jump_len, jump_len + 1);
+			g_memmove(conn->tcp_rxqueue, jump, conn->tcp_rxlen - jump_len);
+			conn->tcp_rxlen -= jump_len;
 			continue;
 		}
 
 		memset(pkt, 0, MAX_PACKET_SIZE);
-		g_memmove(pkt, qd->tcp_rxqueue + bytes, pkt_len - bytes);
-		
+		g_memmove(pkt, conn->tcp_rxqueue + bytes, pkt_len - bytes);
+
 		/* jump to next packet */
-		qd->tcp_rxlen -= pkt_len;
-		if (qd->tcp_rxlen) {
-			/*
-			purple_debug(PURPLE_DEBUG_ERROR, "TCP_PENDING", "shrink tcp_rxqueue to %d\n", qd->tcp_rxlen);		
-			*/
-			jump = g_memdup(qd->tcp_rxqueue + pkt_len, qd->tcp_rxlen);
-			g_free(qd->tcp_rxqueue);
-			qd->tcp_rxqueue = jump;
+		conn->tcp_rxlen -= pkt_len;
+		if (conn->tcp_rxlen) {
+			/* purple_debug_info("TCP_PENDING", "shrink tcp_rxqueue to %d\n", conn->tcp_rxlen);	*/
+			jump = g_memdup(conn->tcp_rxqueue + pkt_len, conn->tcp_rxlen);
+			g_free(conn->tcp_rxqueue);
+			conn->tcp_rxqueue = jump;
 		} else {
-			/* purple_debug(PURPLE_DEBUG_ERROR, "TCP_PENDING", "free tcp_rxqueue\n"); */
-			g_free(qd->tcp_rxqueue);
-			qd->tcp_rxqueue = NULL;
+			/* purple_debug_info("TCP_PENDING", "free tcp_rxqueue\n"); */
+			g_free(conn->tcp_rxqueue);
+			conn->tcp_rxqueue = NULL;
 		}
 
 		if (pkt == NULL) {
 			continue;
 		}
-		/* do not call packet_process before jump 
-		 * packet_process may call disconnect and destory tcp_rxqueue */
-		packet_process(gc, pkt, pkt_len - bytes);
+		/* packet_process may call disconnect and destory data like conn
+		 * do not call packet_process before jump,
+		 * break if packet_process return FALSE */
+		if (packet_process(gc, pkt, pkt_len - bytes) == FALSE) {
+			purple_debug_info("TCP_PENDING", "Connection has been destory\n");
+			break;
+		}
 	}
 }
 
 static void udp_pending(gpointer data, gint source, PurpleInputCondition cond)
 {
-	PurpleConnection *gc;
+	PurpleConnection *gc = (PurpleConnection *) data;
 	qq_data *qd;
 	guint8 *buf;
 	gint buf_len;
 
 	gc = (PurpleConnection *) data;
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
 
 	if(cond != PURPLE_INPUT_READ) {
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
@@ -393,13 +482,10 @@
 		return;
 	}
 
-	qd = (qq_data *) gc->proto_data;
-	g_return_if_fail(qd->fd >= 0);
-	
 	buf = g_newa(guint8, MAX_PACKET_SIZE);
 
 	/* here we have UDP proxy suppport */
-	buf_len = read(qd->fd, buf, MAX_PACKET_SIZE);
+	buf_len = read(source, buf, MAX_PACKET_SIZE);
 	if (buf_len <= 0) {
 		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 				_("Unable to read from socket"));
@@ -419,73 +505,95 @@
 			return;
 		}
 	}
-	
+
+	/* packet_process may call disconnect and destory data like conn
+	 * do not call packet_process before jump,
+	 * break if packet_process return FALSE */
 	packet_process(gc, buf, buf_len);
 }
 
-static gint udp_send_out(qq_data *qd, guint8 *data, gint data_len)
+static gint udp_send_out(PurpleConnection *gc, guint8 *data, gint data_len)
 {
+	qq_data *qd;
 	gint ret;
 
-	g_return_val_if_fail(qd != NULL && qd->fd >= 0 && data != NULL && data_len > 0, -1);
+	g_return_val_if_fail(data != NULL && data_len > 0, -1);
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
 
-	/*
-	purple_debug(PURPLE_DEBUG_INFO, "UDP_SEND_OUT", "Send %d bytes to socket %d\n", data_len, qd->fd);
-	*/
-	
+#if 0
+	purple_debug_info("UDP_SEND_OUT", "Send %d bytes to socket %d\n", data_len, qd->fd);
+#endif
+
 	errno = 0;
 	ret = send(qd->fd, data, data_len, 0);
 	if (ret < 0 && errno == EAGAIN) {
 		return ret;
 	}
-	
+
 	if (ret < 0) {
 		/* TODO: what to do here - do we really have to disconnect? */
-		purple_debug(PURPLE_DEBUG_ERROR, "UDP_SEND_OUT", "Send failed: %d, %s\n", errno, g_strerror(errno));
-		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
+		purple_debug_error("UDP_SEND_OUT", "Send failed: %d, %s\n", errno, g_strerror(errno));
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
 	}
 	return ret;
 }
 
 static void tcp_can_write(gpointer data, gint source, PurpleInputCondition cond)
 {
-	qq_data *qd = data;
+	PurpleConnection *gc = (PurpleConnection *) data;
+	qq_data *qd;
+	qq_connection *conn;
 	int ret, writelen;
 
-	writelen = purple_circ_buffer_get_max_read(qd->tcp_txbuf);
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
+	conn = connection_find(qd, source);
+	g_return_if_fail(conn != NULL);
+
+	writelen = purple_circ_buffer_get_max_read(conn->tcp_txbuf);
 	if (writelen == 0) {
-		purple_input_remove(qd->tx_handler);
-		qd->tx_handler = 0;
+		purple_input_remove(conn->can_write_handler);
+		conn->can_write_handler = 0;
 		return;
 	}
 
-	ret = write(qd->fd, qd->tcp_txbuf->outptr, writelen);
-	purple_debug(PURPLE_DEBUG_ERROR, "TCP_CAN_WRITE",
-		"total %d bytes is sent %d\n", writelen, ret);
+	ret = write(source, conn->tcp_txbuf->outptr, writelen);
+	purple_debug_info("TCP_CAN_WRITE", "total %d bytes is sent %d\n", writelen, ret);
 
 	if (ret < 0 && errno == EAGAIN)
 		return;
 	else if (ret < 0) {
 		/* TODO: what to do here - do we really have to disconnect? */
-		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
 		                               _("Write Error"));
 		return;
 	}
 
-	purple_circ_buffer_mark_read(qd->tcp_txbuf, ret);
+	purple_circ_buffer_mark_read(conn->tcp_txbuf, ret);
 }
 
-static gint tcp_send_out(qq_data *qd, guint8 *data, gint data_len)
+static gint tcp_send_out(PurpleConnection *gc, guint8 *data, gint data_len)
 {
+	qq_data *qd;
+	qq_connection *conn;
 	gint ret;
 
-	g_return_val_if_fail(qd != NULL && qd->fd >= 0 && data != NULL && data_len > 0, -1);
+	g_return_val_if_fail(data != NULL && data_len > 0, -1);
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
 
-	/*
-	purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT", "Send %d bytes to socket %d\n", data_len, qd->fd);
-	 */
+	conn = connection_find(qd, qd->fd);
+	g_return_val_if_fail(conn, -1);
 
-	if (qd->tx_handler == 0) {
+#if 0
+	purple_debug_info("TCP_SEND_OUT", "Send %d bytes to socket %d\n", data_len, qd->fd);
+#endif
+
+	if (conn->can_write_handler == 0) {
 		ret = write(qd->fd, data, data_len);
 	} else {
 		ret = -1;
@@ -493,28 +601,28 @@
 	}
 
 	/*
-	purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT",
+	purple_debug_info("TCP_SEND_OUT",
 		"Socket %d, total %d bytes is sent %d\n", qd->fd, data_len, ret);
 	*/
 	if (ret < 0 && errno == EAGAIN) {
 		/* socket is busy, send later */
-		purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT", "Socket is busy and send later\n");
+		purple_debug_info("TCP_SEND_OUT", "Socket is busy and send later\n");
 		ret = 0;
 	} else if (ret <= 0) {
 		/* TODO: what to do here - do we really have to disconnect? */
-		purple_debug(PURPLE_DEBUG_ERROR, "TCP_SEND_OUT",
+		purple_debug_error("TCP_SEND_OUT",
 			"Send to socket %d failed: %d, %s\n", qd->fd, errno, g_strerror(errno));
-		purple_connection_error_reason(qd->gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, g_strerror(errno));
 		return ret;
 	}
 
 	if (ret < data_len) {
-		purple_debug(PURPLE_DEBUG_INFO, "TCP_SEND_OUT",
+		purple_debug_info("TCP_SEND_OUT",
 			"Add %d bytes to buffer\n", data_len - ret);
-		if (qd->tx_handler == 0) {
-			qd->tx_handler = purple_input_add(qd->fd, PURPLE_INPUT_WRITE, tcp_can_write, qd);
+		if (conn->can_write_handler == 0) {
+			conn->can_write_handler = purple_input_add(qd->fd, PURPLE_INPUT_WRITE, tcp_can_write, gc);
 		}
-		purple_circ_buffer_append(qd->tcp_txbuf, data + ret, data_len - ret);
+		purple_circ_buffer_append(conn->tcp_txbuf, data + ret, data_len - ret);
 	}
 	return ret;
 }
@@ -528,17 +636,17 @@
 	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, TRUE);
 	qd = (qq_data *) gc->proto_data;
 
-	is_lost_conn = qq_trans_scan(qd);
+	is_lost_conn = qq_trans_scan(gc);
 	if (is_lost_conn) {
 		purple_connection_error_reason(gc,
 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Connection lost"));
 		return TRUE;
 	}
 
-	if ( !qd->logged_in ) {
+	if ( !qd->is_login ) {
 		return TRUE;
 	}
-	
+
 	qd->itv_count.keep_alive--;
 	if (qd->itv_count.keep_alive <= 0) {
 		qd->itv_count.keep_alive = qd->itv_config.keep_alive;
@@ -553,55 +661,30 @@
 	qd->itv_count.update--;
 	if (qd->itv_count.update <= 0) {
 		qd->itv_count.update = qd->itv_config.update;
-		qq_send_packet_get_buddies_online(gc, 0);
-
-		qq_send_cmd_group_all_get_online_members(gc);
+		qq_update_online(gc, 0);
 		return TRUE;
 	}
 
 	return TRUE;		/* if return FALSE, timeout callback stops */
 }
 
-/* the callback function after socket is built
- * we setup the qq protocol related configuration here */
-static void qq_connect_cb(gpointer data, gint source, const gchar *error_message)
+static void do_request_token(PurpleConnection *gc)
 {
 	qq_data *qd;
-	PurpleConnection *gc;
 	gchar *conn_msg;
 	const gchar *passwd;
-	PurpleAccount *account ;
-
-	gc = (PurpleConnection *) data;
-
-	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ_CONN", "Invalid connection\n");
-		close(source);
-		return;
-	}
-
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-
-	qd = (qq_data *) gc->proto_data;
-	account = purple_connection_get_account(gc);
-
-	/* Connect is now complete; clear the PurpleProxyConnectData */
-	qd->connect_data = NULL;
-
-	if (source < 0) {	/* socket returns -1 */
-		purple_debug(PURPLE_DEBUG_INFO, "QQ_CONN", "Invalid connection, source is < 0\n");
-		qq_disconnect(gc);
-		reconnect_later(gc);
-		return;
-	}
 
 	/* _qq_show_socket("Got login socket", source); */
 
+	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
+	qd = (qq_data *) gc->proto_data;
+
 	/* QQ use random seq, to minimize duplicated packets */
-	srandom(time(NULL));
-	qd->send_seq = random() & 0x0000ffff;
-	qd->fd = source;
-	qd->logged_in = FALSE;
+	srand(time(NULL));
+	qd->send_seq = rand() & 0xffff;
+
+	qd->is_login = FALSE;
+	qd->is_finish_update = FALSE;
 	qd->channel = 1;
 	qd->uid = strtol(purple_account_get_username(purple_connection_get_account(gc)), NULL, 10);
 
@@ -614,257 +697,94 @@
 	qq_get_md5(qd->password_twice_md5, sizeof(qd->password_twice_md5),
 		qd->password_twice_md5, sizeof(qd->password_twice_md5));
 
-	g_return_if_fail(qd->network_timeout == 0);
-	qd->itv_config.resend = purple_account_get_int(account, "resend_interval", 10);
-	if (qd->itv_config.resend <= 0) qd->itv_config.resend = 10;
-
-	qd->itv_config.keep_alive = purple_account_get_int(account, "keep_alive_interval", 60);
-	if (qd->itv_config.keep_alive < 30) qd->itv_config.keep_alive = 30;
-	qd->itv_config.keep_alive /= qd->itv_config.resend;
-	qd->itv_count.keep_alive = qd->itv_config.keep_alive;
-
-	qd->itv_config.update = purple_account_get_int(account, "update_interval", 300);
-	if (qd->itv_config.update > 0) {
-		if (qd->itv_config.update < qd->itv_config.keep_alive) {
-			qd->itv_config.update = qd->itv_config.keep_alive;
-		}
-		qd->itv_config.update /= qd->itv_config.resend;
-		qd->itv_count.update = qd->itv_config.update;
-	} else {
-		qd->itv_config.update = 0;
-	}
-
-	qd->network_timeout = purple_timeout_add(qd->itv_config.resend *1000, network_timeout, gc);
-	
-	if (qd->use_tcp)
-		gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, tcp_pending, gc);
-	else
-		gc->inpa = purple_input_add(qd->fd, PURPLE_INPUT_READ, udp_pending, gc);
+	g_return_if_fail(qd->network_watcher == 0);
+	qd->network_watcher = purple_timeout_add_seconds(qd->itv_config.resend, network_timeout, gc);
 
 	/* Update the login progress status display */
-	conn_msg = g_strdup_printf("Login as %d", qd->uid);
-	purple_connection_update_progress(gc, conn_msg, QQ_CONNECT_STEPS - 1, QQ_CONNECT_STEPS);
+	conn_msg = g_strdup_printf(_("Request token"));
+	purple_connection_update_progress(gc, conn_msg, 2, QQ_CONNECT_STEPS);
 	g_free(conn_msg);
 
 	qq_send_packet_token(gc);
 }
 
-static void udp_can_write(gpointer data, gint source, PurpleInputCondition cond)
+/* the callback function after socket is built
+ * we setup the qq protocol related configuration here */
+static void connect_cb(gpointer data, gint source, const gchar *error_message)
 {
 	PurpleConnection *gc;
 	qq_data *qd;
-	socklen_t len;
-	int error=0, ret;
-
-	gc = (PurpleConnection *) data;
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-
-	qd = (qq_data *) gc->proto_data;
-
-
-	purple_debug_info("proxy", "Connected.\n");
-
-	/*
-	 * getsockopt after a non-blocking connect returns -1 if something is
-	 * really messed up (bad descriptor, usually). Otherwise, it returns 0 and
-	 * error holds what connect would have returned if it blocked until now.
-	 * Thus, error == 0 is success, error == EINPROGRESS means "try again",
-	 * and anything else is a real error.
-	 *
-	 * (error == EINPROGRESS can happen after a select because the kernel can
-	 * be overly optimistic sometimes. select is just a hint that you might be
-	 * able to do something.)
-	 */
-	len = sizeof(error);
-	ret = getsockopt(source, SOL_SOCKET, SO_ERROR, &error, &len);
-	if (ret == 0 && error == EINPROGRESS)
-		return; /* we'll be called again later */
-		
-	purple_input_remove(qd->tx_handler);
-	qd->tx_handler = 0;
-	if (ret < 0 || error != 0) {
-		if(ret != 0) 
-			error = errno;
-
-		close(source);
-
-		purple_debug_error("proxy", "getsockopt SO_ERROR check: %s\n", g_strerror(error));
-
-		qq_connect_cb(gc, -1, _("Unable to connect"));
-		return;
-	}
-
-	qq_connect_cb(gc, source, NULL);
-}
-
-static void udp_host_resolved(GSList *hosts, gpointer data, const char *error_message) {
-	PurpleConnection *gc;
-	qq_data *qd;
-	struct sockaddr server_addr;
-	int addr_size;
-	gint fd = -1;
-	int flags;
+	PurpleAccount *account ;
+	qq_connection *conn;
 
 	gc = (PurpleConnection *) data;
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
 
 	qd = (qq_data *) gc->proto_data;
-
-	/* udp_query_data must be set as NULL.
-	 * Otherwise purple_dnsquery_destroy in qq_disconnect cause glib double free error */
-	qd->udp_query_data = NULL;
+	account = purple_connection_get_account(gc);
 
-	if (!hosts || !hosts->data) {
-		purple_connection_error_reason(gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Couldn't resolve host"));
+	/* conn_data will be destoryed */
+	qd->conn_data = NULL;
+
+	if (!PURPLE_CONNECTION_IS_VALID(gc)) {
+		purple_debug_info("QQ_CONN", "Invalid connection\n");
+		close(source);
 		return;
 	}
 
-	addr_size = GPOINTER_TO_INT(hosts->data);
-	hosts = g_slist_remove(hosts, hosts->data);
-	memcpy(&server_addr, hosts->data, addr_size);
-	g_free(hosts->data);
-	
-	hosts = g_slist_remove(hosts, hosts->data);
-	while(hosts) {
-		hosts = g_slist_remove(hosts, hosts->data);
-		g_free(hosts->data);
-		hosts = g_slist_remove(hosts, hosts->data);
-	}
-
-	fd = socket(PF_INET, SOCK_DGRAM, 0);
-	if (fd < 0) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ", 
-				"Unable to create socket: %s\n", g_strerror(errno));
+	if (source < 0) {	/* socket returns -1 */
+		purple_debug_info("QQ_CONN",
+				"Could not establish a connection with the server:\n%s\n",
+				error_message);
+		if (qd->connect_watcher > 0)	purple_timeout_remove(qd->connect_watcher);
+		qd->connect_watcher = purple_timeout_add_seconds(QQ_CONNECT_INTERVAL, qq_connect_later, gc);
 		return;
 	}
 
-	/* we use non-blocking mode to speed up connection */
-	flags = fcntl(fd, F_GETFL);
-	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+	/* _qq_show_socket("Got login socket", source); */
+	qd->fd = source;
+	conn = connection_create(qd, source);
+	if (qd->use_tcp) {
+		conn->input_handler = purple_input_add(source, PURPLE_INPUT_READ, tcp_pending, gc);
+	} else {
+		conn->input_handler = purple_input_add(source, PURPLE_INPUT_READ, udp_pending, gc);
+	}
 
-	/* From Unix-socket-FAQ: http://www.faqs.org/faqs/unix-faq/socket/
-	 *
-	 * If a UDP socket is unconnected, which is the normal state after a
-	 * bind() call, then send() or write() are not allowed, since no
-	 * destination is available; only sendto() can be used to send data.
-	 *   
-	 * Calling connect() on the socket simply records the specified address
-	 * and port number as being the desired communications partner. That
-	 * means that send() or write() are now allowed; they use the destination
-	 * address and port given on the connect call as the destination of packets.
-	 */
-	if (connect(fd, &server_addr, addr_size) >= 0) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Connected.\n");
-		flags = fcntl(fd, F_GETFL);
-		fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
-		qq_connect_cb(gc, fd, NULL);
-		return;
-	}
-	
-	/* [EINPROGRESS]
-	 *    The socket is marked as non-blocking and the connection cannot be 
-	 *    completed immediately. It is possible to select for completion by 
-	 *    selecting the socket for writing.
-	 * [EINTR]
-	 *    A signal interrupted the call. 
-	 *    The connection is established asynchronously.
-	 */
-	if ((errno == EINPROGRESS) || (errno == EINTR)) {
-			purple_debug(PURPLE_DEBUG_WARNING, "QQ", "Connect in asynchronous mode.\n");
-			qd->tx_handler = purple_input_add(fd, PURPLE_INPUT_WRITE, udp_can_write, gc);
-			return;
-		}
-
-	purple_debug(PURPLE_DEBUG_ERROR, "QQ", "Connection failed: %s\n", g_strerror(errno));
-	close(fd);
+	do_request_token( gc );
 }
 
-/* establish a generic QQ connection 
- * TCP/UDP, and direct/redirected */
-void qq_connect(PurpleAccount *account)
+gboolean connect_to_server(PurpleConnection *gc, gchar *server, gint port)
 {
-	PurpleConnection *gc;
+	PurpleAccount *account ;
 	qq_data *qd;
 	gchar *conn_msg;
 
-	gc = purple_account_get_connection(account);
-	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
-
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, FALSE);
+	account = purple_connection_get_account(gc);
 	qd = (qq_data *) gc->proto_data;
 
-
-	/* test set_new_server
-	while (set_new_server(qd)) {
-   		purple_debug(PURPLE_DEBUG_INFO, "QQ_TEST",
-   			"New server %s:%d  Real server %s:%d\n",
-   			qd->server_name, qd->user_port, qd->real_hostname, qd->real_port);
-	}
-	purple_debug(PURPLE_DEBUG_INFO, "QQ_TEST", "qd->servers %lu\n",
- 			qd->servers);
- 	exit(1);
-	*/
-	if (qd->server_name == NULL) {
-		/* must be first call this function */
-		if ( set_new_server(qd) != TRUE) {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-					_("Failed to connect server"));
-			return;
-		}
+	if (server == NULL || strlen(server) == 0 || port == 0) {
+		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
+				_("Invalid server or port"));
+		return FALSE;
 	}
 
-	if (qd->real_hostname == NULL || qd->real_port == 0) {
-		purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("hostname is NULL or port is 0"));
-		return;
-	}
-
-	conn_msg = g_strdup_printf( _("Connecting server %s, retries %d"),
-		qd->real_hostname, qd->reconnect_times);
+	conn_msg = g_strdup_printf( _("Connecting server %s, retries %d"), server, port);
 	purple_connection_update_progress(gc, conn_msg, 1, QQ_CONNECT_STEPS);
 	g_free(conn_msg);
 
-	if (qd->is_redirect) {
-   		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Redirect to %s:%d\n",
-   			qd->real_hostname, qd->real_port);
-   	}
-	qd->is_redirect = FALSE;
-
-	qd->fd = -1;
-	qd->tx_handler = 0;
-	
-	/* QQ connection via UDP/TCP. 
-	* Now use Purple proxy function to provide TCP proxy support,
-	* and qq_udp_proxy.c to add UDP proxy support (thanks henry) */
-	if(qd->use_tcp) {
-   		purple_debug(PURPLE_DEBUG_INFO, "QQ", "TCP Connect to %s:%d\n",
-   			qd->real_hostname, qd->real_port);
-
-		/* TODO: is there a good default grow size? */
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Create tcp_txbuf\n");
-		qd->tcp_txbuf = purple_circ_buffer_new(0);
+	purple_debug_info("QQ", "Connect to %s:%d\n", server, port);
 
-		qd->connect_data = purple_proxy_connect(NULL, account,
-				qd->real_hostname, qd->real_port, qq_connect_cb, gc);
-		if (qd->connect_data == NULL) {
-			purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-				_("Unable to connect."));
-		}
-		return;
+	if (qd->conn_data != NULL) {
+		purple_proxy_connect_cancel(qd->conn_data);
+		qd->conn_data = NULL;
 	}
-	
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "UDP Connect to %s:%d\n",
-		qd->real_hostname, qd->real_port);
-
-	g_return_if_fail(qd->udp_query_data == NULL);
-	qd->udp_query_data = purple_dnsquery_a(qd->real_hostname, qd->real_port,
-		udp_host_resolved, gc);
-	if (qd->udp_query_data == NULL) {
-		purple_connection_error_reason(qd->gc,
-			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
-			_("Could not resolve hostname"));
+	qd->conn_data = purple_proxy_connect(gc, account, server, port, connect_cb, gc);
+	if ( qd->conn_data == NULL ) {
+		purple_debug_error("QQ", _("Couldn't create socket"));
+		return FALSE;
 	}
+	return TRUE;
 }
 
 /* clean up qq_data structure and all its components
@@ -876,65 +796,32 @@
 	g_return_if_fail(gc != NULL && gc->proto_data != NULL);
 	qd = (qq_data *) gc->proto_data;
 
-	purple_debug(PURPLE_DEBUG_INFO, "QQ", "Disconnecting ...\n");
+	purple_debug_info("QQ", "Disconnecting ...\n");
 
-	if (qd->network_timeout > 0) {
-		purple_timeout_remove(qd->network_timeout);
-		qd->network_timeout = 0;
+	if (qd->network_watcher > 0) {
+		purple_debug_info("QQ", "Remove network watcher\n");
+		purple_timeout_remove(qd->network_watcher);
+		qd->network_watcher = 0;
 	}
 
 	/* finish  all I/O */
-	if (qd->fd >= 0 && qd->logged_in) {
+	if (qd->fd >= 0 && qd->is_login) {
 		qq_send_packet_logout(gc);
 	}
 
-	if (gc->inpa > 0) {
-		purple_input_remove(gc->inpa);
-		gc->inpa = 0;
-	}
-
-	if (qd->fd >= 0) {
-		close(qd->fd);
-		qd->fd = -1;
-	}
-
-	if (qd->reconnect_timeout > 0) {
-		purple_timeout_remove(qd->reconnect_timeout);
-		qd->reconnect_timeout = 0;
-	}
-
-	if (qd->connect_data != NULL) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "Cancel connect_data\n");
-		purple_proxy_connect_cancel(qd->connect_data);
+	/* not connected */
+	if (qd->conn_data != NULL) {
+		purple_debug_info("QQ", "Connect cancel\n");
+		purple_proxy_connect_cancel(qd->conn_data);
+		qd->conn_data = NULL;
 	}
-	
-	if(qd->tcp_txbuf != NULL) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "destroy tcp_txbuf\n");
-		purple_circ_buffer_destroy(qd->tcp_txbuf);
-		qd->tcp_txbuf = NULL;
-	}
-	
-	if (qd->tx_handler) {
-		purple_input_remove(qd->tx_handler);
-		qd->tx_handler = 0;
-	}
-	if (qd->tcp_rxqueue != NULL) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "destroy tcp_rxqueue\n");
-		g_free(qd->tcp_rxqueue);
-		qd->tcp_rxqueue = NULL;
-		qd->tcp_rxlen = 0;
-	}
-	
-	if (qd->udp_query_data != NULL) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "destroy udp_query_data\n");
-		purple_dnsquery_destroy(qd->udp_query_data);
-		qd->udp_query_data = NULL;
-	}
+	connection_free_all(qd);
+	qd->fd = -1;
 
-	qq_trans_remove_all(qd);
-	
+	qq_trans_remove_all(gc);
+
 	if (qd->token) {
-		purple_debug(PURPLE_DEBUG_INFO, "QQ", "free token\n");
+		purple_debug_info("QQ", "free token\n");
 		g_free(qd->token);
 		qd->token = NULL;
 		qd->token_len = 0;
@@ -952,13 +839,13 @@
 	qq_buddies_list_free(gc->account, qd);
 }
 
-static gint encap(qq_data *qd, guint8 *buf, gint maxlen, guint16 cmd, guint16 seq, 
+static gint packet_encap(qq_data *qd, guint8 *buf, gint maxlen, guint16 cmd, guint16 seq,
 	guint8 *data, gint data_len)
 {
 	gint bytes = 0;
 	g_return_val_if_fail(qd != NULL && buf != NULL && maxlen > 0, -1);
 	g_return_val_if_fail(data != NULL && data_len > 0, -1);
-	
+
 	/* QQ TCP packet has two bytes in the begining defines packet length
 	 * so leave room here to store packet size */
 	if (qd->use_tcp) {
@@ -968,7 +855,7 @@
 	bytes += qq_put8(buf + bytes, QQ_PACKET_TAG);
 	bytes += qq_put16(buf + bytes, QQ_CLIENT);
 	bytes += qq_put16(buf + bytes, cmd);
-	
+
 	bytes += qq_put16(buf + bytes, seq);
 
 	bytes += qq_put32(buf + bytes, qd->uid);
@@ -984,109 +871,144 @@
 }
 
 /* data has been encrypted before */
-gint qq_send_data(qq_data *qd, guint16 cmd, guint16 seq, gboolean need_ack,
-	guint8 *data, gint data_len)
+static gint packet_send_out(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *data, gint data_len)
 {
+	qq_data *qd;
 	guint8 *buf;
 	gint buf_len;
 	gint bytes_sent;
 
-	g_return_val_if_fail(qd != NULL, -1);
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *)gc->proto_data;
 	g_return_val_if_fail(data != NULL && data_len > 0, -1);
 
 	buf = g_newa(guint8, MAX_PACKET_SIZE);
 	memset(buf, 0, MAX_PACKET_SIZE);
-	buf_len = encap(qd, buf, MAX_PACKET_SIZE, cmd, seq, data, data_len);
+	buf_len = packet_encap(qd, buf, MAX_PACKET_SIZE, cmd, seq, data, data_len);
 	if (buf_len <= 0) {
 		return -1;
 	}
 
 	if (qd->use_tcp) {
-		bytes_sent = tcp_send_out(qd, buf, buf_len);
+		bytes_sent = tcp_send_out(gc, buf, buf_len);
 	} else {
-		bytes_sent = udp_send_out(qd, buf, buf_len);
+		bytes_sent = udp_send_out(gc, buf, buf_len);
 	}
 
-	if (need_ack)  {
-		qq_trans_add_client_cmd(qd, cmd, seq, data, data_len);
-	}
-	
-#if 1
-		/* qq_show_packet("QQ_SEND_DATA", buf, buf_len); */
-		purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				"<== [%05d], 0x%04X %s, total %d bytes is sent %d\n", 
-				seq, cmd, qq_get_cmd_desc(cmd), buf_len, bytes_sent);
-#endif
 	return bytes_sent;
 }
 
-/* Encrypt data with session_key, then call qq_send_data */
-gint qq_send_cmd_detail(qq_data *qd, guint16 cmd, guint16 seq, gboolean need_ack,
-	guint8 *data, gint data_len)
+gint qq_send_cmd_encrypted(PurpleConnection *gc, guint16 cmd, guint16 seq,
+	guint8 *data, gint data_len, gboolean need_ack)
 {
+	gint send_len;
+
+#if 1
+		purple_debug_info("QQ", "<== [%05d], %s(0x%04X), datalen %d\n",
+				seq, qq_get_cmd_desc(cmd), cmd, data_len);
+#endif
+
+	send_len = packet_send_out(gc, cmd, seq, data, data_len);
+	if (need_ack)  {
+		qq_trans_add_client_cmd(gc, cmd, seq, data, data_len, 0, 0);
+	}
+	return send_len;
+}
+
+/* Encrypt data with session_key, and send packet out */
+static gint send_cmd_detail(PurpleConnection *gc, guint16 cmd, guint16 seq,
+	guint8 *data, gint data_len, gboolean need_ack, gint update_class, guint32 ship32)
+{
+	qq_data *qd;
 	guint8 *encrypted_data;
 	gint encrypted_len;
+	gint bytes_sent;
 
-	g_return_val_if_fail(qd != NULL, -1);
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *)gc->proto_data;
 	g_return_val_if_fail(data != NULL && data_len > 0, -1);
 
 	/* at most 16 bytes more */
 	encrypted_data = g_newa(guint8, data_len + 16);
-#if 0
-	purple_debug(PURPLE_DEBUG_INFO, "QQ_ENCRYPT",
-			"Before %d: [%05d] 0x%04X %s\n",
-			data_len, seq, cmd, qq_get_cmd_desc(cmd));
-#endif
 	encrypted_len = qq_encrypt(encrypted_data, data, data_len, qd->session_key);
 	if (encrypted_len < 16) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ_ENCRYPT",
-				"Error len %d: [%05d] 0x%04X %s\n",
+		purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] 0x%04X %s\n",
 				encrypted_len, seq, cmd, qq_get_cmd_desc(cmd));
 		return -1;
 	}
 
-#if 0
-	purple_debug(PURPLE_DEBUG_INFO, "QQ_ENCRYPT",
-			"After %d: [%05d] 0x%04X %s\n",
-			encrypted_len, seq, cmd, qq_get_cmd_desc(cmd));
-#endif
-	return qq_send_data(qd, cmd, seq, need_ack, encrypted_data, encrypted_len);
+	bytes_sent = packet_send_out(gc, cmd, seq, encrypted_data, encrypted_len);
+
+	if (need_ack)  {
+		qq_trans_add_client_cmd(gc, cmd, seq, encrypted_data, encrypted_len, update_class, ship32);
+	}
+	return bytes_sent;
 }
 
-/* set seq and need_ack, then call qq_send_cmd_detail */
-gint qq_send_cmd(qq_data *qd, guint16 cmd, guint8 *data, gint data_len)
+gint qq_send_cmd_mess(PurpleConnection *gc, guint16 cmd, guint8 *data, gint data_len,
+		gint update_class, guint32 ship32)
 {
-	g_return_val_if_fail(qd != NULL, -1);
+	qq_data *qd;
+	guint16 seq;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
 	g_return_val_if_fail(data != NULL && data_len > 0, -1);
 
-	qd->send_seq++;
-	return qq_send_cmd_detail(qd, cmd, qd->send_seq, TRUE, data, data_len);
+	seq = ++qd->send_seq;
+#if 1
+		purple_debug_info("QQ", "<== [%05d], %s(0x%04X), datalen %d\n",
+				seq, qq_get_cmd_desc(cmd), cmd, data_len);
+#endif
+	return send_cmd_detail(gc, cmd, seq, data, data_len, TRUE, update_class, ship32);
 }
 
-gint qq_send_room_cmd_noid(PurpleConnection *gc, guint8 room_cmd, 
-		guint8 *data, gint data_len)
+/* set seq and need_ack, then call send_cmd_detail */
+gint qq_send_cmd(PurpleConnection *gc, guint16 cmd, guint8 *data, gint data_len)
 {
-	return qq_send_room_cmd(gc, room_cmd, 0, data, data_len);
+	qq_data *qd;
+	guint16 seq;
+	gboolean need_ack;
+
+	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
+	qd = (qq_data *) gc->proto_data;
+	g_return_val_if_fail(data != NULL && data_len > 0, -1);
+
+	if (cmd != QQ_CMD_LOGOUT) {
+		seq = ++qd->send_seq;
+		need_ack = TRUE;
+	} else {
+		seq = 0xFFFF;
+		need_ack = FALSE;
+	}
+#if 1
+		purple_debug_info("QQ", "<== [%05d], %s(0x%04X), datalen %d\n",
+				seq, qq_get_cmd_desc(cmd), cmd, data_len);
+#endif
+	return send_cmd_detail(gc, cmd, seq, data, data_len, need_ack, 0, 0);
 }
 
-gint qq_send_room_cmd_only(PurpleConnection *gc, guint8 room_cmd, guint32 room_id)
+/* set seq and need_ack, then call send_cmd_detail */
+gint qq_send_server_reply(PurpleConnection *gc, guint16 cmd, guint16 seq, guint8 *data, gint data_len)
 {
-	g_return_val_if_fail(room_cmd > 0 && room_id > 0, -1);
-	return qq_send_room_cmd(gc, room_cmd, room_id, NULL, 0);
+#if 1
+		purple_debug_info("QQ", "<== [SRV-%05d], %s(0x%04X), datalen %d\n",
+				seq, qq_get_cmd_desc(cmd), cmd, data_len);
+#endif
+	return send_cmd_detail(gc, cmd, seq, data, data_len, FALSE, 0, 0);
 }
 
-gint qq_send_room_cmd(PurpleConnection *gc, guint8 room_cmd, guint32 room_id,
-		guint8 *data, gint data_len)
+static gint send_room_cmd(PurpleConnection *gc, guint8 room_cmd, guint32 room_id,
+		guint8 *data, gint data_len, gint update_class, guint32 ship32)
 {
 	qq_data *qd;
-
 	guint8 *buf;
 	gint buf_len;
 	guint8 *encrypted_data;
 	gint encrypted_len;
 	gint bytes_sent;
 	guint16 seq;
-	
+
 	g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, -1);
 	qd = (qq_data *) gc->proto_data;
 
@@ -1103,6 +1025,7 @@
 	if (data != NULL && data_len > 0) {
 		buf_len += qq_putdata(buf + buf_len, data, data_len);
 	}
+
 	qd->send_seq++;
 	seq = qd->send_seq;
 
@@ -1111,32 +1034,43 @@
 	encrypted_data = g_newa(guint8, buf_len + 16);
 	encrypted_len = qq_encrypt(encrypted_data, buf, buf_len, qd->session_key);
 	if (encrypted_len < 16) {
-		purple_debug(PURPLE_DEBUG_ERROR, "QQ_ENCRYPT",
-				"Error len %d: [%05d] QQ_CMD_ROOM.(0x%02X %s)\n",
-				encrypted_len, seq, room_cmd, qq_get_room_cmd_desc(room_cmd));
-		return -1;
-	}
-
-	/* Encap header to buf */
-	buf_len = encap(qd, buf, MAX_PACKET_SIZE, QQ_CMD_ROOM, seq, encrypted_data, encrypted_len);
-	if (buf_len <= 0) {
+		purple_debug_error("QQ_ENCRYPT", "Error len %d: [%05d] %s (0x%02X)\n",
+				encrypted_len, seq, qq_get_room_cmd_desc(room_cmd), room_cmd);
 		return -1;
 	}
 
-	if (qd->use_tcp) {
-		bytes_sent = tcp_send_out(qd, buf, buf_len);
-	} else {
-		bytes_sent = udp_send_out(qd, buf, buf_len);
-	}
-
-	qq_trans_add_room_cmd(qd, seq, room_cmd, room_id, buf, buf_len);
-	
+	bytes_sent = packet_send_out(gc, QQ_CMD_ROOM, seq, encrypted_data, encrypted_len);
 #if 1
 		/* qq_show_packet("QQ_SEND_DATA", buf, buf_len); */
-		purple_debug(PURPLE_DEBUG_INFO, "QQ",
-				"<== [%05d], QQ_CMD_ROOM.(0x%02X %s) to room %d, total %d bytes is sent %d\n", 
-				seq, room_cmd, qq_get_room_cmd_desc(room_cmd), room_id,
-				buf_len, bytes_sent);
+		purple_debug_info("QQ",
+				"<== [%05d], %s (0x%02X) to room %d, datalen %d\n",
+				seq, qq_get_room_cmd_desc(room_cmd), room_cmd, room_id, buf_len);
 #endif
+
+	qq_trans_add_room_cmd(gc, seq, room_cmd, room_id, buf, buf_len, update_class, ship32);
 	return bytes_sent;
 }
+
+gint qq_send_room_cmd_mess(PurpleConnection *gc, guint8 room_cmd, guint32 room_id,
+		guint8 *data, gint data_len, gint update_class, guint32 ship32)
+{
+	return send_room_cmd(gc, room_cmd, room_id, data, data_len, update_class, ship32);
+}
+
+gint qq_send_room_cmd(PurpleConnection *gc, guint8 room_cmd, guint32 room_id,
+		guint8 *data, gint data_len)
+{
+	return send_room_cmd(gc, room_cmd, room_id, data, data_len, 0, 0);
+}
+
+gint qq_send_room_cmd_noid(PurpleConnection *gc, guint8 room_cmd,
+		guint8 *data, gint data_len)
+{
+	return send_room_cmd(gc, room_cmd, 0, data, data_len, 0, 0);
+}
+
+gint qq_send_room_cmd_only(PurpleConnection *gc, guint8 room_cmd, guint32 room_id)
+{
+	g_return_val_if_fail(room_cmd > 0 && room_id > 0, -1);
+	return send_room_cmd(gc, room_cmd, room_id, NULL, 0, 0, 0);
+}