libpurple/protocols/gg/lib/libgadu.c

changeset 35557
e83a87761544
parent 33710
0d7ead568881
child 35573
d9a333109990
--- a/libpurple/protocols/gg/lib/libgadu.c	Thu Feb 13 04:27:27 2014 +0100
+++ b/libpurple/protocols/gg/lib/libgadu.c	Thu Feb 13 18:29:10 2014 +0100
@@ -1,11 +1,11 @@
-/* $Id: libgadu.c 1245 2012-01-10 22:48:31Z wojtekka $ */
+/* $Id$ */
 
 /*
  *  (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
- *                          Robert J. Woźny <speedy@ziew.org>
- *                          Arkadiusz Miśkiewicz <arekm@pld-linux.org>
- *                          Tomasz Chiliński <chilek@chilan.com>
- *                          Adam Wysocki <gophi@ekg.chmurka.net>
+ *			  Robert J. Woźny <speedy@ziew.org>
+ *			  Arkadiusz Miśkiewicz <arekm@pld-linux.org>
+ *			  Tomasz Chiliński <chilek@chilan.com>
+ *			  Adam Wysocki <gophi@ekg.chmurka.net>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU Lesser General Public License Version
@@ -28,12 +28,12 @@
  * \brief Główny moduł biblioteki
  */
 
-#include <sys/types.h>
+#include "strman.h"
+#include "network.h"
 #ifdef sun
 #  include <sys/filio.h>
 #endif
 
-#include "compat.h"
 #include "libgadu.h"
 #include "protocol.h"
 #include "resolver.h"
@@ -43,15 +43,15 @@
 #include "session.h"
 #include "message.h"
 #include "deflate.h"
+#include "tvbuilder.h"
+#include "protobuf.h"
+#include "packets.pb-c.h"
 
 #include <errno.h>
 #include <stdarg.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <signal.h>
 #include <time.h>
-#include <unistd.h>
 #ifdef GG_CONFIG_HAVE_GNUTLS
 #  include <gnutls/gnutls.h>
 #endif
@@ -130,11 +130,32 @@
 #ifdef __GNUC__
 __attribute__ ((unused))
 #endif
-= "$Id: libgadu.c 1245 2012-01-10 22:48:31Z wojtekka $";
+= "$Id$";
 #endif
 
 #endif /* DOXYGEN */
 
+static void gg_compat_message_sent(struct gg_session *sess, int seq, size_t recipients_count, uin_t *recipients);
+static void gg_compat_message_cleanup(struct gg_session *sess);
+
+#ifdef GG_CONFIG_IS_GPL_COMPLIANT
+/**
+ * Symbol zdefiniowany tylko dla libgadu zgodnego z licencją GPL.
+ *
+ * Zwracana wartość nie jest istotna, a ponadto może się zmienić w przyszłych
+ * wersjach biblioteki. Istotne jest tylko wywołanie tej funkcji w kodzie, który
+ * ma być zgodny z GPL, aby wymusić jej istnienie.
+ *
+ * \return Wartość 1.
+ *
+ * \ingroup version
+ */
+int gg_is_gpl_compliant(void)
+{
+	return 1;
+}
+#endif
+
 /**
  * Zwraca wersję biblioteki.
  *
@@ -142,88 +163,54 @@
  *
  * \ingroup version
  */
-const char *gg_libgadu_version()
+const char *gg_libgadu_version(void)
 {
 	return GG_LIBGADU_VERSION;
 }
 
-#ifdef HAVE_UINT64_T
-/**
- * \internal Zamienia kolejność bajtów w 64-bitowym słowie.
- *
- * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
- * big-endianowych odwraca kolejność bajtów w słowie.
- *
- * \param x Liczba do zamiany
- *
- * \return Liczba z odpowiednią kolejnością bajtów
- *
- * \ingroup helper
- */
-uint64_t gg_fix64(uint64_t x)
+void * gg_new0(size_t size)
 {
-#ifndef GG_CONFIG_BIGENDIAN
-	return x;
-#else
-	return (uint64_t)
-		(((x & (uint64_t) 0x00000000000000ffULL) << 56) |
-		((x & (uint64_t) 0x000000000000ff00ULL) << 40) |
-		((x & (uint64_t) 0x0000000000ff0000ULL) << 24) |
-		((x & (uint64_t) 0x00000000ff000000ULL) << 8) |
-		((x & (uint64_t) 0x000000ff00000000ULL) >> 8) |
-		((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) |
-		((x & (uint64_t) 0x00ff000000000000ULL) >> 40) |
-		((x & (uint64_t) 0xff00000000000000ULL) >> 56));
-#endif
+	void *ptr;
+
+	ptr = malloc(size);
+	if (ptr == NULL) {
+		gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"//gg_new0(%zd) not enough memory\n", size);
+		return NULL;
+	}
+
+	memset(ptr, 0, size);
+	return ptr;
 }
-#endif /* HAVE_UINT64_T */
-
-/**
- * \internal Zamienia kolejność bajtów w 32-bitowym słowie.
- *
- * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
- * big-endianowych odwraca kolejność bajtów w słowie.
- *
- * \param x Liczba do zamiany
- *
- * \return Liczba z odpowiednią kolejnością bajtów
- *
- * \ingroup helper
- */
-uint32_t gg_fix32(uint32_t x)
+
+int
+gg_required_proto(struct gg_session *gs, int protocol_version)
+{
+	if (gs->protocol_version >= protocol_version)
+		return 1;
+
+	gg_debug_session(gs, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// requested "
+		"feature requires protocol %#02x, but %#02x is selected\n",
+		protocol_version, gs->protocol_version);
+	return 0;
+}
+
+int gg_get_dummy_fd(struct gg_session *sess)
 {
-#ifndef GG_CONFIG_BIGENDIAN
-	return x;
-#else
-	return (uint32_t)
-		(((x & (uint32_t) 0x000000ffU) << 24) |
-		((x & (uint32_t) 0x0000ff00U) << 8) |
-		((x & (uint32_t) 0x00ff0000U) >> 8) |
-		((x & (uint32_t) 0xff000000U) >> 24));
-#endif
-}
-
-/**
- * \internal Zamienia kolejność bajtów w 16-bitowym słowie.
- *
- * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach
- * big-endianowych zamienia kolejność bajtów w słowie.
- *
- * \param x Liczba do zamiany
- *
- * \return Liczba z odpowiednią kolejnością bajtów
- *
- * \ingroup helper
- */
-uint16_t gg_fix16(uint16_t x)
-{
-#ifndef GG_CONFIG_BIGENDIAN
-	return x;
-#else
-	return (uint16_t)
-		(((x & (uint16_t) 0x00ffU) << 8) |
-		((x & (uint16_t) 0xff00U) >> 8));
-#endif
+	struct gg_session_private *p = sess->private_data;
+
+	if (p->dummyfds_created)
+		return p->dummyfds[0];
+
+	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, p->dummyfds) == -1) {
+		gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_get_dummy_fd() "
+			"unable to create pipes (errno=%d, %s)\n",
+			errno, strerror(errno));
+		return -1;
+	}
+
+	p->dummyfds_created = 1;
+	return p->dummyfds[0];
 }
 
 /**
@@ -273,6 +260,7 @@
  */
 int gg_read(struct gg_session *sess, char *buf, int length)
 {
+	struct gg_session_private *p = sess->private_data;
 	int res;
 
 #ifdef GG_CONFIG_HAVE_GNUTLS
@@ -281,11 +269,10 @@
 			res = gnutls_record_recv(GG_SESSION_GNUTLS(sess), buf, length);
 
 			if (res < 0) {
-				if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED)
-					continue;
-
 				if (res == GNUTLS_E_AGAIN)
 					errno = EAGAIN;
+				else if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED)
+					continue;
 				else
 					errno = EINVAL;
 
@@ -323,8 +310,33 @@
 	}
 #endif
 
+	if (p->socket_handle != NULL) {
+		if (p->socket_manager.read_cb == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+				"// gg_read() socket_manager.read callback is "
+				"empty\n");
+			errno = EINVAL;
+			return -1;
+		}
+
+		do {
+			res = p->socket_manager.read_cb(
+				p->socket_manager.cb_data, p->socket_handle,
+				(unsigned char*)buf, length);
+		} while (res < 0 && errno == EINTR);
+
+		if (res < 0) {
+			if (errno == EAGAIN)
+				return -1;
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+				"// gg_read() unexpected errno=%d\n", errno);
+			errno = EINVAL;
+		}
+		return res;
+	}
+
 	for (;;) {
-		res = read(sess->fd, buf, length);
+		res = recv(sess->fd, buf, length, 0);
 
 		if (res == -1 && errno == EINTR)
 			continue;
@@ -351,6 +363,7 @@
  */
 static int gg_write_common(struct gg_session *sess, const char *buf, int length)
 {
+	struct gg_session_private *p = sess->private_data;
 	int res;
 
 #ifdef GG_CONFIG_HAVE_GNUTLS
@@ -401,8 +414,34 @@
 	}
 #endif
 
+	if (p->socket_handle != NULL) {
+		if (p->socket_manager.write_cb == NULL) {
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+				"// gg_write_common() socket_manager.write "
+				"callback is empty\n");
+			errno = EINVAL;
+			return -1;
+		}
+
+		do {
+			res = p->socket_manager.write_cb(
+				p->socket_manager.cb_data, p->socket_handle,
+				(const unsigned char*)buf, length);
+		} while (res < 0 && errno == EINTR);
+
+		if (res < 0) {
+			if (errno == EAGAIN)
+				return -1;
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+				"// gg_read() unexpected errno=%d\n", errno);
+			errno = EINVAL;
+		}
+
+		return res;
+	}
+
 	for (;;) {
-		res = write(sess->fd, buf, length);
+		res = send(sess->fd, buf, length, 0);
 
 		if (res == -1 && errno == EINTR)
 			continue;
@@ -469,6 +508,46 @@
 	return res;
 }
 
