Вы находитесь на странице: 1из 29

Protokół Gadu-Gadu

© Copyright 2001-2008 Autorzy

Spis treści
1. Protokół Gadu-Gadu
1.1. Format pakietów i konwencje
1.2. Zanim się połączymy
1.3. Logowanie się
1.4. Zmiana stanu
1.5. Ludzie przychodzą, ludzie odchodzą
1.6. Wysyłanie wiadomości
1.7. Otrzymywanie wiadomości
1.8. Ping, pong
1.9. Rozłączenie
1.10. Wiadomości systemowe
1.11. Katalog publiczny
1.12. Lista kontaktów
1.13. Indeks pakietów
2. Usługi HTTP
2.1. Format danych
2.2. Tokeny
2.3. Rejestracja konta
2.4. Usunięcie konta
2.5. Zmiana hasła
2.6. Przypomnienie hasła pocztą
3. Połączenia bezpośrednie
3.1. Nawiązanie połączenia
3.2. Przesyłanie plików
3.3. Rozmowy głosowe
4. Autorzy

Informacje wstępne
Opis 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://toxygen.net/libgadu/protocol/.
1. Protokół Gadu-Gadu
1.1. Format pakietów i konwencje

Podobnie 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. Wszystkie teksty są kodowane przy użyciu zestawu znaków CP1250 (windows-
1250). Linie kończą się znakami \r\n.

Przy opisie struktur, założono, że char ma rozmiar 1 bajtu, short 2 bajtów, int 4 bajtów,
long long 8 bajtów, wszystkie bez znaku. Używając architektur innych niż i386, należy
zwrócić szczególną uwagę na rozmiar typów zmiennych i kolejność bajtów. Poza tym,
większość dostępnych obecnie kompilatorów domyślnie wyrównuje zmienne do rozmiaru
słowa danej architektury, więc należy wyłączyć tą funkcję. W przypadku gcc będzie to
__attribute__ ((packed)) zaraz za deklaracją każdej struktury, a dla Microsoft Visual C++
powinno pomóc:

#pragma pack(push, 1)

/* deklaracje */

#pragma pack(pop)

Pola, których 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, ...
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:

• 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)

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 ziarno — wartość, którą razem z hasłem przekazuje się do funkcji
skrótu:

struct gg_welcome {
int seed; /* ziarno */
};

Kiedy mamy już tą wartość możemy odesłać pakiet logowania:

#define GG_LOGIN70 0x0019

struct gg_login70 {
int uin; /* mój numerek */
char hash_type; /* rodzaj funkcji skrótu hasła */
char hash[64]; /* skrót hasła */
int status; /* status na dzień dobry */
int version; /* moja wersja klienta */
char unknown1; /* 0x00 */
int local_ip; /* mój adres ip */
short 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 */
char description[]; /* opis, nie musi wystąpić */
int time; /* czas, nie musi wystąpić */
};

Skrót hasła można liczyć na dwa sposoby:

#define GG_LOGIN_HASH_GG32 0x01


#define GG_LOGIN_HASH_SHA1 0x02

Pierwszy algorytm (GG_LOGIN_HASH_GG32) został wymyślony na potrzeby Gadu-Gadu i


zwraca 32-bitową wartość dla danego ziarna i hasła. Jego implementacja w języku C
wygląda następująco:

int gg_login_hash(unsigned char *password, unsigned 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;
}

Ze względu na niewielki zakres wartości wyjściowych, istnieje prawdopodobieństwo, że


inne hasło przy odpowiednim ziarnie da taki sam wynik. Z tego powodu zalecane jest
używane algorytmu SHA-1, którego implementacje są dostępne dla większości
współczesnych systemów operacyjnych.

Liczba oznaczająca wersję może być jedną z poniższych:

Wartość Wersje klientów


0x2a 7.7 (build 3315)
0x29 7.6 (build 1688)
0x28 7.5.0 (build 2201)
0x27 7.0 (build 22)
0x26 7.0 (build 20)
0x25 7.0 (build 1)
0x24 6.1 (build 155) lub 7.6 (build 1359)
0x22 6.0 (build 140)
0x21 6.0 (build 133)
0x20 6.0
0x1e 5.7 beta (build 121)
0x1c 5.7 beta
0x1b 5.0.5
0x19 5.0.3
0x18 5.0.1, 5.0.0, 4.9.3
0x17 4.9.2
0x16 4.9.1
0x15 4.8.9
0x14 4.8.3, 4.8.1
0x11 4.6.10, 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 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 osoba korzysta z bramki Era Omnix, do wersji dodawana jest wartość:

#define GG_ERA_OMNIX_MASK 0x04000000

