perf: sync dispatch hot path optimizations

This commit is contained in:
A 2026-07-06 14:27:03 +08:00
parent 03b785ebf4
commit 7e64d9c30e
13 changed files with 824 additions and 215 deletions

View file

@ -0,0 +1,93 @@
package mtprotoedge
import (
"crypto/aes"
"encoding/binary"
"errors"
"fmt"
"github.com/gotd/ige"
"github.com/gotd/td/bin"
"github.com/gotd/td/crypto"
)
// clientFrame 是解密后的单帧客户端消息视图。data/plaintext 引用调用方持有的复用明文
// 缓冲,仅在解密下一帧前有效;需要跨帧保留的字节必须拷贝(dispatch 对 RPC body 已
// b.Copy(),container/gzip 均在当前帧内同步消费)。
type clientFrame struct {
salt int64
sessionID int64
messageID int64
seqNo int32
// data 是去掉 32 字节头与 padding 后的消息体。
data []byte
// plaintext 是完整明文(头 + 数据 + padding),quick ack token 直接对它做 SHA256,
// 免去 gotd 路径上「为算 token 把解密结果整帧重编码一遍」的拷贝。
plaintext []byte
}
// encryptedFrameHeaderLen 是 MTProto 2.0 明文头长度:salt(8)+session_id(8)+msg_id(8)+seq_no(4)+len(4)。
const encryptedFrameHeaderLen = 32
// decryptClientFrame 把一帧客户端加密消息解密进 plain 复用缓冲(telesrv-owned,
// 与出站 encryptOutboundFrame 对称)。校验集合与 gotd Cipher.Decrypt 逐条一致:
// auth_key_id 匹配、密文 16 字节块对齐、msg_key(SHA256,client side x=0)、明文头
// 长度、message_data_len 非负 / 4 字节对齐 / 不越界、padding ≤ 1024。区别只在
// 明文缓冲复用与免结构体堆分配——gotd DecryptFromBuffer 每帧 make 整帧明文
// (上传帧可达 512KiB+),是入站 Dispatch 链路最大的稳态分配点。
func decryptClientFrame(key crypto.AuthKey, b *bin.Buffer, plain *bin.Buffer) (clientFrame, error) {
buf := b.Buf
if len(buf) < 24 {
return clientFrame{}, errors.New("encrypted message is too short")
}
var authKeyID [8]byte
copy(authKeyID[:], buf[:8])
if authKeyID != key.ID {
return clientFrame{}, errors.New("unknown auth key id")
}
var msgKey bin.Int128
copy(msgKey[:], buf[8:24])
encrypted := buf[24:]
if len(encrypted) == 0 || len(encrypted)%16 != 0 {
return clientFrame{}, errors.New("invalid encrypted data padding")
}
aesKey, iv := crypto.Keys(key.Value, msgKey, crypto.Client)
aesBlock, err := aes.NewCipher(aesKey[:])
if err != nil {
return clientFrame{}, err
}
ensureBinBufferLen(plain, len(encrypted))
ige.DecryptBlocks(aesBlock, iv[:], plain.Buf, encrypted)
plaintext := plain.Buf
if crypto.MessageKey(key.Value, plaintext, crypto.Client) != msgKey {
return clientFrame{}, errors.New("msg_key is invalid")
}
if len(plaintext) < encryptedFrameHeaderLen {
return clientFrame{}, errors.New("message data is too short")
}
dataLen := int(int32(binary.LittleEndian.Uint32(plaintext[28:32])))
withPadding := plaintext[encryptedFrameHeaderLen:]
switch {
case dataLen < 0:
return clientFrame{}, fmt.Errorf("message length is invalid: %d less than zero", dataLen)
case dataLen%4 != 0:
return clientFrame{}, fmt.Errorf("message length is invalid: %d is not divisible by 4", dataLen)
case dataLen > len(withPadding):
return clientFrame{}, fmt.Errorf("message length %d is bigger than data length %d", dataLen, len(withPadding))
case len(withPadding)-dataLen > 1024:
return clientFrame{}, fmt.Errorf("padding %d of message is too big", len(withPadding)-dataLen)
}
return clientFrame{
salt: int64(binary.LittleEndian.Uint64(plaintext[0:8])),
sessionID: int64(binary.LittleEndian.Uint64(plaintext[8:16])),
messageID: int64(binary.LittleEndian.Uint64(plaintext[16:24])),
seqNo: int32(binary.LittleEndian.Uint32(plaintext[24:28])),
data: withPadding[:dataLen],
plaintext: plaintext,
}, nil
}