+void gg_close(struct gg_session *sess)
+{
+	struct gg_session_private *p = sess->private_data;
+	int errno_copy;
+
+	errno_copy = errno;
+
+	if (!p->socket_is_external) {
+		if (sess->fd != -1)
+			close(sess->fd);
+	} else {
+		assert(p->socket_manager_type !=
+			GG_SOCKET_MANAGER_TYPE_INTERNAL);
+		if (p->socket_handle != NULL) {
+			p->socket_manager.close_cb(p->socket_manager.cb_data,
+				p->socket_handle);
+		}
+		p->socket_is_external = 0;
+	}
+	sess->fd = -1;
+	p->socket_handle = NULL;
+
+	while (p->event_queue) {
+		gg_eventqueue_t *next = p->event_queue->next;
+		gg_event_free(p->event_queue->event);
+		free(p->event_queue);
+		p->event_queue = next;
+	}
+
+	if (p->dummyfds_created) {
+		close(p->dummyfds[0]);
+		close(p->dummyfds[1]);
+		p->dummyfds_created = 0;
+	}
+
+	gg_compat_message_cleanup(sess);
+
+	errno = errno_copy;
+}
+
 /**
  * \internal Odbiera pakiet od serwera.
  *
@@ -476,7 +555,7 @@
  * w zaalokowanym buforze.
  *
  * Przy połączeniach asynchronicznych, funkcja może nie być w stanie
- * skompletować całego pakietu -- w takim przypadku zwróci -1, a kodem błędu
+ * skompletować całego pakietu -- w takim przypadku zwróci \c NULL, a kodem błędu
  * będzie \c EAGAIN.
  *
  * \param sess Struktura sesji
@@ -485,129 +564,117 @@
  */
 void *gg_recv_packet(struct gg_session *sess)
 {
-	struct gg_header h;
+	struct gg_header *gh;
 	char *packet;
-	int ret = 0;
-	unsigned int offset, size = 0;
+	int res;
+	size_t len;
 
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess);
 
-	if (!sess) {
+	if (sess == NULL) {
 		errno = EFAULT;
 		return NULL;
 	}
 
-	if (sess->recv_left < 1) {
-		if (sess->header_buf) {
-			memcpy(&h, sess->header_buf, sess->header_done);
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done);
-			free(sess->header_buf);
-			sess->header_buf = NULL;
-		} else
-			sess->header_done = 0;
-
-		while (sess->header_done < sizeof(h)) {
-			ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done);
-
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, (char*)&h + sess->header_done, sizeof(h) - sess->header_done, ret);
-
-			if (!ret) {
-				errno = ECONNRESET;
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n");
-				return NULL;
-			}
-
-			if (ret == -1) {
-				if (errno == EAGAIN) {
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n");
-
-					if (!(sess->header_buf = malloc(sess->header_done))) {
-						gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n");
-						return NULL;
-					}
-
-					memcpy(sess->header_buf, &h, sess->header_done);
-
-					errno = EAGAIN;
-
-					return NULL;
-				}
-
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno));
-
+	for (;;) {
+		if (sess->recv_buf == NULL && sess->recv_done == 0) {
+			sess->recv_buf = malloc(sizeof(struct gg_header) + 1);
+
+			if (sess->recv_buf == NULL) {
+				gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() out of memory\n");
 				return NULL;
 			}
-
-			sess->header_done += ret;
 		}
 
-		h.type = gg_fix32(h.type);
-		h.length = gg_fix32(h.length);
-	} else
-		memcpy(&h, sess->recv_buf, sizeof(h));
-
-	/* jakieś sensowne limity na rozmiar pakietu */
-	if (h.length > 65535) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length);
-		errno = ERANGE;
-		return NULL;
-	}
-
-	if (sess->recv_left > 0) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n");
-		size = sess->recv_left;
-		offset = sess->recv_done;
-	} else {
-		if (!(sess->recv_buf = malloc(sizeof(h) + h.length + 1))) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n");
-			return NULL;
+		gh = (struct gg_header*) sess->recv_buf;
+
+		if ((size_t) sess->recv_done < sizeof(struct gg_header)) {
+			len = sizeof(struct gg_header) - sess->recv_done;
+			gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() header: %d done, %d to go\n", sess->recv_done, len);
+		} else {
+			uint32_t ghlen = gh ? gg_fix32(gh->length) : 0;
+			if ((size_t) sess->recv_done >= sizeof(struct gg_header) + ghlen) {
+				gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() and that's it\n");
+				break;
+			}
+
+			len = sizeof(struct gg_header) + ghlen - sess->recv_done;
+
+			gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() payload: %d done, %d length, %d to go\n", sess->recv_done, ghlen, len);
 		}
 
-		memcpy(sess->recv_buf, &h, sizeof(h));
-
-		offset = 0;
-		size = h.length;
-	}
-
-	while (size > 0) {
-		ret = gg_read(sess, sess->recv_buf + sizeof(h) + offset, size);
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, sess->recv_buf + sizeof(h) + offset, size, ret);
-		if (!ret) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n");
+		res = gg_read(sess, sess->recv_buf + sess->recv_done, len);
+
+		if (res == 0) {
 			errno = ECONNRESET;
+			gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() connection broken\n");
 			goto fail;
 		}
-		if (ret > -1 && ret <= size) {
-			offset += ret;
-			size -= ret;
-		} else if (ret == -1) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno));
-
-			if (errno == EAGAIN) {
-				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size);
-				sess->recv_left = size;
-				sess->recv_done = offset;
-				return NULL;
-			}
-
+
+		if (res == -1 && errno == EAGAIN) {
+			gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() resource temporarily unavailable\n");
+			goto eagain;
+		}
+
+		if (res == -1) {
+			gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() read failed: errno=%d, %s\n", errno, strerror(errno));
 			goto fail;
 		}
+
+		gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() read %d bytes\n", res);
+
+		if (sess->recv_done + res == sizeof(struct gg_header)) {
+			char *tmp;
+			uint32_t ghlen = gh ? gg_fix32(gh->length) : 0;
+
+			gg_debug_session(sess, GG_DEBUG_NET, "// gg_recv_packet() header complete, payload %d bytes\n", ghlen);
+
+			if (ghlen == 0)
+				break;
+
+			if (ghlen > 65535) {
+				gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() invalid packet length (%d)\n", ghlen);
+				errno = ERANGE;
+				goto fail;
+			}
+
+			tmp = realloc(sess->recv_buf, sizeof(struct gg_header) + ghlen + 1);
+
+			if (tmp == NULL) {
+				gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_recv_packet() out of memory\n");
+				goto fail;
+			}
+
+			sess->recv_buf = tmp;
+		}
+
+		sess->recv_done += res;
 	}
 
 	packet = sess->recv_buf;
 	sess->recv_buf = NULL;
-	sess->recv_left = 0;
-
-	gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_recv_packet(type=0x%.2x, length=%d)\n", h.type, h.length);
-	gg_debug_dump(sess, GG_DEBUG_DUMP, packet, sizeof(h) + h.length);
+	sess->recv_done = 0;
+
+	if (gh == NULL)
+		goto fail;
+
+	/* Czasami zakładamy, że teksty w pakietach są zakończone zerem */
+	packet[sizeof(struct gg_header) + gg_fix32(gh->length)] = 0;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet(type=0x%.2x, length=%d)\n", gg_fix32(gh->type), gg_fix32(gh->length));
+	gg_debug_dump(sess, GG_DEBUG_DUMP, packet, sizeof(struct gg_header) + gg_fix32(gh->length));
+
+	gh->type = gg_fix32(gh->type);
+	gh->length = gg_fix32(gh->length);
 
 	return packet;
 
 fail:
 	free(sess->recv_buf);
 	sess->recv_buf = NULL;
-	sess->recv_left = 0;
-
+	sess->recv_done = 0;
+
+eagain:
 	return NULL;
 }
 
@@ -621,7 +688,7 @@
  * \param sess Struktura sesji
  * \param type Rodzaj pakietu
  * \param ... Lista kolejnych części pakietu (wskaźnik na bufor i długość
- *            typu \c int) zakończona \c NULL
+ *	    typu \c int) zakończona \c NULL
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  */
@@ -640,7 +707,7 @@
 	tmp_length = sizeof(struct gg_header);
 
 	if (!(tmp = malloc(tmp_length))) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n");
+		gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_send_packet() not enough memory for packet header\n");
 		return -1;
 	}
 
@@ -654,7 +721,7 @@
 		payload_length = va_arg(ap, unsigned int);
 
 		if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) {
-			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n");
+			gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_send_packet() not enough memory for payload\n");
 			free(tmp);
 			va_end(ap);
 			return -1;
@@ -674,7 +741,7 @@
 	h->type = gg_fix32(type);
 	h->length = gg_fix32(tmp_length - sizeof(struct gg_header));
 
-	gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_send_packet(type=0x%.2x, length=%d)\n", gg_fix32(h->type), gg_fix32(h->length));
+	gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet(type=0x%.2x, length=%d)\n", gg_fix32(h->type), gg_fix32(h->length));
 	gg_debug_dump(sess, GG_DEBUG_DUMP, tmp, tmp_length);
 
 	res = gg_write(sess, tmp, tmp_length);
@@ -682,12 +749,12 @@
 	free(tmp);
 
 	if (res == -1) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno));
+		gg_debug_session(sess, GG_DEBUG_ERROR, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno));
 		return -1;
 	}
 
 	if (sess->async)
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() partial write(), %d sent, %d left, %d total left\n", res, tmp_length - res, sess->send_left);
+		gg_debug_session(sess, GG_DEBUG_NET, "// gg_send_packet() partial write(), %d sent, %d left, %d total left\n", res, tmp_length - res, sess->send_left);
 
 	if (sess->send_buf)
 		sess->check |= GG_CHECK_WRITE;
