Telegram MTProto Description
传输格式
当使用 TCP 进行通信时,需要确定 MTProto 数据包的边界,有两种情况:
第一种情况
+-+---+----...----+
|f|len| payload +
+-+---+----...----+
- f + len:4 字节(小端字节序),f 占 1 个比特位,对于客户端来说,请求包置 1 表明需要 quick_ack,接收包置 1 见第二种情况。
- payload:MTProto 载荷
第二种情况
+-+----...----+
|f| payload |
+-+----...----+
- f + payload: 4 字节,f 占 1 个比特位,固定为 1。payload 为需要加密的内容的 SHA256 的前 4 个字节,忽略最高位。整体称为 quick_ack,即
message_ack = sha256 | (1u << 31)
。(只有在客户端会收到来自服务端这样的响应包)。
注:quick_ack 旨在让服务端快速确认是否收到来自客户端的消息。
These MTProto transport protocols have support for quick acknowledgment. In this case, the client sets the highest-order length bit in the query packet, and the server responds with a special 4 bytes as a separate packet. They are the 32 higher-order bits of SHA256 of the encrypted portion of the packet prepended by 32 bytes from the authorization key (the same hash as computed for verifying the message key), with the most significant bit set to make clear that this is not the length of a regular server response packet; if the abridged version is used, bswap is applied to these four bytes.
MTProto 协议格式
同样,MTProto 既可以传输明文,也可以传输密文,还有传输错误,对此有三种情况:
第一种情况:明文
auth_key_id = 0 int64 |
message_id int64 |
message_data_length int32 |
message_data bytes |
- auth_key_id: 对于明文形式,这个值总是为 0,用于与加密形式区分开。
- message_id: 一个依赖于时间的数字用于标识会话中的一个消息。如果这个值能被 4 整除,表明是一个客户端消息。如果这个值对 4 取余为 1,表明是一个服务端消息。
- message_data_length: 消息长度
- message_data: 消息内容
第二种情况:密文
auth_key_id int64 |
msg_key int128 |
encrypted_data bytes |
- auth_key_id: 这个值是
sha1(auth_key)
的后 8 个字节,对于auth_key
怎么来的,见前文。 - message_key: 这个值是
sha256(auth_key + encrypted_data)
的中间 16 个字节,它能够校验数据的完整性,以及参与计算加密密钥。 - encrypted_data: 加密的内容,采用
AES-IGE
加解密,密钥和初始向量由auth_key
和message_key
推导出来。当对端收到报文时可通过auth_key_id
找到关联的auth_key
,而message_key
就在报文中。
salt int64 |
session_id int64 |
message_id int64 |
seq_no int32 |
message_data_length int32 |
message_data bytes |
padding12..1024 bytes |
- salt: 每 30 分钟变化一次,对于使用旧值的消息在 30 分钟内仍然有效,防止重现攻击。
- session_id: 由客户端生成用于区分不同的实例。
- message_id: 同上。
- seq_no: 对于客户端来说,它的值是那些要求 ack 的消息数的两倍。而对于服务端来说,如果它的值是奇数,表明客户端需要回复 ack。关于 ack 见引用。
- message_data_length: 同上。
- message_data: 同上。
- padding: 填充数据以达到 AES 要求。
Acknowledgment of Receipt
Receipt of virtually all messages (with the exception of some purely service ones as well as the plain-text messages used in the protocol for creating an authorization key) must be acknowledged. This requires the use of the following service message (not requiring an acknowledgment):
msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck;
A server usually acknowledges the receipt of a message from a client (normally, an RPC query) using an RPC response. If a response is a long time coming, a server may first send a receipt acknowledgment, and somewhat later, the RPC response itself.
A client normally acknowledges the receipt of a message from a server (usually, an RPC response) by adding an acknowledgment to the next RPC query if it is not transmitted too late (if it is generated, say, 60-120 seconds following the receipt of a message from the server). However, if for a long period of time there is no reason to send messages to the server or if there is a large number of unacknowledged messages from the server (say, over 16), the client transmits a stand-alone acknowledgment.
Max 8192 IDs are allowed per constructor.
第三种情况:传输错误报告
当遇到传输错误时,比如丢失 auth_key
等,服务端会发一个 4 字节的数据包来表示错误,它的绝对值表示错误代码。
code int32 |
[quick_ack] int32 |
- 如果
code == 0
表示空操作 - 如果
code == -1
表示存在quick_ack
- 其它情况,认为
code
为错误代码。
Result<Transport::ReadResult> Transport::read(MutableSlice message, ...) {
if (message.size() < 12) {
if (message.size() < 4) {
return Status::Error(PSLICE() << "Invalid MTProto message: smaller than 4 bytes");
}
int32 code = as<int32>(message.begin());
if (code == 0) {
return ReadResult::make_nop();
} else if (code == -1 && message.size() >= 8) {
return ReadResult::make_quick_ack(as<uint32>(message.begin() + 4));
} else {
return ReadResult::make_error(code);
}
}
// ...
}