Jeśli autoryzacja się powiedzie, dostaniemy w odpowiedzi pakiet:

#define GG_LOGIN_OK 0x0003

o długości równej 0 lub 1 (dla wersji klienta powyżej 6.0 (build 140) w pakiecie będzie
znajdowało się jednobajtowe pole o wartości 0x1F, którego przeznaczenie nie jest jeszcze
poznane). Możemy także dostać pakiet:

#define GG_NEED_EMAIL 0x0014

gdy numer i hasło się zgadzają, ale serwer chce nas poinformować, że powinniśmy
uzupełnić adres e-mail w katalogu publicznym. W przypadku błędu autoryzacji otrzymamy:

#define GG_LOGIN_FAILED 0x0009

1.4. Zmiana stanu

Gadu-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:

Etykieta Wartość Znaczenie


GG_STATUS_NOT_AVAIL 0x0001 Niedostępny
GG_STATUS_NOT_AVAIL_DESCR 0x0015 Niedostępny (z opisem)
GG_STATUS_AVAIL 0x0002 Dostępny
GG_STATUS_AVAIL_DESCR 0x0004 Dostępny (z opisem)
GG_STATUS_BUSY 0x0003 Zajęty
GG_STATUS_BUSY_DESCR 0x0005 Zajęty (z opisem)
GG_STATUS_INVISIBLE 0x0014 Niewidoczny
GG_STATUS_INVISIBLE_DESCR 0x0016 Niewidoczny z opisem
GG_STATUS_BLOCKED 0x0006 Zablokowany
GG_STATUS_FRIENDS_MASK 0x8000 Maska bitowa oznaczająca tryb tylko dla przyjaciół

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. Lista kontaktów jest dzielona na pakiety po 400
wpisów. Pierwsze wpisy są typu GG_NOTIFY_FIRST, a ostatni typu GG_NOTIFY_LAST, żeby
serwer wiedział, kiedy kończymy. Jeśli lista kontaktów jest mniejsza niż 400 wpisów,
wysyłamy oczywiście tylko GG_NOTIFY_LAST. Pakiety te zawierają struktury gg_notify:

#define GG_NOTIFY_FIRST 0x000f


#define GG_NOTIFY_LAST 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:

Etykieta Wartość Znaczenie


GG_USER_BUDDY 0x01 Każdy użytkownik dodany do listy kontaktów
GG_USER_FRIEND 0x02
Użytkownik, dla którego jesteśmy widoczni w trybie „tylko dla
przyjaciół”
GG_USER_BLOCKED 0x04 Użytkownik, którego wiadomości nie chcemy otrzymywać

Jednak dla zachowania starego nazewnictwa stałych można używać najczęściej spotykane
wartości to:

Etykieta Wartość Znaczenie


GG_USER_OFFLINE 0x01
Użytkownik, dla którego będziemy niedostępni, ale mamy go w
liście kontaktów
GG_USER_NORMAL 0x03 Zwykły użytkownik dodany do listy kontaktów
GG_USER_BLOCKED 0x04 Użytkownik, którego wiadomości nie chcemy otrzymywać

Jeśli nie mamy nikogo na liście wysyłamy następujący pakiet o zerowej długości:

#define GG_LIST_EMPTY 0x0012

Jeśli ktoś jest, serwer odpowie pakietem GG_NOTIFY_REPLY77 zawierającym jedną lub więcej
struktur gg_notify_reply77:

#define GG_NOTIFY_REPLY77 0x0018

