---------------------------------------------------------------------------

                         protokół g*du-g*du 4.x
        (c) copyright 2001 by wojtek kaniewski <wojtekka@irc.pl>

--- 0) disclaimer ---------------------------------------------------------

wszystkie informacje bazują na doświadczeniach przeprowadzonych na moim
domowym komputerze. żaden klient g*du-g*du nie został skrzywdzony podczas
przeprowadzania badań, blabla.

--- 1) transmisja, format wszystkich pakietów -----------------------------

w przeciwieństwie do zabawek typu icq, g*du-g*du korzysta z protokołu tcp.
każdy pakiet zawiera dwa stałe pola:

        struct gg_header {
		int type;		/* typ pakietu */
		int length;		/* długość reszty pakietu */
	};

dla ułatwienia przyjmuję następujące długości zmiennych: sizeof(char) = 1,
sizeof(short) = 2, sizeof(int) = 4. oczywiście wszystkie liczby są zgodnie
z intelowym endianem. zakładam też, że wszystkie zmienne są bez znaku. nie
chce mi się wszędzie pisać `unsigned'.

pola, co do których znaczenia nie mam pewności, lub w ogóle nie mam pojęcia,
skąd się tam wzięły, oznaczam `dunno'.

--- 2) zanim się połączymy -------------------------------------------------

