Академический Документы
Профессиональный Документы
Культура Документы
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.
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.
Ż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:
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
Po połączeniu się portem 8074 lub 443 serwera Gadu-Gadu, otrzymujemy pakiet typu
0x0001, który na potrzeby tego dokumentu nazwiemy:
Reszta pakietu zawiera ziarno — wartość, którą razem z hasłem przekazuje się do funkcji
skrótu:
struct gg_welcome {
int seed; /* ziarno */
};
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ć */
};
y = seed;
z = y & 0x1f;
y = (y << z) | (y >> (32 - z));
}
return y;
}
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ść:
Jeśli osoba korzysta z bramki Era Omnix, do wersji dodawana jest wartość:
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:
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:
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.
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:
struct gg_notify {
int uin; /* numerek danej osoby */
char type; /* rodzaj użytkownika */
};
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 następujący pakiet o zerowej długości:
Jeśli ktoś jest, serwer odpowie pakietem GG_NOTIFY_REPLY77 zawierającym jedną lub więcej
struktur gg_notify_reply77:
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ć */
};
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.
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.
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:
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ć */
};
struct gg_send_msg {
int recipient; /* numer odbiorcy */
int seq; /* numer sekwencyjny */
int class; /* klasa wiadomości */
char message[]; /* treść */
};
Długość treści wiadomości nie powinna przekraczać 2000 znaków. Oryginalny klient
zezwala na wysłanie do 1989 znaków.
struct gg_msg_recipients {
char flag; /* == 1 */
int count; /* ilość odbiorców */
int recipients[]; /* tablica odbiorców */
};
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:
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
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.
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:
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:
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.
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.
1.9. Rozłączenie
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).
Od wersji 7.7 serwer może wysyłać nam wiadomości systemowe przy pomocy pakietu:
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:
struct gg_pubdir50 {
char type;
int seq;
char request[];
};
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:
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.
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:
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.
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:
struct gg_userlist_request {
char type; /* rodzaj zapytania */
char request[]; /* treść, nie musi wystąpić */
};
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
• 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.
struct gg_userlist_reply {
char type; /* rodzaj zapytania */
char reply[]; /* treść, nie musi wystąpić */
};
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:
Pakiety odbierane:
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
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&...
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
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.
Przykład:
pwd=sekret&email=abc@xyz.pl&tokenid=06C05A44&tokenval=e94d56&code=1104465363
Tokens okregisterreply_packet.reg.dwUserId=UIN
Przykład:
fmnumber=4969256&fmpwd=haslo&delete=1&email=deletedaccount@gadu-gadu.pl&pwd=%2D38
8046464&tokenid=06C05A44&tokenval=e94d56&code=1483497094
reg_success:UIN
reg_success:UIN
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.
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ę:
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.
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.
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;
};
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.
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.
struct gg_dcc_request {
int type; /* GG_DCC_REQUEST_VOICE */
};
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.
struct gg_dcc_voice_data {
char type; /* GG_DCC_VOICE_DATA */
int length; /* długość pakietu */
char data[]; /* dane */
};
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:
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 */
};
struct {
int type; /* 0x0006 GG_DCC_GIMME_FILE */
int start; /* od której pozycji zacząć przesyłanie */
int unknown; /* 0 */
};
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.