@@ -743,20 +810,19 @@
  *       obsługa SSL nie jest wkompilowana.
  *
  * \param p Struktura opisująca parametry połączenia. Wymagane pola: uin,
- *          password, async.
+ *	  password, async.
  *
  * \return Wskaźnik do zaalokowanej struktury sesji \c gg_session lub NULL
- *         w przypadku błędu.
+ *	 w przypadku błędu.
  *
  * \ingroup login
  */
 struct gg_session *gg_login(const struct gg_login_params *p)
 {
 	struct gg_session *sess = NULL;
-	char *hostname;
-	int port;
-
-	if (!p) {
+	struct gg_session_private *sess_private = NULL;
+
+	if (p == NULL) {
 		gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p);
 		errno = EFAULT;
 		return NULL;
@@ -764,14 +830,27 @@
 
 	gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async);
 
-	if (!(sess = malloc(sizeof(struct gg_session)))) {
+	sess = malloc(sizeof(struct gg_session));
+
+	if (sess == NULL) {
 		gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n");
 		goto fail;
 	}
 
 	memset(sess, 0, sizeof(struct gg_session));
-
-	if (!p->password || !p->uin) {
+	sess->fd = -1;
+
+	sess_private = malloc(sizeof(struct gg_session_private));
+
+	if (sess_private == NULL) {
+		gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session private data\n");
+		goto fail;
+	}
+
+	memset(sess_private, 0, sizeof(struct gg_session_private));
+	sess->private_data = sess_private;
+
+	if (p->password == NULL || p->uin == 0) {
 		gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n");
 		errno = EFAULT;
 		goto fail;
@@ -797,13 +876,65 @@
 	sess->initial_status = p->status;
 	sess->callback = gg_session_callback;
 	sess->destroy = gg_free_session;
-	sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT);
+	sess->port = p->server_port;
 	sess->server_addr = p->server_addr;
 	sess->external_port = p->external_port;
 	sess->external_addr = p->external_addr;
 	sess->client_addr = p->client_addr;
 	sess->client_port = p->client_port;
 
+	if (GG_LOGIN_PARAMS_HAS_FIELD(p, compatibility))
+		sess_private->compatibility = p->compatibility;
+
+	if (GG_LOGIN_PARAMS_HAS_FIELD(p, connect_host) && p->connect_host != NULL) {
+		int port = 0;
+		char *colon;
+
+		sess->connect_host = strdup(p->connect_host);
+		if (sess->connect_host == NULL)
+			goto fail;
+
+		colon = strchr(sess->connect_host, ':');
+		if (colon != NULL) {
+			colon[0] = '\0';
+			port = atoi(colon + 1);
+		}
+		if (port > 0)
+			sess->port = port;
+	}
+
+	if (GG_LOGIN_PARAMS_HAS_FIELD(p, socket_manager_type) &&
+		GG_LOGIN_PARAMS_HAS_FIELD(p, socket_manager) &&
+		p->socket_manager_type != GG_SOCKET_MANAGER_TYPE_INTERNAL)
+	{
+		if ((unsigned int)p->socket_manager_type >
+			GG_SOCKET_MANAGER_TYPE_TLS)
+		{
+			gg_debug(GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_login()"
+				" invalid arguments. unknown socket manager "
+				"type (%d)\n", p->socket_manager_type);
+			errno = EFAULT;
+			goto fail;
+		} else {
+			sess_private->socket_manager_type =
+				p->socket_manager_type;
+			memcpy(&sess_private->socket_manager,
+				&p->socket_manager,
+				sizeof(gg_socket_manager_t));
+		}
+	} else {
+		sess_private->socket_manager_type =
+			GG_SOCKET_MANAGER_TYPE_INTERNAL;
+	}
+
+	if (GG_LOGIN_PARAMS_HAS_FIELD(p, host_white_list) &&
+		p->host_white_list != NULL) {
+		sess_private->host_white_list =
+			gg_strarr_dup(p->host_white_list);
+		if (sess_private->host_white_list == NULL)
+			goto fail;
+	}
+
 	if (p->protocol_features == 0) {
 		sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION;
 	} else {
@@ -819,12 +950,14 @@
 	if (!(sess->status_flags = p->status_flags))
 		sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM;
 
-	sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION;
-
-	if (p->era_omnix)
-		sess->protocol_flags |= GG_ERA_OMNIX_MASK;
-	if (p->has_audio)
-		sess->protocol_flags |= GG_HAS_AUDIO_MASK;
+	if (!p->protocol_version)
+		sess->protocol_version = GG_DEFAULT_PROTOCOL_VERSION;
+	else if (p->protocol_version < 0x2e) {
+		gg_debug(GG_DEBUG_MISC, "// gg_login() libgadu no longer support protocol < 0x2e\n");
+		sess->protocol_version = 0x2e;
+	} else
+		sess->protocol_version = p->protocol_version;
+
 	sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL;
 	sess->last_sysmsg = p->last_sysmsg;
 	sess->image_size = p->image_size;
@@ -838,201 +971,127 @@
 	}
 
 	if (p->status_descr) {
-		int max_length;
-
-		if (sess->protocol_version >= 0x2d)
-			max_length = GG_STATUS_DESCR_MAXSIZE;
-		else
-			max_length = GG_STATUS_DESCR_MAXSIZE_PRE_8_0;
-
-		if (sess->protocol_version >= 0x2d)
-			sess->initial_descr = gg_encoding_convert(p->status_descr, p->encoding, GG_ENCODING_UTF8, -1, -1);
-		else
-			sess->initial_descr = strdup(p->status_descr);
+		sess->initial_descr = gg_encoding_convert(p->status_descr, p->encoding, GG_ENCODING_UTF8, -1, -1);
 
 		if (!sess->initial_descr) {
 			gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n");
 			goto fail;
 		}
 		
-		// XXX pamiętać, żeby nie ciąć w środku znaku utf-8
+		/* XXX pamiętać, żeby nie ciąć w środku znaku utf-8 */
 		
-		if (strlen(sess->initial_descr) > max_length)
-			sess->initial_descr[max_length] = 0;
+		if (strlen(sess->initial_descr) > GG_STATUS_DESCR_MAXSIZE)
+			sess->initial_descr[GG_STATUS_DESCR_MAXSIZE] = 0;
 	}
 
 	if (p->tls != GG_SSL_DISABLED) {
-#ifdef GG_CONFIG_HAVE_GNUTLS
-		gg_session_gnutls_t *tmp;
-
-		tmp = malloc(sizeof(gg_session_gnutls_t));
-
-		if (tmp == NULL) {
-			gg_debug(GG_DEBUG_MISC, "// gg_login() out of memory for GnuTLS session\n");
-			goto fail;
-		}
-
-		sess->ssl = tmp;
-
-		gnutls_global_init();
-		gnutls_certificate_allocate_credentials(&tmp->xcred);
-		gnutls_init(&tmp->session, GNUTLS_CLIENT);
-		gnutls_set_default_priority(tmp->session);
-		gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred);
-#elif defined(GG_CONFIG_HAVE_OPENSSL)
-		char buf[1024];
-
-		OpenSSL_add_ssl_algorithms();
-
-		if (!RAND_status()) {
-			char rdata[1024];
-			struct {
-				time_t time;
-				void *ptr;
-			} rstruct;
-
-			time(&rstruct.time);
-			rstruct.ptr = (void *) &rstruct;
-
-			RAND_seed((void *) rdata, sizeof(rdata));
-			RAND_seed((void *) &rstruct, sizeof(rstruct));
-		}
-
-		sess->ssl_ctx = SSL_CTX_new(SSLv3_client_method());
-
-		if (!sess->ssl_ctx) {
-			ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
-			gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf);
-			goto fail;
-		}
-
-		SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL);
-
-		sess->ssl = SSL_new(sess->ssl_ctx);
-
-		if (!sess->ssl) {
-			ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
-			gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf);
-			goto fail;
-		}
-#else
+#if !defined(GG_CONFIG_HAVE_GNUTLS) && !defined(GG_CONFIG_HAVE_OPENSSL)
 		gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n");
 
 		if (p->tls == GG_SSL_REQUIRED) {
 			errno = ENOSYS;
 			goto fail;
 		}
+#else
+		sess->ssl_flag = p->tls;
 #endif
 	}
 
-	if (gg_proxy_enabled) {
-		hostname = gg_proxy_host;
-		sess->proxy_port = port = gg_proxy_port;
-	} else {
-		hostname = GG_APPMSG_HOST;
-		port = GG_APPMSG_PORT;
-	}
-
 	if (p->hash_type)
 		sess->hash_type = p->hash_type;
 	else
 		sess->hash_type = GG_LOGIN_HASH_SHA1;
 