żeby wiedzieć, z jakim serwerem mamy się połączyć, należy poudawać przez
chwilę Internet Explorera, połączyć się z hostem `appmsg.gadu-gadu.pl'.

        GET /appsvc/appmsg.asp?fmnumber=<tutaj_numerek_gg> HTTP/1.0
	Host: appmsg.gadu-gadu.pl
	User-Agent: Mozilla/4.7 [en] (Win98; I)
	Pragma: no-cache

na co powinniśmy dostać odpowiedź w stylu:

	HTTP/1.0 200 OK
	
	0 1 0 217.17.33.21:8074 217.17.33.21 217.17.33.21

co to oznacza? nie mam pojęcia ;) wygląda na to, że cały g*du-g*du jest
przemyślany i w przyszłości będzie można używać różnych serwerów do różnych
rzeczy, typu szukanie, obsługa klientów itd. póki co, łączyć się trzeba na
pierwszy adres (tak, ten z portem).

--- 3) logowanie się -------------------------------------------------------

po połączeniu się portem 8074 serwera g*du-g*du, dostajemy pakiet typu 0x0001,
który na potrzeby tego dokumentu nazwiemy:

	#define GG_WELCOME 0x0001

reszta pakietu zawiera liczbę, na podstawie której liczony jest hash z hasła
klienta:

	struct gg_welcome {
		int key;		/* klucz szyfrowania hasła */
	};
	
kiedy mamy już tą wartość możemy odesłać pakiet logowania

	#define GG_LOGIN 0x000c

musimy podać kilka informacji:

	struct gg_login {
		int uin;		/* twój numerek */
		int hash;		/* hash hasła */
		int status;		/* status na dzień dobry */
		int dunno1;		/* == 0x0b */
		int local_ip;		/* mój adres ip */
		short local_port;	/* port, na którym słucham */
	};

jak obliczyć hash hasła? hmm... nic prostszego. do każdej literki hasła
dodaje się jedynkę, mnoży wszystko razem, a potem przez liczbę podaną przez
serwer. 

	for (hash = 1; *passwd; passwd++)
		hash *= (*passwd) + 1;

zrozumiałe, racja? jeśli wszystko się powiedzie, dostaniemy w odpowiedzi
pakiet typu

	#define GG_LOGIN_OK 0x0003

z polem header->length = 0, lub pakiet
	
	#define GG_LOGIN_FAILED 0x0009

--- 4) zmiana statusu -----------------------------------------------------

g*du-g*du przewiduje trzy stany klienta, które zmieniamy pakietem

	#define GG_NEW_STATUS 0x0002

	#define GG_STATUS_NOT_AVAIL 0x0001	/* rozłączony */
	#define GG_STATUS_AVAIL 0x0002		/* dostępny */
	#define GG_STATUS_BUSY 0x0003		/* zajęty */
	#define GG_STATUS_INVISIBLE 0x0014	/* niewidoczny */

	#define GG_STATUS_FRIENDS_MASK 0x8000	/* tylko dla przyjaciół */
	
	struct gg_new_status {
		int status;			/* na jaki zmienić? */
	}

należy pamiętać, żeby przed rozłączeniem się z serwerem należy zmienić
stan na GG_STATUS_NOT_AVAIL. jeśli ma być widoczny tylko dla przyjaciół,
należy dodać GG_STATUS_FRIENDS do normalnej wartości stanu.

--- 5) ludzie przychodzą, ludzie odchodzą ---------------------------------

zaraz po zalogowaniu możemy wysłać serwerowi listę ludzików w naszej liście
kontaktów, żeby dowiedzieć się, czy są w tej chwili dostępni. pakiet zawiera
dowolną ilość struktur gg_notify:

	#define GG_NOTIFY 0x0010
	
	struct gg_notify {
		int uin;		/* numerek danej osoby */
		char dunno1;		/* == 3 */
	};
	
jeśli ktoś jest, serwer odpowie pakietem zawierającym jedną lub więcej
struktur gg_notify_reply:

	#define GG_NOTIFY_REPLY 0x000c	/* tak, to samo co GG_LOGIN */
	
	struct gg_notify_reply {
		int uin;		/* numerek */
		int status;		/* status danej osoby */
		int remote_ip;		/* adres ip delikwenta */
		short remote_port;	/* port, na którym słucha klient */
		int dunno1;		/* == 0x0b */
		short dunno2;		/* znowu port? */
	};

jeśli klient nie obsługuje połączeń między klientami (np. g*du-g*du 3.x)
zamiast adresu ip jest 0, zamiast portu jest 2. nieważne ;) w każdym razie,
jeśli ktoś się pojawi w trakcie pracy, również zostanie przysłany ten
pakiet. proste? proste :)

żeby dodać kogoś do listy w trakcie pracy, trzeba wysłać niżej opisany
pakiet. jego format jest identyczny jak przy GG_NOTIFY.

	#define GG_ADD 0x000d
	
	struct gg_add {
		int uin;		/* numerek */
		char dunno1;		/* == 3 */
	};

jeśli ktoś opuści g*du-g*du lub zmieni stan, otrzymamy pakiet

	#define GG_STATUS 0x0002
	
	struct gg_status {
		int uin;		/* numerek */
		int status;		/* nowy stan */
	};

--- 6) wysyłanie wiadomości ------------------------------------------------
	
przejdźmy do sedna sprawy ;)

	#define GG_SEND_MSG 0x000b

	#define GG_CLASS_MSG 0x0004
	#define GG_CLASS_CHAT 0x0008

	struct gg_send_msg {
		int recipient;
		int seq;
		int class;
		char message[];
	};

wiadomo, odbiorca. numer sekwencyjny, który wykorzystujemy potem do
potwierdzenia. nie wykluczone, że w jakis sposób odróżnia się różne
rozmowy za pomocą części bajtów, ale raczej nie ma znaczenia. klasa
wiadomości pozwala odróżnić, czy wiadomość ma się pokazać w osobym
okienku czy jako kolejna linijka w okienku rozmowy. wygląda na to,
że to jakaś bitmapa, więc najlepiej olać inne bity niż 0x0f. (czasem
klienty wysyłają 0x04, czasem 0x24)

serwer po otrzymaniu wiadomości odsyła informację o tym. przy okazji
mówi, czy wiadomość dotarła do odbiorcy (status == GG_ACK_DELIVERED),
czy może jest offline i została zakolejkowana (GG_ACK_QUEUED):

	#define GG_SEND_MSG_ACK 0x0005
	
	#define GG_ACK_DELIVERED 0x0002
	#define GG_ACK_QUEUED 0x0003

	struct gg_send_msg_ack {
		int status;
		int recipient;
		int seq;
	};

numer sekwencyjny i adresat ten sam, co przy wysyłaniu.

--- 7) otrzymywanie wiadomości ---------------------------------------------

zbyt wiele wyjaśnień chyba nie trzeba. wiadomo od kogo. nieznane pola to
coś a'la numer sekwencyjny albo identyfikator okienka z rozmową albo nowe
dane dla setiathome. klasa wiadomości taka sama jak przy wysyłaniu:

	#define GG_RECV_MSG 0x000a
	
	struct gg_recv_msg {
		int sender;
		int dunno1;
		int dunno2;
		int class;
		char message[];
	};

--- 8) otrzymywanie wiadomości ---------------------------------------------

od czasu do czasu klient wysyła pakiet a'la ping do serwera i dostaje pustą
odpowiedź. ciężko stwierdzić, czy serwer wywala, jeśli nie dostanie pinga
przez jakiś czas, czy klient się rozłącza, jeśli serwer mu nie odpowie.
jakoś nie chce mi się sprawdzać ;)

	#define GG_PING 0x0008
	
	/* nie ma niczego */
	
	#define GG_PONG 0x0007
	
	/* nie ma niczego */

--- 9) podziękowania -------------------------------------------------------

swój wkład w poznanie protokołu miał Robert Woźny, który opisał nowości
w GG 4.6.

----------------------------------------------------------------------------

$Id: protocol.txt 2406 2001-09-29 23:06:30Z warmenhoven $

