Protokół Gadu-Gadu

# Protokół Gadu-Gadu ### © Copyright 2001 - 2004 Autorzy ## Spis treści 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. Katalog publiczny 1.11. Lista kontaktów 1.12. Indeks pakietów 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ą Połączenia bezpośrednie 3.1. Nawiązanie połączenia 3.2. Przesyłanie plików 3.3. Rozmowy głosowe 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://dev.null.pl/ekg/docs/protocol.html. ## 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. 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ó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: `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 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 */
    	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ć */
    };
Hash hasła można obliczyć następującą funkcją języka C:
    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;
    }
Liczba oznaczająca wersję może być jedną z poniższych:
Wartość | Wersje klientów
`0x26` | 7.0 (build 20)
`0x24` | 6.1 (build 155)
`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 zerowej długości lub pakiet:
    #define GG_NEED_EMAIL 0x14
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 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 */
    	char 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:
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_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ś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
`GG_CLASS_QUEUED` | `0x0001` | Bit ustawiany wyłącznie przy odbiorze wiadomości, gdy 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 | `0x02` (ilość adresatów)
m + 2
m + 3
m + 4
m + 5 | Numer pierwszego adresata
m + 6
m + 7
m + 8
m + 9 | Numer drugiego adresata
m + 10
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` 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). 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
`GG_ACK_BLOCKED` | `0x0001` | Wiadomości nie przesłano (zdarza się przy 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
`GG_ACK_MBOXFULL` | `0x0004` | Wiadomości nie dostarczono. Skrzynka odbiorcza na 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, lub gdy równocześnie połączy się drugi klient z tym samym numerem (nowe połączenie ma wyższy priorytet). ### 1.10. 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
`GG_PUBDIR50_BIRTHYEAR` | `birthyear` | Rok urodzenia. Jeśli chcemy szukać osób z danego przedziału, podajemy rok początkowy i końcowy, oddzielone spacją. Na przykład ,,`1980 1985`''.
`GG_PUBDIR50_CITY` | `city` | Miejscowość
`GG_PUBDIR50_GENDER` | `gender` | 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 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
| `nextstart` | Pole występujące w ostatnim wyniku, określające, od 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.11. 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 request[];		/* 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.12. 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
`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
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
`0x000e` | `GG_PUBDIR50_REPLY` | Odpowiedź katalogu publicznego
`0x000f` | `GG_STATUS60` | Zmiana stanu
`0x0010` | `GG_USERLIST_REPLY` | Odpowiedź listy kontaktów na serwerze
`0x0011` | `GG_NOTIFY_REPLY60` | Stan listy kontaktów
`0x0014` | `GG_NEED_EMAIL` | Logowanie powiodło się, ale powinniśmy uzupełnić adres e-mail w katalogu publicznym
## 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
    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. 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:
    reg_success:UIN
Gdzie `UIN` to nowy numer, który właśnie otrzymaliśmy. ### 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 Autorami powyższego opisu są: 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. $Id: protocol.html,v 1.71 2005/10/16 12:42:48 wojtekka Exp $