-	if (!p->async) {
-		struct in_addr addr;
-
-		if (!sess->server_addr) {
-			if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) {
-				struct in_addr *addr_list = NULL;
-				int addr_count;
-
-				if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) {
-					gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname);
-					free(addr_list);
-					goto fail;
-				}
-
-				addr = addr_list[0];
-
-				free(addr_list);
-			}
+	if (sess->server_addr == 0 && sess->connect_host == NULL) {
+		if (gg_proxy_enabled) {
+			sess->resolver_host = gg_proxy_host;
+			sess->proxy_port = gg_proxy_port;
+			sess->state = (sess->async) ? GG_STATE_RESOLVE_PROXY_HUB_ASYNC : GG_STATE_RESOLVE_PROXY_HUB_SYNC;
 		} else {
-			addr.s_addr = sess->server_addr;
-			port = sess->port;
+			sess->resolver_host = GG_APPMSG_HOST;
+			sess->proxy_port = 0;
+			sess->state = (sess->async) ? GG_STATE_RESOLVE_HUB_ASYNC : GG_STATE_RESOLVE_HUB_SYNC;
+		}
+	} else {
+		if (sess->connect_host != NULL)
+			sess->server_addr = 0;
+		else {
+			// XXX inet_ntoa i wielowątkowość
+			sess->connect_host = strdup(inet_ntoa(*(struct in_addr*) &sess->server_addr));
+			if (sess->connect_host == NULL)
+				goto fail;
 		}
-
-		sess->hub_addr = addr.s_addr;
-
-		if (gg_proxy_enabled)
-			sess->proxy_addr = addr.s_addr;
-
-		if ((sess->fd = gg_connect(&addr, port, 0)) == -1) {
-			gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno));
-
-			/* nie wyszło? próbujemy portu 443. */
-			if (sess->server_addr) {
-				sess->port = GG_HTTPS_PORT;
-
-				if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, 0)) == -1) {
-					/* ostatnia deska ratunku zawiodła?
-					 * w takim razie zwijamy manatki. */
-					gg_debug_session(sess, GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno));
-					goto fail;
+		sess->connect_index = 0;
+
+		if (gg_proxy_enabled) {
+			sess->resolver_host = gg_proxy_host;
+			sess->proxy_port = gg_proxy_port;
+			if (sess->port == 0)
+				sess->connect_port[0] = GG_HTTPS_PORT;
+			else
+				sess->connect_port[0] = sess->port;
+			sess->connect_port[1] = 0;
+			sess->state = (sess->async) ? GG_STATE_RESOLVE_PROXY_GG_ASYNC : GG_STATE_RESOLVE_PROXY_GG_SYNC;
+		} else {
+			sess->resolver_host = sess->connect_host;
+			if (sess->port == 0) {
+				if (sess->ssl_flag == GG_SSL_DISABLED) {
+					sess->connect_port[0] = GG_DEFAULT_PORT;
+					sess->connect_port[1] = GG_HTTPS_PORT;
+				} else {
+					sess->connect_port[0] = GG_HTTPS_PORT;
+					sess->connect_port[1] = 0;
 				}
 			} else {
-				goto fail;
+				sess->connect_port[0] = sess->port;
+				sess->connect_port[1] = 0;
 			}
+			sess->state = (sess->async) ? GG_STATE_RESOLVE_GG_ASYNC : GG_STATE_RESOLVE_GG_SYNC;
 		}
-
-		if (sess->server_addr)
-			sess->state = GG_STATE_CONNECTING_GG;
-		else
-			sess->state = GG_STATE_CONNECTING_HUB;
-
-		while (sess->state != GG_STATE_CONNECTED) {
-			struct gg_event *e;
-
-			if (!(e = gg_watch_fd(sess))) {
-				gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n");
+	}
+
+	// XXX inaczej gg_watch_fd() wyjdzie z timeoutem
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+
+	if (!sess->async) {
+		while (!GG_SESSION_IS_CONNECTED(sess)) {
+			struct gg_event *ge;
+
+			ge = gg_watch_fd(sess);
+
+			if (ge == NULL) {
+				gg_debug(GG_DEBUG_MISC, "// gg_session_connect() critical error in gg_watch_fd()\n");
 				goto fail;
 			}
 
-			if (e->type == GG_EVENT_CONN_FAILED) {
+			if (ge->type == GG_EVENT_CONN_FAILED) {
 				errno = EACCES;
-				gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n");
-				gg_event_free(e);
+				gg_debug(GG_DEBUG_MISC, "// gg_session_connect() could not login\n");
+				gg_event_free(ge);
 				goto fail;
 			}
 
-			gg_event_free(e);
+			gg_event_free(ge);
 		}
-
-		return sess;
-	}
-
-	if (!sess->server_addr || gg_proxy_enabled) {
-		if (sess->resolver_start(&sess->fd, &sess->resolver, hostname) == -1) {
-			gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
+	} else {
+		struct gg_event *ge;
+
+		ge = gg_watch_fd(sess);
+
+		if (ge == NULL) {
+			gg_debug(GG_DEBUG_MISC, "// gg_session_connect() critical error in gg_watch_fd()\n");
 			goto fail;
 		}
-	} else {
-		if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) {
-			gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno));
-			goto fail;
-		}
-		sess->state = GG_STATE_CONNECTING_GG;
-		sess->check = GG_CHECK_WRITE;
-		sess->soft_timeout = 1;
+
+		gg_event_free(ge);
 	}
 
+	
+
 	return sess;
 
 fail:
-	if (sess) {
-		free(sess->password);
-		free(sess->initial_descr);
-		free(sess);
-	}
+	gg_free_session(sess);
 
 	return NULL;
 }
@@ -1105,10 +1164,7 @@
 
 	sess->resolver_cleanup(&sess->resolver, 1);
 
-	if (sess->fd != -1) {
-		close(sess->fd);
-		sess->fd = -1;
-	}
+	gg_close(sess);
 
 	if (sess->send_buf) {
 		free(sess->send_buf);
@@ -1129,17 +1185,22 @@
 void gg_free_session(struct gg_session *sess)
 {
 	struct gg_dcc7 *dcc;
-
-	if (!sess)
+	gg_chat_list_t *chat;
+
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_free_session(%p);\n", sess);
+
+	if (sess == NULL)
 		return;
 
 	/* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */
 
+	free(sess->resolver_result);
+	free(sess->connect_host);
 	free(sess->password);
 	free(sess->initial_descr);
 	free(sess->client_version);
+	free(sess->header_buf);
 	free(sess->recv_buf);
-	free(sess->header_buf);
 
 #ifdef GG_CONFIG_HAVE_GNUTLS
 	if (sess->ssl != NULL) {
@@ -1163,8 +1224,7 @@
 
 	sess->resolver_cleanup(&sess->resolver, 1);
 
-	if (sess->fd != -1)
-		close(sess->fd);
+	gg_close(sess);
 
 	while (sess->images)
 		gg_image_queue_remove(sess, sess->images, 1);
@@ -1174,129 +1234,21 @@
 	for (dcc = sess->dcc7_list; dcc; dcc = dcc->next)
 		dcc->sess = NULL;
 
+	chat = sess->private_data->chat_list;
+	while (chat != NULL) {
+		gg_chat_list_t *next = chat->next;
+		free(chat->participants);
+		free(chat);
+		chat = next;
+	}
+
+	gg_strarr_free(sess->private_data->host_white_list);
+
+	free(sess->private_data);
+
 	free(sess);
 }
 
-#ifndef DOXYGEN
-
-/**
- * \internal Funkcja wysyłająca pakiet zmiany statusu użytkownika.
- *
- * \param sess Struktura sesji
- * \param status Nowy status użytkownika
- * \param descr Opis statusu użytkownika (lub \c NULL)
- * \param time Czas powrotu w postaci uniksowego znacznika czasu (lub 0)
- *
- * \return 0 jeśli się powiodło, -1 w przypadku błędu
- *
- * \ingroup status
- */
-static int gg_change_status_common(struct gg_session *sess, int status, const char *descr, int time)
-{
-	char *new_descr = NULL;
-	uint32_t new_time;
-	int descr_len = 0;
-	int descr_len_max;
-	int packet_type;
-	int append_null = 0;
-	int res;
-
-	if (!sess) {
-		errno = EFAULT;
-		return -1;
-	}
-
-	if (sess->state != GG_STATE_CONNECTED) {
-		errno = ENOTCONN;
-		return -1;
-	}
-
-	/* XXX, obcinać stany których stary protokół niezna (czyt. dnd->aw; ffc->av) */
-
-	/* dodaj flagę obsługi połączeń głosowych zgodną z GG 7.x */
-	if ((sess->protocol_version >= 0x2a) && (sess->protocol_version < 0x2d /* ? */ ) && (sess->protocol_flags & GG_HAS_AUDIO_MASK) && !GG_S_I(status))
-		status |= GG_STATUS_VOICE_MASK;
-
-	sess->status = status;
-
-	if (sess->protocol_version >= 0x2d) {
-		if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) {
-			new_descr = gg_encoding_convert(descr, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1);
-
-			if (!new_descr)
-				return -1;
-		}
-
-		if (sess->protocol_version >= 0x2e)
-			packet_type = GG_NEW_STATUS80;
-		else /* sess->protocol_version == 0x2d */
-			packet_type = GG_NEW_STATUS80BETA;
-		descr_len_max = GG_STATUS_DESCR_MAXSIZE;
-		append_null = 1;
-
-	} else {
-		packet_type = GG_NEW_STATUS;
-		descr_len_max = GG_STATUS_DESCR_MAXSIZE_PRE_8_0;
-
-		if (time != 0)
-			append_null = 1;
-	}
-
-	if (descr) {
-		descr_len = strlen((new_descr) ? new_descr : descr);
-
-		if (descr_len > descr_len_max)
-			descr_len = descr_len_max;
-
-		// XXX pamiętać o tym, żeby nie ucinać w środku znaku utf-8
-	}
-
-	if (time)
-		new_time = gg_fix32(time);
-
-	if (packet_type == GG_NEW_STATUS80) {
-		struct gg_new_status80 p;
-
-		p.status		= gg_fix32(status);
-		p.flags			= gg_fix32(sess->status_flags);
-		p.description_size	= gg_fix32(descr_len);
-		res = gg_send_packet(sess,
-				packet_type,
-				&p,
-				sizeof(p),
-				(new_descr) ? new_descr : descr,
-				descr_len,
-				NULL);
-
-	} else {
-		struct gg_new_status p;
-
-		p.status = gg_fix32(status);
-		res = gg_send_packet(sess,
-				packet_type,
-				&p,
-				sizeof(p),
-				(new_descr) ? new_descr : descr,
-				descr_len,
-				(append_null) ? "\0" : NULL,
-				(append_null) ? 1 : 0,
-				(time) ? &new_time : NULL,
-				(time) ? sizeof(new_time) : 0,
-				NULL);
-	}
-
-	free(new_descr);
-
-	if (GG_S_NA(status)) {
-		sess->state = GG_STATE_DISCONNECTING;
-		sess->timeout = GG_TIMEOUT_DISCONNECT;
-	}
-
-	return res;
-}
-
-#endif /* DOXYGEN */
-
 /**
  * Zmienia status użytkownika.
  *
@@ -1311,7 +1263,7 @@
 {
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status);
 
-	return gg_change_status_common(sess, status, NULL, 0);
+	return gg_change_status_descr(sess, status, NULL);
 }
 
 /**
@@ -1319,7 +1271,7 @@
  *
  * \param sess Struktura sesji
  * \param status Nowy status użytkownika
- * \param descr Opis statusu użytkownika
+ * \param descr Opis statusu użytkownika (lub \c NULL)
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  *
@@ -1327,9 +1279,67 @@
  */
 int gg_change_status_descr(struct gg_session *sess, int status, const char *descr)
 {
+	struct gg_new_status80 p;
+	char *gen_descr = NULL;
+	int descr_len = 0;
+	int descr_null_len = 0;
+	int res;
+
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr);
 
-	return gg_change_status_common(sess, status, descr, 0);
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	sess->status = status;
+
+	if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) {
+		descr = gen_descr = gg_encoding_convert(descr, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1);
+
+		if (!gen_descr)
+			return -1;
+	}
+
+	if (descr) {
+		descr_len = strlen(descr);
+
+		if (descr_len > GG_STATUS_DESCR_MAXSIZE)
+			descr_len = GG_STATUS_DESCR_MAXSIZE;
+
+		/* XXX pamiętać o tym, żeby nie ucinać w środku znaku utf-8 */
+	} else {
+		descr = "";
+	}
+
+	p.status		= gg_fix32(status);
+	p.flags			= gg_fix32(sess->status_flags);
+	p.description_size	= gg_fix32(descr_len);
+
+	if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) {
+		p.flags = gg_fix32(0x00000014);
+		descr_null_len = 1;
+	}
+
+	res = gg_send_packet(sess, GG_NEW_STATUS80, 
+			&p, sizeof(p), 
+			descr, descr_len,
+			"\x00", descr_null_len,
+			NULL);
+
+	free(gen_descr);
+
+	if (GG_S_NA(status)) {
+		sess->state = GG_STATE_DISCONNECTING;
+		sess->timeout = GG_TIMEOUT_DISCONNECT;
+	}
+
+	return res;
 }
 
 /**
@@ -1338,17 +1348,17 @@
  * \param sess Struktura sesji
  * \param status Nowy status użytkownika
  * \param descr Opis statusu użytkownika
- * \param time Czas powrotu w postaci uniksowego znacznika czasu
+ * \param ts Czas powrotu w postaci uniksowego znacznika czasu
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
  *
  * \ingroup status
  */