struct gg_notify_reply77 {
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 */
int unknown2; /* 0x00000000 */
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:

Etykieta Wartość Znaczenie


GG_UINFLAG_UNKNOWN1 0x10 Nieznane
GG_UINFLAG_UNKNOWN2 0x20 Flaga spotykana, gdy użytkownik staje się niedostępny
GG_UINFLAG_VOICE 0x40 Użytkownik może prowadzić rozmowy głosowe
GG_UINFLAG_ERA_OMNIX 0x08 Użytkownik łączy się przez bramkę Era Omnix

remote_port poza zwykłym portem może przyjmować również poniższe wartości:


Wartość Znaczenie
0 Klient nie obsługuje bezpośrednich połączeń
1 Klient łączy się zza NAT lub innej formy maskarady
2 Klient nie ma nas w swojej liście kontaktów

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 GG_NOTIFY_*. Dodaje on flagi rodzaju 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_STATUS77 0x0017

struct gg_status77 {
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 */
int unknown2; /* 0x00000000 */
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_reply77.

1.6. Wysyłanie wiadomości


Wiadomoś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:

Etykieta Wartość Znaczenie


Bit ustawiany wyłącznie przy odbiorze wiadomości, gdy
GG_CLASS_QUEUED 0x0001 wiadomość została wcześniej zakolejkowania z powodu
nieobecności
GG_CLASS_MSG 0x0004 Wiadomość ma się pojawić w osobnym okienku
GG_CLASS_CHAT 0x0008
Wiadomość jest częścią toczącej się rozmowy i zostanie
wyświetlona w istniejącym okienku
GG_CLASS_CTCP 0x0010
Wiadomość jest przeznaczona dla klienta Gadu-Gadu i nie
powinna być wyświetlona użytkownikowi.
GG_CLASS_ACK 0x0020 Klient nie życzy sobie potwierdzenia wiadomości.

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:

Offset Wartość
n Treść wiadomości
m 0x01 (wiadomość konferencyjna)
m+1
m+2
0x02 (ilość adresatów)
m+3
m+4
m+5
m+6
Numer pierwszego adresata
m+7
m+8
m+9
m + 10
Numer drugiego adresata
m + 11
m + 12

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:

Etykieta Wartość Znaczenie


GG_FONT_BOLD 0x01 Pogrubiony tekst
GG_FONT_ITALIC 0x02 Kursywa
GG_FONT_UNDERLINE 0x04 Podkreślenie
GG_FONT_COLOR 0x08 Kolorowy tekst. Tylko w tym wypadku struktura
gg_msg_richtext_format zawiera pole rgb[] będące opisem
trzech składowych koloru, kolejno czerwonej, zielonej i
niebieskiej.
GG_FONT_IMAGE 0x80
Obrazek. Tylko w tym wypadku struktura
gg_msg_richtext_format zawiera pole image.

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; /* 0x0109 */
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, nie musi wystąpić */
};

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 przekraczających 1909 bajtów. Pierwszy pakiet ma pole flag
filename w ogóle nie występuje (nawet bajt zakończenia ciągu znaków).

Jeśli otrzymamy pakiet bez pola filename oraz image, oznacza to, że klient nie posiada
żądanego obrazka.

Przykładowo, by przesłać tekst „ala ma kota”, należy dołączyć do wiadomości następującą


sekwencję bajtów:

Offset Wartość Znaczenie


n 0x02 Opis atrybutów tekstu...
n +1
0x0006 ...mający 6 bajtów długości
n +2
n +3
0x0004 Atrybut zaczyna się od pozycji 4...
n +4
n +5 0x01 ...i jest to pogrubiony tekst
n +6
0x0006 Atrybut zaczyna się od pozycji 6...
n +7
n +8 0x00 ...i jest to zwykły tekst

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:

Etykieta Wartość Znaczenie


Wiadomości nie przesłano (zdarza się przy
GG_ACK_BLOCKED 0x0001
wiadomościach zawierających adresy internetowe
blokowanych przez serwer GG gdy odbiorca nie ma nas
na liście)
GG_ACK_DELIVERED 0x0002 Wiadomość dostarczono
GG_ACK_QUEUED 0x0003 Wiadomość zakolejkowano
Wiadomości nie dostarczono. Skrzynka odbiorcza na
GG_ACK_MBOXFULL 0x0004 serwerze jest pełna (20 wiadomości maks). Występuje
tylko w trybie offline
GG_ACK_NOT_DELIVERED 0x0006
Wiadomości nie dostarczono. Odpowiedź ta występuje
tylko w przypadku wiadomości klasy GG_CLASS_CTCP

1.7. Otrzymywanie wiadomości

Wiadomoś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, pong

Od 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łączenie

Jeś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
(wtedy pakiet zostanie wysłany w odpowiedzi na GG_LOGIN70), lub gdy równocześnie
połączy się drugi klient z tym samym numerem (nowe połączenie ma wyższy priorytet).

W nowych wersjach protokołu (zaobserwowano w 0x2a), po wysłaniu pakietu


zmieniającego status na niedostępny, serwer może przysłać pakiet:
#define GG_DISCONNECTING2 0x000d

1.10. Wiadomości systemowe

Od wersji 7.7 serwer może wysyłać nam wiadomości systemowe przy pomocy pakietu:

#define GG_XML_EVENT 0x0027

Wiadomość systemowa zawiera kod XML zakodowany w UTF-8 z informacjami dotyczącymi


np. przedłużenia konta w mobilnym GG, czy nowej wiadomości na poczcie głosowej.
Przykładowy kod:

<?xml version="1.0" encoding="utf-8"?>


<event xmlns:ev="www.gadu-gadu.pl/Event/1.0" id ="" type="realtime"
creation_time="1194732873" ttl="60">
<ev:actions>
<ev:showMessage>
<ev:text>Wejdź na stronę EKG</ev:text>

<ev:executeHtml url="ekg.chmurka.net" />


</ev:showMessage>
</ev:actions>
</event>

1.11. Katalog publiczny

Od 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, różnym od zera, 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:

Etykieta Wartość Znaczenie


GG_PUBDIR50_UIN FmNumber Numer szukanej osoby
GG_PUBDIR50_FIRSTNAME firstname Imię
GG_PUBDIR50_LASTNAME lastname Nazwisko
GG_PUBDIR50_NICKNAME nickname Pseudonim
Rok urodzenia. Jeśli chcemy szukać osób z danego
GG_PUBDIR50_BIRTHYEAR birthyear przedziału, podajemy rok początkowy i końcowy,
oddzielone spacją. Na przykład „1980 1985”.
GG_PUBDIR50_CITY city Miejscowość
Płeć. Jeśli szukamy kobiet, ma wartość „1” (stała
GG_PUBDIR50_GENDER_FEMALE). Jeśli mężczyzn, ma
wartość „2” (stała GG_PUBDIR50_GENDER_MALE). W
GG_PUBDIR50_GENDER gender przypadku pobierania lub ustawiania informacji o
sobie stałe mają odwrócone znaczenia (stałe
GG_PUBDIR50_GENDER_SET_FEMALE i
GG_PUBDIR50_GENDER_SET_MALE)

GG_PUBDIR50_ACTIVE ActiveOnly
Jeśli szukamy tylko dostępnych osób, ma mieć
wartość „1” (stała GG_PUBDIR50_ACTIVE_TRUE).
GG_PUBDIR50_FAMILYNAME familyname
Nazwisko panieńskie. Ma znaczenie tylko przy
ustawianiu własnych danych.
GG_PUBDIR50_FAMILYCITY familycity
Miejscowość pochodzenia. Ma znaczenie tylko przy
ustawianiu własnych danych.
GG_PUBDIR50_START fmstart
Numer, od którego rozpocząć wyszukiwanie. Ma
znaczenie, gdy kontynuujemy wyszukiwanie.

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".

Etykieta Wartość Znaczenie


GG_PUBDIR50_STATUS FmStatus Stan szukanej osoby

Pole występujące w ostatnim wyniku, określające, od


nextstart
jakiego numeru należy rozpocząć wyszukiwanie, by
otrzymać kolejną porcję danych. Podaje się go w
zapytaniu jako parametr „start”.

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 i płci znalezionych osób.

1.12. Lista kontaktów

Od 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 /* początek eksportu listy */


#define GG_USERLIST_PUT_MORE 0x01 /* dalsza część eksportu listy */
#define GG_USERLIST_GET 0x02 /* import listy */

W przypadku eksportu listy kontaktów, pole request zawiera tekst złożony z dowolnej
liczby linii postaci:
imię;nazwisko;pseudonim;wyświetlane;telefon_komórkowy;grupa;uin;adres_email;dostę
pny;ścieżka_dostępny;wiadomość;ścieżka_wiadomość;ukrywanie;telefon_domowy

Funkcje mniej oczywistych pól to:

• dostępny określa dźwięki związane z pojawieniem się danej osoby i przyjmuje


wartości 0 (użyte zostaną ustawienia globalne), 1 (dźwięk powiadomienia zostanie
wyłączony), 2 (zostanie odtworzony plik określony w polu ścieżka_dostępny).

• wiadomość działa podobnie jak dostępny, ale określa dźwięk dla przychodzącej
wiadomości.

• ukrywanie określa czy będziemy dostępni (0) czy niedostępni (1) dla danej osoby w
trybie tylko dla znajomych.

Pole niewypełnione może zostać puste, a w przypadku pól liczbowych, przyjąć wartość 0.

Podczas przesyłania lista kontaktów jest dzielona na pakiety po 2048 bajtów. Pierwszy jest
wysyłany pakietem typu GG_USERLIST_PUT, żeby uaktualnić plik na serwerze, pozostałe typu
GG_USERLIST_PUT_MORE, żeby dopisać do pliku.

Na zapytania dotyczące listy kontaktów serwer odpowiada pakietem:

#define GG_USERLIST_REPLY 0x0010

struct gg_userlist_reply {
char type; /* rodzaj zapytania */
char reply[]; /* treść, nie musi wystąpić */
};

Pole type oznacza rodzaj odpowiedzi:

#define GG_USERLIST_PUT_REPLY 0x00 /* początek eksportu listy */


#define GG_USERLIST_PUT_MORE_REPLY 0x02 /* kontynuacja */
#define GG_USERLIST_GET_MORE_REPLY 0x04 /* początek importu listy */
#define GG_USERLIST_GET_REPLY 0x06 /* ostatnia część importu */

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ść. Podobnie jak przy wysyłaniu,
przychodzi podzielona na mniejsze pakiety. Pobieranie krótkiej listy kontaktów zwykle
powoduje wysłanie pojedynczego pakietu GG_USERLIST_GET_REPLY, a gdy lista jest długa,
serwer może przysłać dowolną ilość pakietów GG_USERLIST_GET_MORE_REPLY przed pakietem
GG_USERLIST_GET_REPLY.

Aby usunąć listę kontaktów z serwera należy wysłać pustą listę kontaktów.
1.13. Indeks pakietów

Pakiety wysyłane:

Wartość Etykieta Znaczenie


0x0002 GG_NEW_STATUS Zmiana stanu
0x0007 GG_PONG Pong
0x0008 GG_PING Ping
0x000b GG_SEND_MSG Wysłanie wiadomości
0x000c GG_LOGIN Logowanie przed GG 6.0
0x000d GG_ADD_NOTIFY Dodanie do listy kontaktów
0x000e GG_REMOVE_NOTIFY Usunięcie z listy kontaktów
0x000f GG_NOTIFY_FIRST
Początkowy fragment listy kontaktów większej niż 400
wpisów
0x0010 GG_NOTIFY_LAST Ostatni fragment listy kontaktów
0x0012 GG_LIST_EMPTY Lista kontaktów jest pusta
0x0013 GG_LOGIN_EXT Logowanie przed GG 6.0
0x0014 GG_PUBDIR50_REQUEST Zapytanie katalogu publicznego
0x0015 GG_LOGIN60 Logowanie
0x0016 GG_USERLIST_REQUEST Zapytanie listy kontaktów na serwerze
0x0019 GG_LOGIN70 Logowanie

Pakiety odbierane:

Wartość Etykieta Znaczenie


0x0001 GG_WELCOME Liczba do wyznaczenie hashu hasła
0x0002 GG_STATUS Zmiana stanu przed GG 6.0
0x0003 GG_LOGIN_OK Logowanie powiodło się
0x0005 GG_SEND_MSG_ACK Potwierdzenie wiadomości
0x0007 GG_PONG Pong
0x0008 GG_PING Ping
0x0009 GG_LOGIN_FAILED Logowanie nie powiodło się
0x000a GG_RECV_MSG Przychodząca wiadomość
0x000b GG_DISCONNECTING Zerwanie połączenia
0x000c GG_NOTIFY_REPLY Stan listy kontaktów przed GG 6.0
0x000d GG_DISCONNECTING2 Zerwanie połączenia po zmianie stanu na niedostępny
0x000e GG_PUBDIR50_REPLY Odpowiedź katalogu publicznego
0x000f GG_STATUS60 Zmiana stanu przed GG 7.7
0x0010 GG_USERLIST_REPLY Odpowiedź listy kontaktów na serwerze
0x0011 GG_NOTIFY_REPLY60 Stan listy kontaktów przed GG 7.7

0x0014 GG_NEED_EMAIL
Logowanie powiodło się, ale powinniśmy uzupełnić adres e-
mail w katalogu publicznym
0x0017 GG_STATUS77 Zmiana stanu
0x0018 GG_NOTIFY_REPLY77 Stan listy kontaktów
0x0027 GG_XML_EVENT Odebrano wiadomość systemową

2. Usługi HTTP
2.1. Format danych

Komunikacja 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
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. Tokeny

Prawdopodobnie ze względu na nadużycia i wykorzystywanie automatów rejestrujących do


polowań na „złote numery GG”, wprowadzono konieczność autoryzacji za pomocą tokenu.
Każda operacja zaczyna się od pobrania tokenu z serwera, wyświetlenia użytkownikowi,
odczytaniu jego wartości i wysłania zapytania z identyfikatorem i wartością tokenu.
Pobranie tokenu wygląda następująco:

Pole nagłówka Wartość


HOST register.gadu-gadu.pl
ŚCIEŻKA /appsvc/regtoken.asp

Nie są wysyłane żadne parametry. Przykład:

POST /appsvc/regtoken.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: 0
Pragma: no-cache

Serwer w odpowiedzi odeśle:

SZEROKOŚĆ WYSOKOŚĆ DŁUGOŚĆ


IDENTYFIKATOR
ŚCIEŻKA

Gdzie SZEROKOŚĆ i WYSOKOŚĆ opisują wymiary obrazka z wartością tokenu, DŁUGOŚĆ mówi ile
znaków zawiera token, IDENTYFIKATOR jest identyfikatorem tokenu (tylko do niego pasuje
wartość tokenu), a ŚCIEŻKA to ścieżka do skryptu zwracającego obrazek z wartością
tokenu. Przykładowa odpowiedź:

60 24 6
06C05A44
http://register.gadu-gadu.pl/appsvc/tokenpic.asp
Możemy teraz pobrać metodą GET z podanej ścieżki obrazek z tokenem, doklejając do
ścieżki parametr tokenid o wartości będącej identyfikatorem uzyskanym przed chwilą.
Adres obrazka z wartością tokenu dla powyższego przykładu to:

http://register.gadu-gadu.pl/appsvc/tokenpic.asp?tokenid=06C05A44

Pobrany obrazek (w tej chwili jest w formacie JPEG, ale prawdopodobnie może się to
zmienić na dowolny format obsługiwany domyślnie przez system Windows) najlepiej
wyświetlić użytkownikowi, prosząc o podanie wartości na nim przedstawionej. Będzie ona
niezbędna do przeprowadzenia kolejnych operacji.

2.3. Rejestracja konta

Pole nagłówka Wartość


HOST register.gadu-gadu.pl
ŚCIEŻKA /appsvc/fmregister3.asp
Wysyłamy pole Znaczenie
pwd hasło dla nowego numeru
email e-mail na który będzie przesyłane przypomnienie hasła
tokenid identyfikator tokenu
tokenval wartość tokenu
code hash liczony z pól email i pwd. Algorytmu szukaj w źródłach libgadu w
lib/common.c

Przykład:

POST /appsvc/fmregister3.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: 76
Pragma: no-cache

pwd=sekret&email=abc@xyz.pl&tokenid=06C05A44&tokenval=e94d56&code=1104465363

Jeśli wszystko przebiegło poprawnie, serwer odpowie:

Tokens okregisterreply_packet.reg.dwUserId=UIN

Gdzie UIN to nowy numer, który właśnie otrzymaliśmy.

Jeśli został podany nieprawidłowy token, serwer odpowie:


bad_tokenval

2.4. Usunięcie konta

Pole nagłówka Wartość


HOST register.gadu-gadu.pl
ŚCIEŻKA /appsvc/fmregister3.asp
Wysyłamy pole Znaczenie
fmnumber usuwany numer
fmpwd hasło
delete wartość „1”
pwd losowa liczba
email wartość „deletedaccount@gadu-gadu.pl”
tokenid identyfikator tokenu
tokenval wartość tokenu
code hash liczony z pól pwd i email

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: 137
Pragma: no-cache

fmnumber=4969256&fmpwd=haslo&delete=1&email=deletedaccount@gadu-gadu.pl&pwd=%2D38
8046464&tokenid=06C05A44&tokenval=e94d56&code=1483497094

Jeśli wszystko przebiegło poprawnie, serwer odpowie:

reg_success:UIN

Gdzie UIN to numer, który skasowaliśmy.

2.5. Zmiana hasła

Pole nagłówka Wartość


HOST register.gadu-gadu.pl
ŚCIEŻKA /appsvc/fmregister3.asp
Wysyłamy pole Znaczenie
fmnumber numer
fmpwd stare hasło
pwd nowe hasło
email nowe adres e-email
tokenid identyfikator tokenu
tokenval wartość tokenu
code hash liczony z pól pwd i email

Jeśli wszystko przebiegło poprawnie, serwer odpowie:

reg_success:UIN

2.6. Przypomnienie hasła pocztą

Pole nagłówka Wartość


HOST retr.gadu-gadu.pl
ŚCIEŻKA /appsvc/fmsendpwd3.asp
Wysyłamy pole Znaczenie
userid numer
tokenid identyfikator tokenu
tokenval wartość tokenu
code hash liczony z pola userid

Jeśli się udało, serwer odpowie:

pwdsend_success

3. Połączenia bezpośrednie
3.1. Nawiązanie połączenia

Połączenia bezpośrednie pozwalają przesyłać pliki lub prowadzić rozmowy głosowe bez
pośrednictwa serwera. Początkowe wersje Gadu-Gadu potrafiły przesyłać bezpośrednio
również wiadomości tekstowe, ale funkcjonalność ta została zarzucona.

Połączenia są możliwe jedynie w sytuacji, gdy co najmniej jedna ze stron posiada publiczny
adres IP. Jeśli jest to strona wywoływana, wystarczy połączyć się na podany adres IP i
port. Gdy nie ma możliwości połączenia się ze stroną wywoływaną, należy wysłać do niej
wiadomość klasy GG_CLASS_CTCP, zawierającą jeden bajt o wartości 0x02. Odebranie takiej
wiadomości powinno spowodować połączenie bezpośrednie z nadawcą, w którym role stron
będą zamienione aż do momentu określenia rzeczywistego kierunku transmisji.

Po nawiązaniu połączenia należy wysłać pakiet zawierający numery Gadu-Gadu strony


wywołującej i wywoływanej. W odróżnieniu od połączenia z serwerem, w połączeniach
bezpośrednich pakiety nie są poprzedzane strukturami gg_header.

struct gg_dcc_welcome {
int uin; /* numer strony wywołującej */
int peer_uin; /* numer strony wywoływanej */
};

Strona wywoływana sprawdza, czy nadawca istnieje w liście kontaktów i czy łączy się z
tego samego adresu, który zgłosił serwerowi. Gdy któryś z parametrów się nie zgadza,
połączenie należy ze względów bezpieczeństwa zerwać. Pomyślna autoryzacja jest
sygnalizowana przez wysłanie do strony wywołującej pakietu:

struct gg_dcc_welcome_ack {
int ack; /* wartość 0x47414455, tekst "UDAG" */
};

Strona wywołująca następnie wysyła pakiet, w którym określa kto właściwie ma rozpocząć
transmisję:

#define GG_DCC_DIRECTION_IN 0x0002


#defnie GG_DCC_DIRECTION_OUT 0x0003

struct gg_dcc_direction {
int type; /* w którą stronę połączenie? */
};

Jeśli strona wywołująca była stroną inicjującą połączenie bezpośrednie (nie chodzi o
połączenie TCP, a o żądanie użytkownika), wysyła GG_DCC_DIRECTION_IN, a następnie
wysyła pakiety zależne od rodzaju połączenia bezpośredniego, opisane w dalszych
rozdziałach. Gdy strona wywołująca została poproszona o połączenie za pomocą
wiadomości klasy GG_CLASS_CTCP, wysyła GG_DCC_DIRECTION_OUT i oczekuje na pakiet
zależny od rodzaju połączenia bezpośredniego. Jak widać, rzeczywisty kierunek transmisji
jest już określony.

3.2. Przesyłanie plików

Aby powiadomić o chęci przesłania pliku, należy wysłać następujące pakiety. Pierwszy
określa rodzaj połączenia, drugi zawiera szczegóły dotyczące pliku.

#define GG_DCC_REQUEST_SEND 0x0001

struct gg_dcc_request {
int type; /* GG_DCC_REQUEST_SEND */
};
#define GG_DCC_FILE_INFO 0x0003

struct gg_dcc_file_info {
int type; /* GG_DCC_FILE_INFO */
int unknown1; /* == 0 */
int unknown2; /* == 0 */

struct gg_file_info {
int dwFileAttributes; /* atrybuty pliku */
long long ftCreationTime; /* czas utworzenia pliku */
long long ftLastAccessTime; /* czas ostatniego dostępu */
long long ftLastWriteTime; /* czas ostatniego zapisu */
int nFileSizeHigh; /* górne 32 bity rozmiaru */
int nFileSizeLow; /* dolne 32 bity rozmiaru */
int dwReserved0; /* == 0 */
int dwReserved1; /* == 0 */
char cFileName[262]; /* nazwa pliku */
char cAlternateFileName[14]; /* krótka nazwa pliku */
} info;
};

Struktura gg_file_info odpowiada strukturze WIN32_FIND_DATA z API Win32.

Strona wywoływana, w przypadku gdy użytkownik zaakceptuje pobranie pliku, odsyła


pakiet:

#define GG_DCC_SEND_ACK 0x0006

struct gg_dcc_send_ack {
int type; /* GG_DCC_SEND_ACK */
int offset; /* od którego zaczynamy przesyłanie */
int unknown1; /* == 0 */
};

Jeśli plik został już częściowo odebrany i chcemy wznowić przesyłanie, w polu offset
wystarczy podać ile bajtów już mamy, a odebrane dane dopisać na końcu pliku.

Po zaakceptowaniu pliku, strona wywołująca przesyła jego zawartość podzieloną na


pakiety, a następnie zamyka połączenie.

#define GG_DCC_SEND_DATA 0x0003


#define GG_DCC_SEND_DATA_LAST 0x0002

struct gg_dcc_send_data {
int type; /* typ pakietu */
int length; /* rozmiar pakietu */
char data[]; /* dane */
};

Domyślnie dane są dzielone na pakiety o rozmiarze 4096 bajtów. Ostatni pakiet danych jest
oznaczany typem GG_DCC_SEND_DATA_LAST, podczas gdy pozostałe GG_DCC_SEND_DATA.
Podczas implementacji dobrze było by rozważyć, czy strona wywoływana powinna odebrać
nie więcej niż rozmiar pliku zadeklarowany w strukturze gg_file_info czy odbierać dane aż
do otrzymania pakietu typu GG_DCC_SEND_DATA_LAST, implementacji.

3.3. Rozmowy głosowe

Aby powiadomić o chęci rozmowy głosowej należy wysłać pakiet:

#define GG_DCC_REQUEST_VOICE 0x0002

struct gg_dcc_request {
int type; /* GG_DCC_REQUEST_VOICE */
};

Strona wywołana może potwierdzić chęć przeprowadzenia rozmowy za pomocą pakietu:

#define GG_DCC_VOICE_ACK 0x01

struct gg_dcc_voice_ack {
char type; /* GG_DCC_VOICE_ACK */
};

Jeśli strona wywołana chce odrzucić rozmowę głosową, zrywa połączenie. Mimo tego,
strona wywołująca nie powinna ignorować wartości potwierdzenia.

Następnie przesyłane są próbki dźwiękowe kodowane microsoftowym wariantem GSM. Pod


systemem Windows wystarczy użyć standardowego kodeka, pod innymi można skorzystać z
biblioteki libgsm z opcją WAV49. Pakiet danych wygląda następująco:

#define GG_DCC_VOICE_DATA 0x03

struct gg_dcc_voice_data {
char type; /* GG_DCC_VOICE_DATA */
int length; /* długość pakietu */
char data[]; /* dane */
};

W celu zakończenia rozmowy głosowej, zamiast powyższej ramki wysyła się:

#define GG_DCC_VOICE_TERMINATE 0x04

struct gg_dcc_voice_terminate {
char type; /* GG_DCC_VOICE_TERMINATE */
};

Do wersji 5.0.5 w jednym pakiecie było umieszczone 6 ramek GSM (6 * 32,5 = 195
bajtów), a począwszy od tej wersji przesyła się po 10 ramek GSM, poprzedzając je bajtem
zerowym (1 + 10 * 32,5 = 326 bajtów).
----- 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ą strukturę:

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 strukturę 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. Autorzy
Lista autorów tego tekstu znajduje się poniżej. Ich adresy e-mail nie służą do zadawania
pytań o podstawy programowania albo jak się połączyć z serwerem i co zrobić dalej. Jeśli
masz pytania dotyczące protokołu, napisz na listę dyskusyjną libgadu-devel.

• Wojtek Kaniewski (wojtekka%irc.pl): pierwsza wersja opisu, poprawki,


utrzymanie wszystkiego w porządku.
• Robert J. Woźny (speedy%atman.pl): opis nowości w protokole GG 4.6, poprawki.
• Tomasz Jarzynka (tomee%cpi.pl): badanie timeoutów.
• Adam Ludwikowski (adam.ludwikowski%wp.pl): wiele poprawek, wersje klientów,
rozszerzone wiadomości, powody nieobecności.
• Marek Kozina (klith%hybrid.art.pl): czas otrzymania wiadomości.
• Rafał Florek (raf%regionet.regionet.pl): opis połączeń konferencyjnych.
• Igor Popik (igipop%wsfiz.edu.pl): klasy wiadomości przy odbieraniu
zakolejkowanej.
• Rafał Cyran (ajron%wp.pl): informacje o remote_port, rodzaje potwierdzeń przy
ctcp, GG_LOGIN_EXT.
• Piotr Mach (pm%gadu-gadu.com): ilość kontaktów, pełna skrzynka, pusta lista,
maska audio, usługi HTTP, GG_LOGIN_EXT.
• Adam Czyściak (acc%interia.pl): potwierdzenie wiadomości GG_CLASS_ACK.
• Kamil Dębski (kdebski%kki.net.pl): czas w stanach opisowych.
• Paweł Piwowar (alfapawel%go2.pl): format czasu.
• Tomasz Chiliński (chilek%chilan.com): nowości w 5.0.2.
• Radosław Nowak (rano%ranosoft.net): uzupełnienie statusu opisowego, wersja
5.0.3.
• Walerian Sokołowski: pierwsza wersja opisu protokołu bezpośrednich połączeń.
• Nikodem (n-d%tlen.pl): flagi rodzaju użytkownika.
• Adam Wysocki (gophi%ekg.chmurka.net): poprawki, utrzymanie wszystkiego w
porządku, GG_XML_EVENT.
• Marcin Krupowicz (marcin.krupowicz%gmail.com): informacja na temat tego, że
pakiet GG_LOGIN_OK nie zawsze jest zerowej długości.
• Jakub Zawadzki (darkjames%darkjames.ath.cx): nowości w 7.x.

$Id: protocol.html 620 2008-06-09 19:57:14Z wojtekka $

Вам также может понравиться