--------------------------------------------------------------------------- Protokół G*du-G*du 4.x (C) Copyright 2001 by Wojtek Kaniewski , Robert J. Woźny , Tomasz Jarzynka , Adam Ludwikowski , Marek Kozina , Rafał Florek , Igor Popik --- 0) disclaimer --------------------------------------------------------- opis protokołu bazują na doświadczeniach przeprowadzonych na moim domowym komputerze oraz informacjach przysłanych do mnie przez różnych ludzi. ż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= HTTP/1.0 Host: appmsg.gadu-gadu.pl User-Agent: Mozilla/4.7 [en] (Win98; I) Pragma: no-cache oryginalny klient może wysłać jeden z podanych identyfikatorów przeglądarki: Mozilla/4.04 [en] (Win95; I ;Nav) Mozilla/4.7 [en] (Win98; I) Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt) Mozilla/4.0 (compatible; MSIE 5.0; Windows NT) Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt) Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) nowsze wersje klienta do zapytania dodają również `version=...' opisujące, z jakim klientem serwer ma do czynienia. jednak ze względu na możliwe różnice w protokole, lepiej pomijać ten parametr i uwagać GG 4.0. w każdym razie na to zapytanie serwer powinien odpowiedzieć: 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). Jeżeli połączenie z portem 8074 nie wyjdzie z jakiś specyficznych powodów - można się łączyć na port 443. --- 3) logowanie się ------------------------------------------------------- po połączeniu się portem 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 version; /* wersja klienta */ 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? liczba oznaczająca wersję może być jedną z poniższych: 0x11 - 4.6.1 0x10 - 4.5.22, 4.5.21, 4.5.19, 4.5.17, 4.5.15 0x0f - 4.5.12 0x0b - 4.0.30, 4.0.29, 4.0.28, 4.0.25 oczywiście nie są to wszystkie możliwe wersje klientów, lecz te, które udało się sprawdzić. najbezpieczniej będzie przedstawiać się jako ta wersja, której ficzerów używamy. wiadomo, że 4.0.x nie obsługiwały trybu ukrytego, ani tylko dla znajomych itd. 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 version; /* wersja klienta */ short dunno1; /* 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 może być 0, 1, 2... nieważne ;) port może przyjmować wartość 1, jeśli klient znajduje się za jakimś firewallem lub innym urządzeniem robiącym NAT. 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_QUEUED 0x0001 /* tylko przy odbieraniu */ #define GG_CLASS_MSG 0x0004 #define GG_CLASS_CHAT 0x0008 #define GG_CLASS_UNKNOWN_1 0x0020 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ż 0x0e. (czasem klienty wysyłają 0x04, czasem 0x24 -- widocznie 0x20 to też jakaś flaga). jeśli odbiorca był niedostępny podczas wysyłania wiadomości, zostanie zaznaczony bit 0x01. oryginalny klient wysyłając wiadomość do kilku użytkowników, wysyła po prostu kilka takich samych pakietów z różnymi numerkami odbiorców. nie ma osobnego pakietu do tego. natomiast jeśli chodzi o ,,konferencyjnę'' do pakietu doklejana jest za ,,char message[];'' następująca struktura: struct gg_send_recipients { char flag; /* == 1 */ int count; /* ilość odbiorców */ int recipients[]; /* tablica odbiorców */ }; na przykład, by wysłać do trzech ludzi, należy wysłać pakiet: - -- --- --+--+--+--+--+--+--+-----------+-----------+ treść |\0|\1| 0x02 | uin1 | uin2 | - -- -- ---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ 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. drugie pole to najprawdopodobniej jakiś numerek sekwencyjny. trzecie oznacza czas nadania wiadomości. klasa wiadomości taka sama jak przy wysyłaniu: #define GG_RECV_MSG 0x000a struct gg_recv_msg { int sender; int seq; int time; int class; char message[]; }; w przypadku pakietów ,,konferencyjnych'' na koncu pakietu doklejona jest struktura identyczna ze struct gg_send_recipients zawierająca pozostałych rozmówców. --- 8) ping/pong ----------------------------------------------------------- od czasu do czasu klient wysyła pakiet a'la ping do serwera i dostaje pustą odpowiedź. o ile dobrze pamiętam, serwer rozłącza się po upływie 5 minut od otrzymania ostatniej informacji. #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 mieli: - Robert J. Woźny : opis nowości w protokole GG 4.6, - Tomasz Jarzynka : badanie timeoutów, - Adam Ludwikowski : wiele różnych poprawek do tekstu, badanie wersji, - Marek Kozina : czas otrzymania wiadomości, - Rafał Florek : konferencje, - Igor Popik : klasy wiadomości przy odbieraniu zakolejkowanej. ---------------------------------------------------------------------------- $Id: protocol.txt,v 1.2 2001/11/27 22:54:32 warmenhoven Exp $