-int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time)
+int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int ts)
 {
-	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time);
-
-	return gg_change_status_common(sess, status, descr, time);
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, ts);
+
+	return gg_change_status_descr(sess, status, descr);
 }
 
 /**
@@ -1378,6 +1388,315 @@
 	return 0;
 }
 
+#ifndef DOXYGEN
+
+static int gg_send_message_110(struct gg_session *sess,
+	uin_t recipient, uint64_t chat_id,
+	const char *message, int is_html)
+{
+	GG110SendMessage msg = GG110_SEND_MESSAGE__INIT;
+	int packet_type = recipient ? GG_SEND_MSG110 : GG_CHAT_SEND_MSG;
+	int seq;
+	char *html_message_gen = NULL, *plain_message_gen = NULL;
+	const char *html_message, *plain_message;
+	int succ = 1;
+
+	gg_debug_session(sess, GG_DEBUG_FUNCTION,
+		"** gg_send_message_110(%p, %u, %llu, %p, %d);\n",
+		sess, recipient, chat_id, message, is_html);
+
+	if (message == NULL)
+		return -1;
+
+	if ((recipient == 0) == (chat_id == 0))
+		return -1;
+
+	if (is_html) {
+		html_message = message;
+
+		if (sess->encoding != GG_ENCODING_UTF8) {
+			html_message = html_message_gen = gg_encoding_convert(
+				html_message, sess->encoding, GG_ENCODING_UTF8,
+				-1, -1);
+			if (html_message_gen == NULL)
+				return -1;
+		}
+
+		plain_message = plain_message_gen =
+			gg_message_html_to_text_110(html_message);
+		if (plain_message_gen == NULL) {
+			free(html_message_gen);
+			return -1;
+		}
+	} else {
+		plain_message = message;
+
+		if (sess->encoding != GG_ENCODING_UTF8) {
+			plain_message = plain_message_gen = gg_encoding_convert(
+				plain_message, sess->encoding, GG_ENCODING_UTF8,
+				-1, -1);
+			if (plain_message_gen == NULL)
+				return -1;
+		}
+		
+		html_message = html_message_gen =
+			gg_message_text_to_html_110(plain_message, -1);
+		if (html_message_gen == NULL) {
+			free(plain_message_gen);
+			return -1;
+		}
+	}
+
+	seq = ++sess->seq;
+
+	if (recipient) {
+		msg.has_recipient = 1;
+		gg_protobuf_set_uin(&msg.recipient, recipient, NULL);
+	}
+
+	msg.seq = seq;
+
+	/* rzutujemy z const, ale msg i tak nie będzie modyfikowany */
+	msg.msg_plain = (char*)plain_message;
+	msg.msg_xhtml = (char*)html_message;
+
+	if (chat_id) {
+		msg.dummy3 = "";
+		msg.has_chat_id = 1;
+		msg.chat_id = chat_id;
+	}
+
+	if (!GG_PROTOBUF_SEND(sess, NULL, packet_type, gg110_send_message, msg))
+		succ = 0;
+
+	free(html_message_gen);
+	free(plain_message_gen);
+
+	return succ ? seq : -1;
+}
+
+/**
+ * \internal Wysyła wiadomość.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipients_count Liczba adresatów
+ * \param recipients Wskaźnik do tablicy z numerami adresatów
+ * \param message Treść wiadomości
+ * \param format Informacje o formatowaniu
+ * \param formatlen Długość informacji o formatowaniu
+ * \param html_message Treść wiadomości HTML
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+static int gg_send_message_common(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen, const unsigned char *html_message)
+{
+	struct gg_send_msg80 s80;
+	const char *cp_msg = NULL, *utf_html_msg = NULL;
+	char *recoded_msg = NULL, *recoded_html_msg = NULL;
+	unsigned char *generated_format = NULL;
+	int seq_no = -1;
+
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_common(%p, %d, %d, %p, %p, %p, %d, %p);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen, html_message);
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	if ((message == NULL && html_message == NULL) || recipients_count <= 0 || recipients_count > 0xffff || recipients == NULL || (format == NULL && formatlen != 0)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (sess->protocol_version >= GG_PROTOCOL_VERSION_110 && recipients_count == 1)
+	{
+		int is_html = (html_message != NULL);
+		seq_no = gg_send_message_110(sess, recipients[0], 0,
+			(const char*)(is_html ? html_message : message),
+			is_html);
+		goto cleanup;
+	}
+
+	if (sess->protocol_version >= GG_PROTOCOL_VERSION_110 && !gg_compat_feature_is_enabled(sess, GG_COMPAT_FEATURE_LEGACY_CONFER)) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR, "// gg_send_message_common() legacy conferences disabled\n");
+		errno = EINVAL;
+		return -1;
+	}
+
+	if (message == NULL) {
+		char *tmp_msg;
+		size_t len, fmt_len;
+		uint16_t fixed_fmt_len;
+
+		len = gg_message_html_to_text(NULL, NULL, &fmt_len, (const char*) html_message, sess->encoding);
+
+		tmp_msg = malloc(len + 1);
+
+		if (tmp_msg == NULL)
+			goto cleanup;
+
+		if (fmt_len != 0) {
+			generated_format = malloc(fmt_len + 3);
+
+			if (generated_format == NULL) {
+				free(tmp_msg);
+				goto cleanup;
+			}
+
+			generated_format[0] = '\x02';
+			fixed_fmt_len = gg_fix16(fmt_len);
+			memcpy(generated_format + 1, &fixed_fmt_len, sizeof(fixed_fmt_len));
+			gg_message_html_to_text(tmp_msg, generated_format + 3, NULL, (const char*) html_message, sess->encoding);
+
+			format = generated_format;
+			formatlen = fmt_len + 3;
+		} else {
+			gg_message_html_to_text(tmp_msg, NULL, NULL, (const char*) html_message, sess->encoding);
+
+			format = NULL;
+			formatlen = 0;
+		}
+
+		if (sess->encoding == GG_ENCODING_UTF8) {
+			cp_msg = recoded_msg = gg_encoding_convert(tmp_msg, sess->encoding, GG_ENCODING_CP1250, -1, -1);
+			free(tmp_msg);
+
+			if (cp_msg == NULL)
+				goto cleanup;
+		} else {
+			cp_msg = recoded_msg = tmp_msg;
+		}
+	} else {
+		if (sess->encoding == GG_ENCODING_UTF8) {
+			cp_msg = recoded_msg = gg_encoding_convert((const char*) message, sess->encoding, GG_ENCODING_CP1250, -1, -1);
+
+			if (cp_msg == NULL)
+				goto cleanup;
+		} else {
+			cp_msg = (const char*) message;
+		}
+	}
+
+	if (html_message == NULL) {
+		size_t len;
+		char *tmp;
+		const char *utf_msg;
+		const unsigned char *format_ = NULL;
+		size_t formatlen_ = 0;
+
+		if (sess->encoding == GG_ENCODING_UTF8) {
+			utf_msg = (const char*) message;
+		} else {
+			utf_msg = recoded_msg = gg_encoding_convert((const char*) message, sess->encoding, GG_ENCODING_UTF8, -1, -1);
+
+			if (utf_msg == NULL)
+				goto cleanup;
+		}
+
+		if (format != NULL && formatlen >= 3) {
+			format_ = format + 3;
+			formatlen_ = formatlen - 3;
+		}
+
+		len = gg_message_text_to_html(NULL, utf_msg, GG_ENCODING_UTF8, format_, formatlen_);
+
+		tmp = malloc(len + 1);
+
+		if (tmp == NULL)
+			goto cleanup;
+
+		gg_message_text_to_html(tmp, utf_msg, GG_ENCODING_UTF8, format_, formatlen_);
+
+		utf_html_msg = recoded_html_msg = tmp;
+	} else {
+		if (sess->encoding == GG_ENCODING_UTF8) {
+			utf_html_msg = (const char*) html_message;
+		} else {
+			utf_html_msg = recoded_html_msg = gg_encoding_convert((const char*) html_message, sess->encoding, GG_ENCODING_UTF8, -1, -1);
+
+			if (utf_html_msg == NULL)
+				goto cleanup;
+		}
+	}
+
+	/* Drobne odchylenie od protokołu. Jeśli wysyłamy kilka
+	 * wiadomości w ciągu jednej sekundy, zwiększamy poprzednią
+	 * wartość, żeby każda wiadomość miała unikalny numer.
+	 */
+
+	seq_no = time(NULL);
+
+	if (seq_no <= sess->seq)
+		seq_no = sess->seq + 1;
+
+	sess->seq = seq_no;
+
+	s80.seq = gg_fix32(seq_no);
+	s80.msgclass = gg_fix32(msgclass);
+	s80.offset_plain = gg_fix32(sizeof(s80) + strlen(utf_html_msg) + 1);
+	s80.offset_attr = gg_fix32(sizeof(s80) + strlen(utf_html_msg) + 1 + strlen(cp_msg) + 1);
+
+	if (recipients_count > 1) {
+		struct gg_msg_recipients r;
+		int i, j, k;
+		uin_t *recps;
+
+		r.flag = GG_MSG_OPTION_CONFERENCE;
+		r.count = gg_fix32(recipients_count - 1);
+
+		recps = malloc(sizeof(uin_t) * (recipients_count - 1));
+
+		if (!recps) {
+			seq_no = -1;
+			goto cleanup;
+		}
+
+		for (i = 0; i < recipients_count; i++) {
+			for (j = 0, k = 0; j < recipients_count; j++) {
+				if (j != i) {
+					recps[k] = gg_fix32(recipients[j]);
+					k++;
+				}
+			}
+
+			s80.recipient = gg_fix32(recipients[i]);
+
+			if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), utf_html_msg, strlen(utf_html_msg) + 1, cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1)
+				seq_no = -1;
+		}
+
+		free(recps);
+	} else {
+		s80.recipient = gg_fix32(recipients[0]);
+
+		if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), utf_html_msg, strlen(utf_html_msg) + 1, cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1)
+			seq_no = -1;
+	}
+
+cleanup:
+	free(recoded_msg);
+	free(recoded_html_msg);
+	free(generated_format);
+
+	if (seq_no >= 0)
+		gg_compat_message_sent(sess, seq_no, recipients_count, recipients);
+
+	return seq_no;
+}
+
+#endif /* DOXYGEN */
+
 /**
  * Wysyła wiadomość do użytkownika.
  *
@@ -1397,7 +1716,7 @@
 {
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message);
 
-	return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, NULL, 0);
+	return gg_send_message_common(sess, msgclass, 1, &recipient, message, (const unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00", 9, NULL);
 }
 
 /**
@@ -1421,7 +1740,29 @@
 {
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen);
 
-	return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, format, formatlen);
+	return gg_send_message_common(sess, msgclass, 1, &recipient, message, format, formatlen, NULL);
+}
+
+/**
+ * Wysyła formatowaną wiadomość HTML.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipient Numer adresata
+ * \param html_message Treść wiadomości HTML
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message_html(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *html_message)
+{
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_html(%p, %d, %u, %p);\n", sess, msgclass, recipient, html_message);
+
+	return gg_send_message_common(sess, msgclass, 1, &recipient, NULL, NULL, 0, html_message);
 }
 
 /**
@@ -1444,7 +1785,7 @@
 {
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message);
 
-	return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0);
+	return gg_send_message_common(sess, msgclass, recipients_count, recipients, message, (const unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00", 9, NULL);
 }
 
 /**
@@ -1462,153 +1803,37 @@
  * \param formatlen Długość informacji o formatowaniu
  *
  * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
- * 
+ *
  * \ingroup messages
  */
 int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen)
 {
-	struct gg_send_msg s;
-	struct gg_send_msg80 s80;
-	struct gg_msg_recipients r;
-	char *cp_msg = NULL;
-	char *utf_msg = NULL;
-	char *html_msg = NULL;
-	int seq_no;
-	int i, j, k;
-	uin_t *recps;
-
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen);
 
