Лежат двое влюбленных в постели, утомленные первым бурным соитием.
Она:
- Милый, а ты помнишь, когда мы с тобой познакомились?
Он:
- Погоди... ща отдышусь и пойду хистори в аське посмотрю.
Перед рассмотрением работы обработчика AuthorizePart надо немного поговорить и о протоколе.
Перед тем, как подключиться к ICQ-серверу и начать работать мы должны пройти авторизацию на Authorization Server. Его адрес - login.icq.com:5190.
Необходимо:
соединиться с Authorization Server;
передать ему пакет с UINом и паролем;
получить от него IP-адрес и порт основного сервера и Cookie (256 байт случайных данных). Cookie - это будет наш пропуск при последующем (после авторизации) коннекте к основному рабочему серверу;
разьединиться с Authorization Server.
Именно к Authorization Server инициируется соединение в процедуре icq_Login.
Сервер отвечает нам маленьким пакетом:
FLAP
Command Start
2A
Channel ID
01
Sequence Number
XX XX
Data Field Length
00 04
Data
00 00 00 01
В нем только лишь 00 00 00 01. Для нас - это сигнал начать передачу пакета с авторизационными данными (с UINом и паролем).
Сейчас уже пора разобраться и с форматом блока данных FLAP-пакета.
Можно сказать, что показанный выше пакет совсем не имеет никакой структуры: просто DWORD и все. В большинстве случаев в FLAP-пакете размещены данные, которые упакованы еще в один протокол: т.н. SNAC. В этом случае пакет данных выглядит так:
FLAP
Command Start
2A
Channel ID
02
Sequence Number
word
Data Field Length
word
SNAC
Family ID
word
SubType ID
word
Flags[0]
byte
Flags[1]
byte
Request ID
dword
SNAC Data
variable
SNAC
SNAC - это обычное содержимое блока данных FLAP-пакета в основной рабочей фазе соединения. Т.е. SNACи посылаются только через Сhannel ID = 2.
В любом FLAP-пакете может находиться только один пакет SNAC.
Прием (анализ) и передача SCACов - это то основное, что предстоит делать, чтобы реализовать все функции ICQ-клиента. Будь то передача списка контактов, или изменение нашего статуса, или получение и передача сообщений, или запрос информации о любом клиенте, для любого запроса и ответа на него есть свой SNAC (FamilyID, SubTypeID).
Из сказанного видно, что вся смысловая информация помещена в SNACи.
И UINы, и никнэймы, и и-мэйлы с хоумпэйджами. Конечно же они не просто так накиданы в SNACи. Они там размещены в юнитах, которые называются TLV.
TLV
TLV дословно означает - "Type, Length, Value" ("Тип, Длина, Значение"). Его структура такая:
TLV
(T)ype code
word
(L)ength code
word
(V)alue field
variable length
В TLV упаковывается все, что используется в ICQ-протоколе: текстовые строки, байты, слова, двойные слова, другие массивы и т.д. и т.п.. На тип содеожимого TLV указывает Type code. Чаще всего TLV располагаются внутри SNACов, но это не является обязательным условием. Они могут также напрямую использоваться в блоке данных FLAP-пакета. Именно напрямую (т.е. без использования SNACов) TLV задействованы на этапе авторизации.
Этот механизм мы и рассмотрим именно сейчас, т.к. мы соединены уже с Authorization Server и получили от него добро в виде DWORD=00000001 на передачу нашего UINа и пароля.
procedure TForm1.AuthorizePart(p:PPack);
var ss : string;
T : integer;
tmp : PPack;
begin// позиционируемся на начало блока данных, пропустив заголовок
PacketGoto(p,sizeof(FLAP_HDR));
// если FLAP-данные содержат лишь 00000001,// то это самое начало сессии if (swap(p^.Len)=4)and
(swap(p^.SNAC.FamilyID)=0)and
(swap(p^.SNAC.SubTypeID)=1) thenbegin
M(Memo,'< Authorize Server CONNECT');
// каждый раз, когда начинается новая TCP-сессия,// присваиваем SEQ случайное начальное значение
SEQ := random($7FFF); // в ответ надо передать пакет с UINом и паролем// создаем объект-пакет типа PPack: в нем формируется// FLAP-заголовок с Chanel_ID=1
tmp := CreatePacket(1,SEQ);
// сначала надо вставить такой же DWORD=00000001// (еще надо помнить о порядке следования байтов в DWORD !!!)
PacketAppend32(tmp,DSwap(1));
// далее в поле данных добавляются несколько TLV// это наш UIN - TLV(1)
TLVAppendStr(tmp,$1,s(UIN));
// и закодированный пароль - TLV(2)
TLVAppendStr(tmp,$2,Calc_Pass(PASSWORD));
// описывать содержимое других TLV особого смысла нет
TLVAppendStr(tmp,$3,
'ICQ Inc. - Product of ICQ (TM).2000a.4.31.1.3143.85');
TLVAppendWord(tmp,$16,$010A);
TLVAppendWord(tmp,$17,$0004); // 4 - для ICQ2000a
TLVAppendWord(tmp,$18,$001F);
TLVAppendWord(tmp,$19,$0001);
TLVAppendWord(tmp,$1A,$0C47);
TLVAppendDWord(tmp,$14,$00000055);
TLVAppendStr(tmp,$0F,'en');
TLVAppendStr(tmp,$0E,'us');
// посылаем пакет через ClientSocket// (здесь tmp-пакет будет также и удален)
PacketSend(tmp);
M(Memo,'> Auth Request (Login)');
endelse// на это сервер ответит так:// его ответ содержит TLV(1) - т.е. наш UINif (TLVReadStr(p,ss)=1)and(ss=s(UIN))thenbegin// если это так, то считаем следующий TLV
T := TLVReadStr(p,ss);
case T of// если это TLV(5) - значит это адрес и порт основного сервера
5: g>begin // BOS-IP:PORT
M(Memo,'< Auth Responce (COOKIE)');
// запоминаем и адрес и порт
WorkAddress := copy(ss,1,pos(':',ss)-1);
WorkPort := strtoint(copy(ss,pos(':',ss)+1,
length(ss)-pos(':',ss)));
// за ними должен быть и TLV(6) - т.н. COOKIE (256 байт)// принимаем его прямо в переменную sCOOKIE// (он пригодится при коннекте к основному серверу)if (TLVReadStr(p,sCOOKIE)=6) thenbegin;
// COOKIE получен и значит пора разъединяться// формируем пустой пакет с Channel_ID=4
tmp:=CreatePacket(4,SEQ); // ChID=4// который и передаем
PacketSend(tmp);
// закрываем свой ClientSocket
OfflineDiscconnect1Click(self);
// говорим себе, что авторизация пройдена
isAuth := false;
// настраиваем ClientSocket на адрес:порт// основного (BOS) сервера
CLI.Address := WorkAddress;
CLI.Host := '';
CLI.Port := WorkPort;
M(Memo,'');
M(Memo,'>>> Connecting to BOS: '+ss);
// и коннектимся к нему
CLI.Open;
{ ******************************************* }{ в этом месте заканчивается этап авторизации }{ ******************************************* }end;
end;
// а, например, в случае неверного UINа или пароля// мы получим TLV(4) и TLV(8)
4,8: begin
M(Memo,'< Auth ERROR');
M(Memo,'TLV($'+inttohex(T,2)+') ERROR');
M(Memo,'STRING: '+ss);
if pos('http://',ss)>0 thenbegin// и даже можем загрузить в браузер присланный нам URL// с описанием ошибки// Web.Navigate(ss); // {это навигатор с панели компонентов Делфи}end;
TLVReadStr(p,ss); M(Memo,ss);
// конечно же закрываем ClientSocket
OfflineDiscconnect1Click(self);
M(Memo,'');
end;
end;
end;
end;
После успешного прохождения авторизации, мы подключаемся к основному рабочему серверу ICQ. Т.к. флажек isAuth уже сброшен, то диспетчер MainTTimer все пакеты будет направлять на обработчик WorkPart. Его построение во многом схоже с только, что рассмотренным обработчиком AuthorizePart.
В таком случае продолжим...
Статья ICQ2000 сделай сам 5 раздела Интернет и Сети ICQ может быть полезна для разработчиков на Delphi и FreePascal.
Комментарии и вопросы
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.