ICQ2000 сделай сам 7
|
Надпись на могиле аськера: <Он добавил мир в игнор-лист>.
|
Для разбора конкретного примера возьмем ситуацию, когда мы запрашиваем у ICQ-сервера оффлайновые сообщения (т.е. те, которые накопились на сервере, пока нас не было в онлайне).
Запрос оффлайновых сообщений делаем с помощью SNAC(15,2), а ответ на него получим соответственно в SNAC(15,3). Оба этих SNACa имеют очень простой формат. Они содержат в себе только по одному TLV, а именно TLV(1). На первый взгляд все очень просто. Но... TLV(1), в свою очередь, имеет очень ветвистую структуру. (Такие особенности имеют и некоторые другие SNACи, например, SNAC(4,6) для передачи и SNAC(4,7) для приема сообщений).
В заметках к протоколу ICQv7 от Massimo Melina есть описание SNAC(15,2). Этот SNAC используется во множестве различных запросов. Я лишь выделю те строки, которые будут включены в наш запрос, а именно:
- заголовок самого SNAC(15,2);
- TLV(1), который включает в себя:
- длину, следующих далее данных,
- наш UIN,
- тип запроса ($3С00),
- cookie (по которому мы узнаем ответный SNAC(15,3) ).
В описании это находится вот здесь:
SNAC 15,02
TLV(1)
WORD (LE) bytes remaining, useless
UIN my uin
WORD type
WORD cookie
type = 3C00 // ask for offlines messages
nothing
type = 3E00 // ack to offline messages,
nothing type=D007
WORD subtype
subtype=9808 xml-stype in an LNTS
LNTS '' name of required data ''
subtype=1F05 // simple query info
UIN user to request info subtype=B204 // query info about user
UIN user to request info subtype=D004 // query my info
UIN my uin
..............
..............
..............
|
В исходном коде это выглядит так:
// Get offline messages
// создаем FLAP-заголовок с Channel_ID=2 и SEQ++
tmp := CreatePacket(2,SEQ);
// добавляем SNAC-заголовок SNAC(15,2)
SNACAppend(tmp,$15,$2);
// добавляем TLV(1) ($0001-Type, $000A-Length)
PacketAppend32(tmp,dswap($0001000A));
// добавляем саму Value Для TLV(1)
PacketAppend16(tmp, swap($0800));// бесполезная длина
PacketAppend32(tmp, UIN); // наш UIN
PacketAppend16(tmp, swap($3C00));// тип запроса
PacketAppend16(tmp, swap($0200));// cookie
PacketSend(tmp);
M(Memo,'> Get offline messages');
|
Этот кусок кода сгенерирует следующий дамп:
2A 02 36 86 00 18 00 15
00 02 00 00 00 87 00 02
00 01 00 0A 08 00 XX XX
XX XX 3C 00 02 00
|
Разпишем его в табличном виде для лучшего восприятия:
FLAP |
Command Start |
2A |
Channel ID |
02 |
Sequence Number |
36 86 |
Data Field Length |
00 18 |
SNAC (15,
02) |
Family ID |
00 15 |
SubType ID |
00 02 |
Flags[0] |
00 |
Flags[1] |
00 |
Request ID |
00 87 00
02 |
TLV
(1) |
Type |
00 01 |
Length |
00 0A |
Value |
08 00
|
|
XX XX XX
XX |
наш UIN
|
3C
00 |
запрос на
оффлайновые сообщения |
02 00 |
cookie |
| |
| |
Передадим пакет и от сервера получим FLAP-пакет с таким дампом:
2A 02 74 6D 00 4D 00 15
00 03 00 01 00 87 00 02
00 01 00 3F 3D 00 XX XX
XX XX 41 00 02 00 F8 5F
F1 08 D2 07 02 0C 10 12
01 00 25 00 EF F0 E8 E2
E5 F2 0D 0A FD F2 EE 20
F2 E5 F1 F2 EE E2 EE E5
20 F1 EE EE E1 F9 E5 ED
E8 E5 20 21 21 21 0D 0A
00 00 00
|
И снова распишем его в таблицу:
FLAP |
Command Start |
2A |
Channel ID |
02 |
Sequence Number |
74 6D |
Data Field Length |
4D 00 |
SNAC (15,
03) |
Family ID |
00 15 |
SubType ID |
00 03 |
Flags[0] |
00 |
Flags[1] |
01 |
Request ID |
00 87 00 02
(такой же как и в запросе) |
TLV
(1) |
Type |
00 01 |
Length |
00 3F |
Value |
3D 00
|
|
XX XX XX XX |
наш UIN
|
41
00 |
тип:
оффлайновое сообщение |
02 00 |
cookie (как и в
запросе) |
тело сообщения
|
XX XX XX
XX |
его UIN
|
D2 07 |
год (2002)
|
02 |
месяц (февраль)
|
0C |
день (12)
|
10 |
час
(16) |
12 |
минуты (18)
|
01 |
под-тип
сообщения (обычное) |
00 |
флаги сообщения
(?) |
25
00 |
длина сообщения
(37) |
EF F0 E8 E2 E5
F2 0D 0A FD F2 EE 20 F2 E5 F1 F2 EE E2 EE E5 20
F1 EE EE E1 F9 E5 ED E8 E5 20 21 21 21 0D 0A
00 |
текст
сообщения:
"привет это тестовое сообщение
!!!" |
00 00 |
присутствют,
если сообщение единственное
|
| |
| |
В протокольных заметках я выделю ту часть описания SNAC(15,3), которая соответствует таблице:
SNAC 15,03
TLV(1)
WORD (LE) bytes remaining, useless
UIN my uin
WORD message-type
WORD cookie
message-type = 4100 // offline message
UIN his uin
WORD year (LE)
BYTE month (1=jan)
BYTE day
BYTE hour (GMT time)
BYTE minutes
BYTE msg-subtype
BYTE msg-flags
LNTS msg
WORD 0000, present only in single messages
message-type = 4200 // end of offline messages
BYTE unknown, usually 0
message-type = D007
2 BYTE unknown, usually 98 08
WORD length of the following NTS
NTS ""field-type""
field-type = DataFilesIP
6 BYTE unk, usually 2A 02 44 25 00 31
message-type = DA07
3 BYTE subtype
subtype=A4010A // wp-full-request result
wp-result-info
..............
..............
..............
subtype=B4000A // ack to remove user
empty
subtype=AA000A // ack to change password
empty
|
И "нарешти" - код для приема SNAC(15,3). Множественные комментарии, кажется тут уже излишни.
procedure TForm1.SNAC_15_3(p:PPack);
var MessageType,Cookie : word;
myUIN,hisUIN : longint;
year,month,day,hour,minute,typemes,subtypemes,lenmes : word;
tmp : PPack;
begin
// просто пролетаем над началом TLV(1)
PacketRead32(p);
PacketRead16(p);
// а дальше имена переменных объясняют больше, чем комментарии
myUIN := PacketRead32(p);
MessageType := swap(PacketRead16(p));
Cookie := swap(PacketRead16(p));
M(Memo,'< Cookie: $'+inttohex(Cookie,4));
case MessageType of
$4100: begin // OFFLINE MESSAGE
hisUIN := PacketRead32(p);
M(Memo,'< Message-Type: $'+inttohex(MessageType,4));
M(Memo,'< OFFLINE MESSAGE from UIN: '+s(hisUIN));
year := PacketRead16(p);
month := PacketRead8(p);
day := PacketRead8(p);
hour := PacketRead8(p);
minute := PacketRead8(p);
typemes := PacketRead8(p);
subtypemes := PacketRead8(p);
lenmes := PacketRead16(p);
DoMsg(false,typemes,lenmes,PCharArray(@(p^.data[p^.cursor])),
hisUIN,UTC2LT(year,month,day,hour,minute));
end;
end;
end;
|
Тут можно на недельку передохнуть...
В скором времени я добавлю такие модули:
- передача сообщений (SendMess);
- прием сообщений (MessFrom);
- информация о пользователе (UserInfo);
- поиск пользователей по разным критериям (SearchUser);
...следите за обновлениями сайта
|