-	if (!sess) {
-		errno = EFAULT;
-		return -1;
-	}
-
-	if (sess->state != GG_STATE_CONNECTED) {
-		errno = ENOTCONN;
-		return -1;
-	}
-
-	if (message == NULL || recipients_count <= 0 || recipients_count > 0xffff || (recipients_count != 1 && recipients == NULL)) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	if (sess->encoding == GG_ENCODING_UTF8) {
-		if (!(cp_msg = gg_encoding_convert((const char *) message, GG_ENCODING_UTF8, GG_ENCODING_CP1250, -1, -1)))
-			return -1;
-
-		utf_msg = (char*) message;
-	} else {
-		if (sess->protocol_version >= 0x2d) {
-			if (!(utf_msg = gg_encoding_convert((const char *) message, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1)))
-				return -1;
-		}
-
-		cp_msg = (char*) message;
-	}
-
-	if (sess->protocol_version < 0x2d) {
-		if (!sess->seq)
-			sess->seq = 0x01740000 | (rand() & 0xffff);
-		seq_no = sess->seq;
-		sess->seq += (rand() % 0x300) + 0x300;
-
-		s.msgclass = gg_fix32(msgclass);
-		s.seq = gg_fix32(seq_no);
-	} else {
-		int len;
-		
-		// Drobne odchylenie od protokołu. Jeśli wysyłamy kilka
-		// wiadomości w ciągu jednej sekundy, zwiększamy poprzednią
-		// wartość, żeby każda wiadomość miała unikalny numer.
-
-		seq_no = time(NULL);
-
-		if (seq_no <= sess->seq)
-			seq_no = sess->seq + 1;
-
-		sess->seq = seq_no;
-
-		if (format == NULL || formatlen < 3) {
-			format = (unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00";
-			formatlen = 9;
-		}
-
-		len = gg_message_text_to_html(NULL, utf_msg, (char*) format + 3, formatlen - 3);
-
-		html_msg = malloc(len + 1);
-
-		if (html_msg == NULL) {
-			seq_no = -1;
-			goto cleanup;
-		}
-
-		gg_message_text_to_html(html_msg, utf_msg, (char*) format + 3, formatlen - 3);
-
-		s80.seq = gg_fix32(seq_no);
-		s80.msgclass = gg_fix32(msgclass);
-		s80.offset_plain = gg_fix32(sizeof(s80) + strlen(html_msg) + 1);
-		s80.offset_attr = gg_fix32(sizeof(s80) + strlen(html_msg) + 1 + strlen(cp_msg) + 1);
-	}
-
-	if (recipients_count > 1) {
-		r.flag = 0x01;
-		r.count = gg_fix32(recipients_count - 1);
-
-		recps = malloc(sizeof(uin_t) * recipients_count);
-
-		if (!recps) {
-			seq_no = -1;
-			goto cleanup;
-		}
-
-		for (i = 0; i < recipients_count; i++) {
-			for (j = 0, k = 0; j < recipients_count; j++) {
-				if (recipients[j] != recipients[i]) {
-					recps[k] = gg_fix32(recipients[j]);
-					k++;
-				}
-			}
-
-			if (sess->protocol_version < 0x2d) {
-				s.recipient = gg_fix32(recipients[i]);
-
-				if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1)
-					seq_no = -1;
-			} else {
-				s80.recipient = gg_fix32(recipients[i]);
-
-				if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1)
-					seq_no = -1;
-			}
-		}
-
-		free(recps);
-	} else {
-		if (sess->protocol_version < 0x2d) {
-			s.recipient = gg_fix32(recipients[0]);
-
-			if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1)
-				seq_no = -1;
-		} else {
-			s80.recipient = gg_fix32(recipients[0]);
-
-			if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1)
-				seq_no = -1;
-		}
-	}
-
-cleanup:
-	if (cp_msg != (char*) message)
-		free(cp_msg);
-
-	if (utf_msg != (char*) message)
-		free(utf_msg);
-
-	free(html_msg);
-
-	return seq_no;
+	return gg_send_message_common(sess, msgclass, recipients_count, recipients, message, format, formatlen, NULL);
+}
+
+/**
+ * Wysyła formatowaną wiadomość HTML w ramach konferencji.
+ *
+ * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
+ * do potwierdzenia.
+ *
+ * \param sess Struktura sesji
+ * \param msgclass Klasa wiadomości
+ * \param recipients_count Liczba adresatów
+ * \param recipients Wskaźnik do tablicy z numerami adresatów
+ * \param html_message Treść wiadomości HTML
+ *
+ * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
+ *
+ * \ingroup messages
+ */
+int gg_send_message_confer_html(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *html_message)
+{
+	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_html(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, html_message);
+
+	return gg_send_message_common(sess, msgclass, recipients_count, recipients, NULL, NULL, 0, html_message);
 }
 
 /**
@@ -1696,7 +1921,7 @@
 	s.seq = gg_fix32(0);
 	s.msgclass = gg_fix32(GG_CLASS_MSG);
 
-	r.flag = 0x04;
+	r.flag = GG_MSG_OPTION_IMAGE_REQUEST;
 	r.size = gg_fix32(size);
 	r.crc32 = gg_fix32(crc32);
 
@@ -1795,23 +2020,23 @@
 	buf[0] = 0;
 	r = (void*) &buf[1];
 
-	r->flag = 0x05;
+	r->flag = GG_MSG_OPTION_IMAGE_REPLY;
 	r->size = gg_fix32(size);
-	r->crc32 = gg_fix32(gg_crc32(0, (unsigned char*) image, size));
+	r->crc32 = gg_fix32(gg_crc32(0, (const unsigned char*) image, size));
 
 	while (size > 0) {
-		int buflen, chunklen;
+		size_t buflen, chunklen;
 
 		/* \0 + struct gg_msg_image_reply */
 		buflen = sizeof(struct gg_msg_image_reply) + 1;
 
 		/* w pierwszym kawałku jest nazwa pliku */
