Protokół Gadu-Gadu© Copyright 2001 - 2003 AutorzySpis treści
Informacje wstępneOpis protokołu używanego przez Gadu-Gadu bazuje na doświadczeniach przeprowadzonych przez autorów oraz informacjach nadsyłanych przez użytkowników. Żaden klient Gadu-Gadu nie został skrzywdzony podczas badań. Reverse-engineering opierał się głównie na analizie pakietów przesyłanych między klientem a serwerem. Najnowsza wersja opisu protokołu znajduje się pod adresem http://dev.null.pl/ekg/docs/protocol.html. 1. Protokół Gadu-Gadu1.1. Format pakietówPodobnie jak coraz większa ilość komunikatorów, Gadu-Gadu korzysta z protokołu TCP/IP. Każdy pakiet zawiera na początku dwa stałe pola: struct gg_header { int type; /* typ pakietu */ int length; /* długość reszty pakietu */ }; Wszystkie zmienne liczbowe są zgodne z kolejnością bajtów maszyn Intela, czyli Little-Endian. Przy opisie struktur, założono, że char ma rozmiar 1 bajtu, short 2 bajtów, int 4 bajtów. Używając innych architektur niż i386 należy zwrócić szczególną uwagę na rozmiar typów zmiennych i kolejność znaków. Pola, który znaczenie jest nieznane, lub nie do końca jasne, oznaczono przedrostkiem unknown. Możliwe jest połączenie za pośrednictwem protokołu TLSv1. Szczegóły znajdują się w poniższym opisie. 1.2. Zanim się połączymyŻeby wiedzieć, z jakim serwerem mamy się połączyć, należy za pomocą HTTP połączyć się z appmsg.gadu-gadu.pl i wysłać: GET /appsvc/appmsg4.asp?fmnumber=NUMER&version=WERSJA&fmt=FORMAT&lastmsg=WIADOMOŚĆ Accept: image/gif, image/jpeg, image/pjpeg, ... Accept-Language: pl User-Agent: PRZEGLĄDARKA Pragma: no-cache Host: appmsg.gadu-gadu.pl NUMER jest numerem Gadu-Gadu. WERSJA jest wersją klienta w postaci ,,A, B, C, D'' (na przykład ,,5, 0, 5, 107'' dla wersji 5.0.5 build 107). FORMAT określa czy wiadomość systemowa będzie przesyłana czystym tekstem (brak zmiennej "fmt") czy w HTMLu (wartość ,,2''). WIADOMOŚĆ jest numerem ostatnio otrzymanej wiadomości systemowej. PRZEGLĄDARKA może być jednym z poniższych tekstów:
Na postawione w ten sposób zapytanie, serwer powinien odpowiedzieć na przykład tak: HTTP/1.0 200 OK 0 0 217.17.41.84:8074 217.17.41.84 Pierwsze pole jest numerem wiadomości systemowej, a trzecie i czwarte podają nam namiary na właściwy serwer. Jeśli serwer jest niedostępny, zamiast adresu IP jest zwracany tekst ,,notoperating''. Jeżeli połączenie z portem 8074 nie powiedzie się z jakichś powodów, można się łączyć na port 443. Jeśli pierwsza liczba nie jest równa zero, zaraz po nagłówku znajduje się wiadomość systemowa, lub jeśli linia zaczyna się od znaku ,,@'', adres strony, którą należy otworzyć w przeglądarce. Jeśli klient chce się łączyć za pomocą protokołu TLSv1, wysyła zapytanie do innego skryptu (,,appmsg3.asp'') i otrzymuje w odpowiedzi adres serwera oraz port 443. Protokół jest identyczny, z tym wyjątkiem, że cała transmisja jest szyfrowana. Dobrym zwyczajem jest również sprawdzane autentyczności certyfikatu, by uniknąć ataków typu man-in-the-middle. GET /appsvc/appmsg3.asp?fmnumber=NUMER&version=WERSJA&fmt=FORMAT&lastmsg=WIADOMOŚĆ Host: appmsg.gadu-gadu.pl User-Agent: PRZEGLĄDARKA Pragma: no-cache 1.3. Logowanie sięPo połączeniu się portem 8074 lub 443 serwera Gadu-Gadu, otrzymujemy 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 seed; /* klucz szyfrowania hasła */ }; Kiedy mamy już tą wartość możemy odesłać pakiet logowania: #define GG_LOGIN60 0x0015 struct gg_login60 { int uin; /* mój numerek */ int hash; /* hash hasła */ int status; /* status na dzień dobry */ int version; /* moja wersja klienta */ char unknown1; /* 0x00 */ int local_ip; /* mój adres ip */ int local_port; /* port, na którym słucham */ int external_ip; /* zewnętrzny adres ip */ short external_port; /* zewnętrzny port */ char image_size; /* maksymalny rozmiar grafiki w KB */ char unknown2; /* 0xbe */ }; Hash hasła można obliczyć następującą funkcją języka C: int gg_login_hash(char *password, int seed) { unsigned int x, y, z; y = seed; for (x = 0; *password; password++) { x = (x & 0xffffff00) | *password; y ^= x; y += x; x <<= 8; y ^= x; x <<= 8; y -= x; x <<= 8; y ^= x; z = y & 0x1f; y = (y << z) | (y >> (32 - z)); } return y; } Liczba oznaczająca wersję może być jedną z poniższych:
Oczywiście nie są to wszystkie możliwe wersje klientów, lecz te, które zostały zauważone i odnotowane. W każdym razie, tak czy inaczej należy się przedstawić jako co najmniej wersja 6.0, ponieważ tej wersji protokołu dotyczy poniższy dokument. Jeśli klient ma kartę dźwiękową i jest w stanie obsługiwać rozmowy głosowe, do wersji dodawana jest wartość: #define GG_HAS_AUDIO_MASK 0x40000000 Jeśli autoryzacja się powiedzie, dostaniemy w odpowiedzi pakiet: #define GG_LOGIN_OK 0x0003 o zerowej długości, lub w przypadku błędu pakiet: #define GG_LOGIN_FAILED 0x0009 1.4. Zmiana stanuGadu-Gadu przewiduje kilka stanów klienta, które zmieniamy pakietem typu: #define GG_NEW_STATUS 0x0002 struct gg_new_status { int status; /* na jaki zmienić? */ char description[]; /* opis, nie musi wystąpić */ int time; /* czas, nie musi wystąpić */ } Możliwe stany to:
Należy pamiętać, żeby przed rozłączeniem się z serwerem należy zmienić stan na GG_STATUS_NOT_AVAIL lub GG_STATUS_NOT_AVAIL_DESCR. Jeśli ma być widoczny tylko dla przyjaciół, należy dodać GG_STATUS_FRIENDS_MASK do normalnej wartości stanu. Jeśli wybieramy stan opisowy, należy dołączyć ciąg znaków zakończony zerem oraz ewentualny czas powrotu w postaci ilości sekund od 1 stycznia 1970r (UTC). Maksymalna długość opisu wynosi 70 znaków plus zero plus 4 bajty na godzinę powrotu, co razem daje 75 bajtów. 1.5. Ludzie przychodzą, ludzie odchodząZaraz po zalogowaniu możemy wysłać serwerowi naszą listę kontaktów, żeby dowiedzieć się, czy są w danej chwili dostępni. Pakiet zawiera maksymalnie 409 struktur gg_notify: #define GG_NOTIFY 0x0010 struct gg_notify { int uin; /* numerek danej osoby */ char type; /* rodzaj użytkownika */ }; Gdzie pole type jest mapą bitową następujących wartości:
Jednak dla zachowania starego nazewnictwa stałych można używać najczęściej spotykane wartości to:
Jeśli nie mamy nikogo na liście wysyłamy pakiet: #define GG_LIST_EMPTY 0x0012 o zerowej długości. Jeśli ktoś jest, serwer odpowie pakietem GG_NOTIFY_REPLY albo GG_NOTIFY_REPLY60 zawierającym jedną lub więcej struktur gg_notify_reply60: #define GG_NOTIFY_REPLY 0x000c struct gg_notify_reply { int uin; /* numer */ 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 unknown1; /* znowu port? */ char description[]; /* opis, nie musi wystąpić */ int time; /* czas, nie musi wystąpić */ }; #define GG_NOTIFY_REPLY60 0x0011 struct gg_notify_reply60 { int uin; /* numerek plus flagi w najstarszym bajcie */ char status; /* status danej osoby */ int remote_ip; /* adres IP bezpośrednich połączeń */ short remote_port; /* port bezpośrednich połączeń */ char version; /* wersja klienta */ char image_size; /* maksymalny rozmiar obrazków w KB */ char unknown1; /* 0x00 */ char description_size; /* rozmiar opisu i czasu, nie musi wystąpić */ char description[]; /* opis, nie musi wystąpić */ int time; /* czas, nie musi wystąpić */ }; W najstarszym bajcie pola uin mogą znajdować się następujące flagi:
remote_port poza zwykłym portem może przyjmować również poniższe wartości:
Zdarzają się też inne ,,nietypowe'' wartości, ale ich znaczenie nie jest jeszcze do końca znane. Żeby dodać kogoś do listy w trakcie pracy, trzeba wysłać niżej opisany pakiet. Jego format jest identyczny jak przy GG_NOTIFY. Dodaje on flagi rodzaj użytkownika. #define GG_ADD_NOTIFY 0x000d struct gg_add_notify { int uin; /* numerek */ char type; /* rodzaj użytkownika */ }; Poniższy pakiet usuwa flagi rodzaj użytkownika, więc można go wykorzystać zarówno do usunięcia użytkownika z listy kontaktów, jak i do zmiany rodzaju. #define GG_REMOVE_NOTIFY 0x000e struct gg_remove_notify { int uin; /* numerek */ char type; /* rodzaj użytkownika */ }; Jeśli ktoś opuści Gadu-Gadu lub zmieni stan, otrzymamy jeden z pakietów: #define GG_STATUS 0x0002 struct gg_status { int uin; /* numer */ int status; /* nowy stan */ char description[]; /* opis, nie musi wystąpić */ int time; /* czas, nie musi wystąpić */ }; #define GG_STATUS60 0x000f struct gg_status60 { int uin; /* numer plus flagi w najstarszym bajcie */ char status; /* nowy stan */ int remote_ip; /* adres IP bezpośrednich połączeń */ short remote_port; /* port bezpośrednich połączeń */ char version; /* wersja klienta */ char image_size; /* maksymalny rozmiar grafiki */ char unknown1; /* 0x00 * char description[]; /* opis, nie musi wystąpić */ int time; /* czas, nie musi wystąpić */ }; Znaczenie pól jest takie samo jak w struct gg_notify_reply60. 1.6. Wysyłanie wiadomościWiadomości wysyła się następującym typem pakietu: #define GG_SEND_MSG 0x000b struct gg_send_msg { int recipient; /* numer odbiorcy */ int seq; /* numer sekwencyjny */ int class; /* klasa wiadomości */ char message[]; /* treść */ }; Numer sekwencyjny jest wykorzystywany przy potwierdzeniu dostarczenia lub zakolejkowania pakietu. Nie jest wykluczone, że w jakiś sposób odróżnia się różne rozmowy za pomocą części bajtów, ale raczej nie powinno mieć to ma znaczenia. Klasa wiadomości pozwala odróżnić, czy wiadomość ma się pojawić w osobnym okienku czy jako kolejna linijka w okienku rozmowy. Jest to mapa bitowa, więc najlepiej ignorować te bity, których znaczenia nie znamy:
Długość treści wiadomości nie powinna przekraczać 2000 znaków. Oryginalny klient zezwala na wysłanie do 1989 znaków. Oryginalny klient wysyłając wiadomość do kilku użytkowników, wysyła po kilka takich samych pakietów z różnymi numerkami odbiorców. Nie ma osobnego pakietu do tego. Natomiast jeśli chodzi o połączenia konferencyjne do pakietu doklejana jest następująca struktura: struct gg_msg_recipients { char flag; /* == 1 */ int count; /* ilość odbiorców */ int recipients[]; /* tablica odbiorców */ }; Na przykład, by wysłać do dwóch osób, należy wysłać pakiet:
Od wersji 4.8.1 możliwe jest również dodawanie do wiadomości różnych atrybutów tekstu jak pogrubienie czy kolory. Niezbędne jest dołączenie następującej struktury: struct gg_msg_richtext { char flag; /* == 2 */ short length; /* długość dalszej części */ }; Dalsza część pakietu zawiera odpowiednią ilość struktur o łącznej długości określonej polem length: struct gg_msg_richtext_format { short position; /* pozycja atrybutu w tekście */ char font; /* atrybuty czcionki */ char rgb[3]; /* kolor czcionki, nie musi wystąpić */ struct gg_msg_richtext_image image; /* nie musi wystąpić */ }; Każda z tych struktur określa kawałek tekstu począwszy od znaku określonego przez pole position (liczone od zera) aż do następnego wpisu lub końca tekstu. Pole font jest mapą bitową i kolejne bity mają następujące znaczenie:
Jeśli wiadomość zawiera obrazek, przesyłana jest jego suma kontrolna CRC32 i rozmiar. Dzięki temu nie trzeba za każdym razem wysyłać każdego obrazka -- klienty je zachowują. Struktura gg_msg_richtext_image opisująca obrazek umieszczony w wiadomości wygląda następująco: struct gg_msg_richtext_image { short unknown1; /* 0x0190 */ long size; /* rozmiar obrazka */ long crc32; /* suma kontrolna obrazka */ }; Gdy klient nie pamięta obrazka o podanych parametrach, wysyła pustą wiadomość o klasie GG_CLASS_MSG z dołączoną strukturą gg_msg_image_request: struct gg_msg_image_request { char flag; /* 0x04 */ int size; /* rozmiar */ int crc32; /* suma kontrolna */ }; Przykładowa treść wiadomości z prośbą o wysłanie obrazka o długości 258 bajtów i sumie kontrolnej 0x12345678 to: 00 04 02 01 00 00 78 56 34 12 W odpowiedzi, drugi klient wysyła obrazek za pomocą wiadomości o zerowej długości (należy pamiętać o kończącym bajcie o wartości 0x00) z dołączoną strukturą gg_msg_image_reply: struct gg_msg_image_reply { char flag; /* 0x05 lub 0x06 */ int size; /* rozmiar */ int crc32; /* suma kontrolna */ char filename[]; /* nazwa pliku, nie musi wystąpić */ char image[]; /* zawartość obrazka */ }; Jeśli długość struktury gg_msg_image_reply jest dłuższa niż 1909 bajtów, treść obrazka jest dzielona na kilka pakietów nie przeraczających 1909 bajtów. Pierwszy pakiet ma pole flag równe 0x05 i ma wypełnione pole filename, a w kolejnych pole flag jest równe 0x06 i pole filename w ogóle nie występuje (nawet bajt zakończenia ciągu znaków). Przykładowo, by przesłać tekst ,,ala ma kota'', należy dołączyć do wiadomości następującą sekwencję bajtów:
Serwer po otrzymaniu wiadomości odsyła potwierdzenie, które przy okazji mówi nam, czy wiadomość dotarła do odbiorcy czy została zakolejkowana z powodu nieobecności. Otrzymujemy je w postaci pakietu: #define GG_SEND_MSG_ACK 0x0005 struct gg_send_msg_ack { int status; /* stan wiadomości */ int recipient; /* numer odbiorcy */ int seq; /* numer sekwencyjny */ }; Numer sekwencyjny i numer adresata są takie same jak podczas wysyłania, a stan wiadomości może być jednym z następujących:
1.7. Otrzymywanie wiadomościWiadomości serwer przysyła za pomocą pakietu: #define GG_RECV_MSG 0x000a struct gg_recv_msg { int sender; /* numer nadawcy */ int seq; /* numer sekwencyjny */ int time; /* czas nadania */ int class; /* klasa wiadomości */ char message[]; /* treść wiadomości */ }; Czas nadania jest zapisany w postaci UTC, jako ilości sekund od 1 stycznie 1970r. W przypadku pakietów ,,konferencyjnych'' na końcu pakietu doklejona jest struktura identyczna z gg_msg_recipients zawierająca pozostałych rozmówców. 1.8. Ping, pongOd czasu do czasu klient wysyła pakiet do serwera, by oznajmić, że połączenie jeszcze jest utrzymywane. Jeśli serwer nie dostanie takiego pakietu w przeciągu 5 minut, zrywa połączenie. To, czy klient dostaje odpowiedź zmienia się z wersji na wersję, więc najlepiej nie polegać na tym. #define GG_PING 0x0008 #define GG_PONG 0x0007 1.9. RozłączenieJeśli serwer zechce nas rozłączyć, wyśle wcześniej pusty pakiet: #define GG_DISCONNECTING 0x000b Ma to miejsce, gdy próbowano zbyt wiele razy połączyć się z nieprawidłowym hasłem, lub gdy równocześnie połączy się drugi klient z tym samym numerem (nowe połączenie ma wyższy priorytet). 1.10. Katalog publicznyOd wersji 5.0.2 zmieniono sposób dostępu do katalogu publicznego -- stał się częścią sesji, zamiast osobnej sesji HTTP. Aby obsługiwać wyszukiwanie osób, odczytywanie własnych informacji lub ich modyfikację należy użyć następującego typu pakietu: #define GG_PUBDIR50_REQUEST 0x0014 struct gg_pubdir50 { char type; int seq; char request[]; }; Pole type oznacza rodzaj zapytania: #define GG_PUBDIR50_WRITE 0x01 #define GG_PUBDIR50_READ 0x02 #define GG_PUBDIR50_SEARCH 0x03 Pole seq jest numerem sekwencyjnym zapytania, zwracanym również w wyniku. Oryginalny klient tworzy go na podstawie aktualnego czasu. request zawiera parametry zapytania. Ilość jest dowolna. Każdy parametr jest postaci "nazwa\0wartość\0", tzn. nazwa od wartości są oddzielone znakiem o kodzie 0, podobnie jak kolejne parametry od siebie. Możliwe parametry zapytania to:
Treść przykładowego zapytania (pomijając pola type i seq) znajduje się poniżej. Szukano dostępnych kobiet o imieniu Ewa z Warszawy. Znaki o kodzie 0 zastąpiono kropkami. firstname.Ewa.city.Warszawa.gender.1.ActiveOnly.1. Wynik zapytania zostanie zwrócony za pomocą pakietu: #define GG_PUBDIR50_REPLY 0x000e struct gg_pubdir50_reply { char type; int seq; char reply[]; }; Pole type poza wartościami takimi jak przy pakiecie typu GG_PUBDIR50_REQUEST może przyjąć jeszcze wartość oznaczającą odpowiedź wyszukiwania: #define GG_PUBDIR50_SEARCH_REPLY 0x05 Wyniki są zbudowane identycznie jak w przypadku zapytań, z tą różnicą, że kolejne osoby oddzielane pustym polem: "parametr\0wartość\0\0parametr\0wartość\0".
Przykładowy wynik zawierający dwie znalezione osoby: FmNumber.12345.FmStatus.1.firstname.Adam.nickname.Janek.birthyear.1979.city.Wzdów ..FmNumber.3141592.FmStatus.5.firstname.Ewa.nickname.Ewcia.birthyear.1982.city.Gd dańsk..nextstart.0. Wyszukiwanie nie zwraca nazwisk znalezionych osób. 1.11. Lista kontaktówOd wersji 6.0 lista kontaktów na serwerze stała częścią sesji, zamiast osobnej sesji HTTP. Aby wysłać lub pobrać listę kontaktów z serwera należy użyć pakietu: #define GG_USERLIST_REQUEST 0x0016 struct gg_userlist_request { char type; /* rodzaj zapytania * char request[]; /* treść, nie musi wystąpić */ }; Pole type oznacza rodzaj zapytania: #define GG_USERLIST_PUT 0x00 /* eksport listy */ #define GG_USERLIST_GET 0x02 /* import listy */ W przypadku eksportu listy kontaktów, pole request zawiera tekst złożony z dowolnej liczby wpisów postaci: imie;nazwisko;pseudo;wyswietlane;telefon;grupa;uin;adres@email;0;;0; Ostatnie 3 pola zostały dodane w wersji 4.9.3 i są związane z dźwiękami. Na zapytania dotyczące listy kontaktów serwer odpowiada pakietem: #define GG_USERLIST_REPLY 0x0010 struct gg_userlist_reply { char type; /* rodzaj zapytania * char request[]; /* treść, nie musi wystąpić */ }; Pole type oznacza rodzaj odpowiedzi: #define GG_USERLIST_PUT_REPLY 0x00 /* eksport listy */ #define GG_USERLIST_GET_REPLY 0x06 /* import listy */ W przypadku importu w polu request znajdzie się lista kontaktów w takiej samej postaci, w jakiej ją umieszczono. Serwer nie ingeruje w jej treść. Aby usunąć listę kontaktów z serwera należy wysłać pustą listę kontaktów. 1.12. Indeks pakietówPakiety wysyłane:
Pakiety odbierane:
2. Usługi HTTP2.1. Format danychKomunikacja z appmsg.gadu-gadu.pl metodą GET HTTP/1.0 została opisana w poprzednim rozdziale, pozostałe pakiety używają POST dla HTTP/1.0, a w odpowiedzi 1.1. Mają one postać: POST ŚCIEŻKA HTTP/1.0 Host: HOST Content-Type: application/x-www-form-urlencoded User-Agent: AGENT Content-Length: DŁUGOŚĆ Pragma: no-cache DANE Gdzie AGENT to nazwa przeglądarki (na przykład Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) lub inne, wymienione w rozdziale 1.2), DŁUGOŚĆ to długość bloku DANE w znakach. Jeśli będzie mowa o wysyłaniu danych do serwera, to chodzi o cały powyższy pakiet, opisane zostaną tylko: HOST, ŚCIEŻKA i DANE. Pakiet jest wysyłany na port 80. Gdy mowa o wysyłaniu pól zapytania, mowa o DANE o wartości: pole1=wartość1&pole2=wartość2&... Pamiętaj o zmianie kodowania na CP1250 i zakodowaniu danych do postaci URL (na przykład funkcją typu urlencode). Odpowiedzi serwera na powyższe zapytania mają mniej więcej postać: HTTP/1.1 200 OK Server: Microsoft-IIS/5.0 Date: Mon, 01 Jul 2002 22:30:31 GMT Connection: Keep-Alive Content-Length: DŁUGOŚĆ Content-Type: text/html Set-Cookie: COOKIE Cache-control: private ODPOWIEDŹ Nagłówki nie są dla nas ważne. Można zauważyć tylko to, że czasami serwer ustawia COOKIE np. ,,ASPSESSIONIDQQGGGLJC=CAEKMBGDJCFBEOKCELEFCNKH; path=/''. Pisząc dalej, że serwer ,,odpowie wartością'' mowa tylko o polu ODPOWIEDŹ. Kodowanie znaków w odpowiedzi to CP1250. 2.2. Rejestracja konta
Dostępne standardowo pytania pomocnicze i ich aliasy (podawane zamiast treści pytania) to:
Przykład: POST /appsvc/fmregister2.asp HTTP/1.0 Host: register.gadu-gadu.pl Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) Content-Length: 62 Pragma: no-cache pwd=sekret&email=moj@adres.email.pl&qa=5~Maria&code=1104465363 Jeśli wszystko przebiegło poprawnie, serwer odpowie: reg_success:UIN Gdzie UIN to nowy numer, który właśnie otrzymaliśmy. 2.3. Usunięcie konta
Przykład: POST /appsvc/fmregister2.asp HTTP/1.0 Host: register.gadu-gadu.pl Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows 98) Content-Length: 74 Pragma: no-cache fmnumber=4969256&fmpwd=haslo&delete=1&pwd=%2D388046464&qa=&code=1483497094 Jeśli wszystko przebiegło poprawnie, serwer odpowie: reg_success:UIN Gdzie UIN to numer, który skasowaliśmy. 2.4. Zmiana hasła
Jeśli wszystko przebiegło poprawnie, serwer odpowie: reg_success:UIN 2.5. Przypomnienie hasła pocztą
Jeśli się udało, serwer odpowie: pwdsend_success 3. Bezpośrednie połączeniaAuthor: Walerian Sokołowski (C) Copyright 2002 You a free to change it in every way you want. ----- (0) disclaimer ------------------------------ Opisuję tu protokół przesyłania plików w między klientami G*du-G*du. Informację czerpałem ze źródeł gglib w którejś z wersji oraz z doświadczeń przeprowadzanych własnoręcznie. W tym celu nic nie desasemblowałem i nie reingenerowałem. ----- 1) zamiast wstępu ------------------------------------------------------ Protokół jest pewną implementacją części powszechnie znanego DCC opisanego w którymś z RFC. Obowiązują naturalne dla GG ustalenia, a więc sizeof(int) = 4 oraz transmisja jest intel endian. ----- 2) nawiązanie połączenia ------------------------------------------------ Klient łącząc się z serwerem GG wysyła swój adres IP i port, na którym nasłuchuje (patrz opis gg_login). Gdy któryś z kontaktów staje się dostępny (właśnie się połączyliśmy lub on właśnie zmienił stan) otrzymujemu powiadomienie o tym w paczce typu GG_NOTIFY_REPLY, która zawiera jego adresek IP i port, na którym on nasłuchuje (patrz opis gg_notify_reply). Tak więc w najprostszej sytuacji, gdy nie dzielą nas żadne firewalle strona wysyłająca ma adres odbiorcy. Jeśli odbiorca znajduje się za firewallem, a nadawca nie, to nadawca może poprosić odbiorcę o nawiązanie połączenia wysyłając do niego komunikat typu 0x0010 (GG_CLASS_CTCP) o zawartości 0x02. W taki sposób na czas nawiązania połączenia nadawca i odbiorca zamieniają się rolami. I do końca punktu będę ich nazywać wg. pełnionych ról. A więc nadawca nawiązuje połączenie TCP z adresem odbiorcy i wysyła swój UIN i UIN odbiorcy: struct { int uin1; /* mój numerek */ int uin2; /* jego numerek */ }; Odbiorca potwierdzając nawiązanie połączenia z klientem GG wysyła 4 bajty: struct { char [] "UDAG"; }; Jeśli nadawca ma wysyłać plik, to wysyła 0x0002: #define GG_DCC_CATCH_FILE 0x0002 Jeśli to odbiorca ma wysyłać, to nadawca wysyła 0x0003: #define GG_DCC_WANT_FILE 0x0003 Po tym wszystkim uważa się połączenie za nawiązane. ----- 3) transmisja pliku: strona nadawcy ------------------------------------- Nadawca wysyła po kolei: #define GG_DCC_HAVE_FILE 0x0001 #define GG_DCC_HAVE_FILEINFO 0x0003 int unknown1; /* 0 */ int unknown2; /* 0 */ file_info_struct finfo; Podejrzewam, że unknown2:unknown1 jest pozycją w pliku, od której nadawca chce wysyłać plik, ale nie udało mi się zasymulować sytuacji, w której byłyby używane. struct file_info_struct { int mode; /* dwFileAttributes */ int ctime[2]; /* ftCreationTime */ int atime[2]; /* ftLastAccessTime */ int mtime[2]; /* ftLastWriteTime */ int size_hdw; /* górne 4 bajty długości pliku */ int size_ldw; /* dolne 4 bajty długości pliku */ int reserved1; /* 0 */ int reserved2; /* 0 */ char file_name[276]; /* tablica zaczynająca się od nazwy pliku, wypełniona zerami */ }; Dalej nadawca czeka na akceptację odbiorcy, czyli następującą strukturkę: struct { int type; /* 0x0006 GG_DCC_GIMME_FILE */ int start; /* od której pozycji zacząć przesyłanie */ int unknown; /* 0 */ }; Teraz możemy zacząć przesyłanie pliku. Plik przesyłamy w paczkach długości ustalonej przez nadawcę. Przed każdą paczką z danymi nadawca wysyła nagłówek paczki: struct { int type; /* 0x0003 GG_DCC_FILEHEADER, jeśli paczka nie jest ostatnia. 0x0002 GG_DCC_LAST_FILEHEADER wpp. */ int chunk_size; /* rozmiar paczki */ int unknown; /* 0 */ }; Po wysłaniu ostatniej paczki zamykamy połączenie. Plik został przesłany. ----- 4) transmisja pliku: strona odbiorcy ------------------------------------ Zachowanie odbiorcy jest symetryczne: 1. odbiera kolejno GG_DCC_HAVE_FILE GG_DCC_HAVE_FILEINFO int unknown1; int unknown2; file_info_struct finfo; 2. jeśli użytkownik zgodzi się odebrać plik, to wysyłamy struktrę jakiej odbiorca się spodziewa. 3. otrzymujemy nagłówek paczki i paczkę z danymi zadeklarowanej długości 4. jeśli nagłówek był typu GG_DCC_LAST_FILEHEADER to otrzymaliśmy całość, więc zamykamy połączenie. Jeśli nie, to wracamy do kroku 3. 4. AutorzyAutorami powyższego opisu są:
$Id: protocol.html,v 1.47 2003/10/06 21:36:39 wojtekka Exp $ |