-		if (r->flag == 0x05) {
+		if (r->flag == GG_MSG_OPTION_IMAGE_REPLY) {
 			strcpy(buf + buflen, filename);
 			buflen += strlen(filename) + 1;
 		}
 
-		chunklen = (size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : size;
+		chunklen = ((size_t) size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : (size_t) size;
 
 		memcpy(buf + buflen, image, chunklen);
 		size -= chunklen;
@@ -1822,12 +2047,48 @@
 		if (res == -1)
 			break;
 
-		r->flag = 0x06;
+		r->flag = GG_MSG_OPTION_IMAGE_REPLY_MORE;
 	}
 
 	return res;
 }
 
+static int gg_notify105_ex(struct gg_session *sess, uin_t *userlist, char *types, int count)
+{
+	int i = 0;
+
+	if (!userlist || !count)
+		return gg_send_packet(sess, GG_NOTIFY105_LIST_EMPTY, NULL);
+
+	while (i < count) {
+		gg_tvbuilder_t *tvb = gg_tvbuilder_new(sess, NULL);
+		gg_tvbuilder_expected_size(tvb, 2100);
+
+		while (i < count) {
+			size_t prev_size = gg_tvbuilder_get_size(tvb);
+			gg_tvbuilder_write_uin(tvb, userlist[i]);
+			gg_tvbuilder_write_uint8(tvb,
+				(types == NULL) ? GG_USER_NORMAL : types[i]);
+
+			/* Oryginalny klient wysyła maksymalnie 2048 bajtów 
+			 * danych w każdym pakiecie tego typu.
+			 */
+			if (gg_tvbuilder_get_size(tvb) > 2048) {
+				gg_tvbuilder_strip(tvb, prev_size);
+				break;
+			}
+			i++;
+		}
+
+		if (!gg_tvbuilder_send(tvb, (i < count) ?
+			GG_NOTIFY105_FIRST : GG_NOTIFY105_LAST)) {
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
 /**
  * Wysyła do serwera listę kontaktów.
  *
@@ -1841,7 +2102,7 @@
  *
  * \param sess Struktura sesji
  * \param userlist Wskaźnik do tablicy numerów kontaktów
- * \param types Wskaźnik do tablicy rodzajów kontaktów
+ * \param types    Wskaźnik do tablicy rodzajów kontaktów. Jeżeli NULL, wszystkie kontakty są typu GG_USER_NORMAL.
  * \param count Liczba kontaktów
  *
  * \return 0 jeśli się powiodło, -1 w przypadku błędu
@@ -1867,6 +2128,9 @@
 		return -1;
 	}
 
+	if (sess->protocol_version >= GG_PROTOCOL_VERSION_110)
+		return gg_notify105_ex(sess, userlist, types, count);
+
 	if (!userlist || !count)
 		return gg_send_packet(sess, GG_LIST_EMPTY, NULL);
 
@@ -1886,7 +2150,10 @@
 
 		for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) {
 			n[i].uin = gg_fix32(*u);
-			n[i].dunno1 = *t;
+			if (types == NULL)
+				n[i].dunno1 = GG_USER_NORMAL;
+			else
+				n[i].dunno1 = *t;
 		}
 
 		if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) {
@@ -1897,7 +2164,8 @@
 
 		count -= part_count;
 		userlist += part_count;
-		types += part_count;
+		if (types != NULL)
+			types += part_count;
 
 		free(n);
 	}
@@ -1921,57 +2189,7 @@
  */
 int gg_notify(struct gg_session *sess, uin_t *userlist, int count)
 {
-	struct gg_notify *n;
-	uin_t *u;
-	int i, res = 0;
-
-	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count);
-
-	if (!sess) {
-		errno = EFAULT;
-		return -1;
-	}
-
-	if (sess->state != GG_STATE_CONNECTED) {
-		errno = ENOTCONN;
-		return -1;
-	}
-
-	if (!userlist || !count)
-		return gg_send_packet(sess, GG_LIST_EMPTY, NULL);
-
-	while (count > 0) {
-		int part_count, packet_type;
-
-		if (count > 400) {
-			part_count = 400;
-			packet_type = GG_NOTIFY_FIRST;
-		} else {
-			part_count = count;
-			packet_type = GG_NOTIFY_LAST;
-		}
-
-		if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count)))
-			return -1;
-
-		for (u = userlist, i = 0; i < part_count; u++, i++) {
-			n[i].uin = gg_fix32(*u);
-			n[i].dunno1 = GG_USER_NORMAL;
-		}
-
-		if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) {
-			res = -1;
-			free(n);
-			break;
-		}
-
-		free(n);
-
-		userlist += part_count;
-		count -= part_count;
-	}
-
-	return res;
+	return gg_notify_ex(sess, userlist, NULL, count);
 }
 
 /**
@@ -1991,8 +2209,6 @@
  */
 int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type)
 {
-	struct gg_add_remove a;
-
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type);
 
 	if (!sess) {
@@ -2005,10 +2221,24 @@
 		return -1;
 	}
 
-	a.uin = gg_fix32(uin);
-	a.dunno1 = type;
-
-	return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL);
+	if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) {
+		gg_tvbuilder_t *tvb = gg_tvbuilder_new(sess, NULL);
+		gg_tvbuilder_expected_size(tvb, 16);
+
+		gg_tvbuilder_write_uin(tvb, uin);
+		gg_tvbuilder_write_uint8(tvb, type);
+
+		if (!gg_tvbuilder_send(tvb, GG_ADD_NOTIFY105))
+			return -1;
+		return 0;
+	} else {
+		struct gg_add_remove a;
+
+		a.uin = gg_fix32(uin);
+		a.dunno1 = type;
+
+		return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL);
+	}
 }
 
 /**
@@ -2044,8 +2274,6 @@
  */
 int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type)
 {
-	struct gg_add_remove a;
-
 	gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type);
 
 	if (!sess) {
@@ -2058,10 +2286,24 @@
 		return -1;
 	}
 
-	a.uin = gg_fix32(uin);
-	a.dunno1 = type;
-
-	return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL);
+	if (sess->protocol_version >= GG_PROTOCOL_VERSION_110) {
+		gg_tvbuilder_t *tvb = gg_tvbuilder_new(sess, NULL);
+		gg_tvbuilder_expected_size(tvb, 16);
+
+		gg_tvbuilder_write_uin(tvb, uin);
+		gg_tvbuilder_write_uint8(tvb, type);
+
+		if (!gg_tvbuilder_send(tvb, GG_REMOVE_NOTIFY105))
+			return -1;
+		return 0;
+	} else {
+		struct gg_add_remove a;
+
+		a.uin = gg_fix32(uin);
+		a.dunno1 = type;
+
+		return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL);
+	}
 }
 
 /**
@@ -2197,7 +2439,7 @@
 	zrequest = gg_deflate(request, &zrequest_len);
 
 	if (zrequest == NULL) {
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_userlist100_request() gg_deflate() failed");
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_userlist100_request() gg_deflate() failed\n");
 		return -1;
 	}
 
@@ -2249,6 +2491,145 @@
 	return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL);
 }
 
+/**
+ * Tworzy nową konferencję (11.0).
+ *
+ * \param gs Struktura sesji
+ *
+ * \return Numer sekwencyjny (ten sam, co w \c gg_event_chat_created), lub -1
+ *         w przypadku błędu
+ *
+ * \ingroup chat
+ */
+int gg_chat_create(struct gg_session *gs)
+{
+	struct gg_chat_create pkt;
+	int seq;
+
+	if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110))
+		return -1;
+
+	seq = ++gs->seq;
+
+	pkt.seq = gg_fix32(seq);
+	pkt.dummy = 0;
+
+	if (gg_send_packet(gs, GG_CHAT_CREATE, &pkt, sizeof(pkt), NULL) == -1)
+		return -1;
+
+	return seq;
+}
+
+/**
+ * Zaprasza nowych użytkowników do konferencji (11.0).
+ *
+ * \param gs                 Struktura sesji
+ * \param id                 Identyfikator konferencji
+ * \param participants       Lista użytkowników do zaproszenia
+ * \param participants_count Liczba użytkowników
+ *
+ * \return Numer sekwencyjny w przypadku powodzenia (ten sam, co w
+ *         \c gg_event_chat_invite_ack), lub -1 w przypadku błędu
+ *
+ * \ingroup chat
+ */
+int gg_chat_invite(struct gg_session *gs, uint64_t id, uin_t *participants,
+	unsigned int participants_count)
+{
+	struct gg_chat_invite pkt;
+	int seq, ret;
+	unsigned int i;
+	struct gg_chat_participant
+	{
+		uint32_t uin;
+		uint32_t dummy;
+	} GG_PACKED;
+	struct gg_chat_participant *participants_list;
+	size_t participants_list_size;
+
+	if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110))
+		return -1;
+
+	if (participants_count == 0 || participants_count >= ~0 / sizeof(struct gg_chat_participant))
+		return -1;
+
+	participants_list_size = sizeof(struct gg_chat_participant) *
+		participants_count;
+	participants_list = malloc(participants_list_size);
+	if (participants_list == NULL)
+		return -1;
+
+	seq = ++gs->seq;
+	pkt.id = gg_fix64(id);
+	pkt.seq = gg_fix32(seq);
+	pkt.participants_count = gg_fix32(participants_count);
+
+	for (i = 0; i < participants_count; i++)
+	{
+		participants_list[i].uin = gg_fix32(participants[i]);
+		participants_list[i].dummy = gg_fix32(0x1e);
+	}
+
+	ret = gg_send_packet(gs, GG_CHAT_INVITE,
+		&pkt, sizeof(pkt),
+		participants_list, participants_list_size,
+		NULL);
+	free(participants_list);
+
+	if (ret == -1)
+		return -1;
+	return seq;
+}
+
+/**
+ * Opuszcza konferencję (11.0).
+ *
+ * \param gs Struktura sesji
+ * \param id Identyfikator konferencji
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup chat
+ */
+int gg_chat_leave(struct gg_session *gs, uint64_t id)
+{
+	struct gg_chat_leave pkt;
+	int seq;
+
+	if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110))
+		return -1;
+
+	seq = ++gs->seq;
+	pkt.id = gg_fix64(id);
+	pkt.seq = gg_fix32(seq);
+
+	if (gg_send_packet(gs, GG_CHAT_LEAVE, &pkt, sizeof(pkt), NULL) == -1)
+		return -1;
+
+	return seq;
+}
+
+/**
+ * Wysyła wiadomość w ramach konferencji (11.0).
+ *
+ * \param gs      Struktura sesji
+ * \param id      Identyfikator konferencji
+ * \param message Wiadomość
+ * \param is_html 1, jeżeli wiadomość jest zapisana jako HTML, 0 w p.p.
+ *
+ * \return Numer sekwencyjny (taki sam, jak w \c gg_event_chat_send_msg_ack)
+ *         jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup chat
+ */
+int gg_chat_send_message(struct gg_session *gs, uint64_t id, const char *message, int is_html)
+{
+	if (!gg_required_proto(gs, GG_PROTOCOL_VERSION_110))
+		return -1;
+
+	return gg_send_message_110(gs, 0, id, message, is_html);
+}
+
 /* @} */
 
 /**
@@ -2292,6 +2673,213 @@
 	return 0;
 }
 
+static void gg_socket_manager_error(struct gg_session *sess, enum gg_failure_t failure)
+{
+	int pipes[2];
+	uint8_t dummy = 0;
+	struct gg_session_private *p = sess->private_data;
+
+	p->socket_failure = failure;
+
+	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, pipes) == -1) {
+		gg_debug(GG_DEBUG_MISC, "// gg_socket_manager_error() unable to"
+			" create pipes (errno=%d, %s)\n", errno,
+			strerror(errno));
+		return;
+	}
+
+	p->socket_is_external = 0;
+	sess->fd = pipes[1];
+	sess->check = GG_CHECK_READ;
+	sess->state = GG_STATE_ERROR;
+	send(pipes[0], &dummy, sizeof(dummy), 0);
+	close(pipes[0]);
+}
+
+int gg_compat_feature_is_enabled(struct gg_session *sess, gg_compat_feature_t feature)
+{
+	gg_compat_t level;
+
+	if (sess == NULL)
+		return 0;
+
+	level = sess->private_data->compatibility;
+
+	switch (feature) {
+		case GG_COMPAT_FEATURE_ACK_EVENT:
+		case GG_COMPAT_FEATURE_LEGACY_CONFER:
+			return (level < GG_COMPAT_1_12_0);
+	}
+
+	return 0;
+}
+
+static gg_msg_list_t * gg_compat_find_sent_message(struct gg_session *sess, int seq, int remove)
+{
+	struct gg_session_private *p = sess->private_data;
+	gg_msg_list_t *it, *previous = NULL;
+
+	for (it = p->sent_messages; it; it = it->next) {
+		if (it->seq == seq)
+			break;
+		else
+			previous = it;
+	}
+
+	if (remove && it) {
+		if (previous == NULL)
+			p->sent_messages = it->next;
+		else
+			previous->next = it->next;
+	}
+
+	return it;
+}
+
+static void gg_compat_message_cleanup(struct gg_session *sess)
+{
+	struct gg_session_private *p = sess->private_data;
+
+	while (p->sent_messages) {
+		gg_msg_list_t *next = p->sent_messages->next;
+		free(p->sent_messages->recipients);
+		free(p->sent_messages);
+		p->sent_messages = next;
+	}
+}
+
+static void gg_compat_message_sent(struct gg_session *sess, int seq, size_t recipients_count, uin_t *recipients)
+{
+	struct gg_session_private *p = sess->private_data;
+	gg_msg_list_t *sm;
+	uin_t *new_recipients;
+	size_t old_count, i;
+
+	if (sess->protocol_version < GG_PROTOCOL_VERSION_110)
+		return;
+
+	if (!gg_compat_feature_is_enabled(sess, GG_COMPAT_FEATURE_ACK_EVENT))
+		return;
+
+	sm = gg_compat_find_sent_message(sess, seq, 0);
+	if (!sm) {
+		sm = gg_new0(sizeof(gg_msg_list_t));
+		if (!sm)
+			return;
+		sm->next = p->sent_messages;
+		p->sent_messages = sm;
+	}
+
+	sm->seq = seq;
+	old_count = sm->recipients_count;
+	sm->recipients_count += recipients_count;
+
+	new_recipients = realloc(sm->recipients, sizeof(uin_t) * sm->recipients_count);
+	if (new_recipients == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_compat_message_sent() not enough memory\n");
+		return;
+	}
+	sm->recipients = new_recipients;
+
+	for (i = 0; i < recipients_count; i++)
+		sm->recipients[old_count + i] = recipients[i];
+}
+
+void gg_compat_message_ack(struct gg_session *sess, int seq)
+{
+	gg_msg_list_t *sm;
+	size_t i;
+
+	if (sess->protocol_version < GG_PROTOCOL_VERSION_110)
+		return;
+
+	if (!gg_compat_feature_is_enabled(sess, GG_COMPAT_FEATURE_ACK_EVENT))
+		return;
+
+	sm = gg_compat_find_sent_message(sess, seq, 1);
+	if (!sm)
+		return;
+
+	for (i = 0; i < sm->recipients_count; i++) {
+		struct gg_event *qev;
+
+		qev = gg_eventqueue_add(sess);
+
+		qev->type = GG_EVENT_ACK;
+		qev->event.ack.status = GG_ACK_DELIVERED;
+		qev->event.ack.recipient = sm->recipients[i];
+		qev->event.ack.seq = seq;
+	}
+
+	free(sm->recipients);
+	free(sm);
+}
+
+/**
+ * Odbiera nowo utworzone gniazdo TCP/TLS.
+ *
+ * Po wywołaniu tej funkcji należy zacząć obserwować deskryptor sesji (nawet
+ * w przypadku niepowodzenia).
+ *
+ * Jeżeli gniazdo nie zostanie obsłużone, należy je zniszczyć.
+ *
+ * \param handle Uchwyt gniazda
+ * \param priv Dane prywatne biblioteki libgadu
+ * \param fd Deskryptor nowo utworzonego gniazda, lub -1 w przypadku błędu
+ *
+ * \return Wartość różna od zera, jeżeli gniazdo zostało obsłużone, 0 w przeciwnym przypadku
+ *
+ * \ingroup socketmanager
+ */
+int gg_socket_manager_connected(void *handle, void *priv, int fd)
+{
+	struct gg_session *sess = priv;
+	struct gg_session_private *p = sess->private_data;
+
+	if (p->socket_handle != handle) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_socket_manager_connected() invalid handle\n");
+		return 0;
+	}
+
+	sess->fd = -1;
+
+	if (fd < 0) {
+		gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+			"// gg_socket_manager_connected() connection error\n");
+		p->socket_handle = NULL;
+		gg_socket_manager_error(sess, GG_FAILURE_CONNECTING);
+		return 0;
+	}
+
+	if (p->socket_next_state == GG_STATE_TLS_NEGOTIATION) {
+		if (gg_session_init_ssl(sess) == -1) {
+			gg_debug_session(sess, GG_DEBUG_MISC | GG_DEBUG_ERROR,
+				"// gg_socket_manager_connected() couldn't "
+				"initialize ssl\n");
+			p->socket_handle = NULL;
+			gg_socket_manager_error(sess, GG_FAILURE_TLS);
+			return 0;
+		}
+	}
+
+	p->socket_is_external = 1;
+	sess->fd = fd;
+	sess->timeout = GG_DEFAULT_TIMEOUT;
+	sess->state = p->socket_next_state;
+
+	gg_debug_session(sess, GG_DEBUG_MISC, "// next state=%s\n",
+		gg_debug_state(p->socket_next_state));
+
+	if (p->socket_next_state == GG_STATE_READING_KEY)
+		sess->check = GG_CHECK_READ;
+	else
+		sess->check = GG_CHECK_WRITE;
+
+	return 1;
+}
+
 /*
  * Local variables:
  * c-indentation-style: k&r

mercurial