84 lines
3 KiB
Go
84 lines
3 KiB
Go
package rpc
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
|
|
"github.com/gotd/td/bin"
|
|
"github.com/gotd/td/tg"
|
|
)
|
|
|
|
// rpcRequestFingerprint 在任何自动实体补全、链接预览解析或上传媒体落库之前,
|
|
// 对客户端原始 TL request 取稳定指纹。这样 lost-response 重放不会因服务端派生
|
|
// photo/document id、pending webpage 状态等变化而被误判为另一条消息。
|
|
func rpcRequestFingerprint(req bin.Encoder) ([]byte, error) {
|
|
if req == nil {
|
|
return nil, fmt.Errorf("fingerprint rpc request: nil request")
|
|
}
|
|
var b bin.Buffer
|
|
if err := req.Encode(&b); err != nil {
|
|
return nil, fmt.Errorf("fingerprint rpc request: %w", err)
|
|
}
|
|
sum := sha256.Sum256(b.Raw())
|
|
return sum[:], nil
|
|
}
|
|
|
|
// sendMessageIdempotencyFingerprint fingerprints only the durable message intent.
|
|
// clear_draft/background/update_stickersets_order are one-shot client-side delivery
|
|
// hints: after a lost response DrKLO/TDesktop may legitimately retry the same
|
|
// random_id without them. They must not turn an exact send replay into
|
|
// RANDOM_ID_DUPLICATE.
|
|
func sendMessageIdempotencyFingerprint(req *tg.MessagesSendMessageRequest) ([]byte, error) {
|
|
if req == nil {
|
|
return nil, fmt.Errorf("fingerprint messages.sendMessage: nil request")
|
|
}
|
|
clone := *req
|
|
clone.Flags = 0
|
|
clone.ClearDraft = false
|
|
clone.Background = false
|
|
clone.UpdateStickersetsOrder = false
|
|
return rpcRequestFingerprint(&clone)
|
|
}
|
|
|
|
func sendMediaIdempotencyFingerprint(req *tg.MessagesSendMediaRequest) ([]byte, error) {
|
|
if req == nil {
|
|
return nil, fmt.Errorf("fingerprint messages.sendMedia: nil request")
|
|
}
|
|
clone := *req
|
|
clone.Flags = 0
|
|
clone.ClearDraft = false
|
|
clone.Background = false
|
|
clone.UpdateStickersetsOrder = false
|
|
return rpcRequestFingerprint(&clone)
|
|
}
|
|
|
|
// sendMultiMediaItemIdempotencyFingerprint deliberately reduces a batch to one
|
|
// InputSingleMedia. A retry containing only the failed subset therefore produces
|
|
// the same fingerprint for every surviving random_id as the original batch.
|
|
func sendMultiMediaItemIdempotencyFingerprint(req *tg.MessagesSendMultiMediaRequest, item tg.InputSingleMedia) ([]byte, error) {
|
|
if req == nil {
|
|
return nil, fmt.Errorf("fingerprint messages.sendMultiMedia item: nil request")
|
|
}
|
|
clone := *req
|
|
clone.Flags = 0
|
|
clone.ClearDraft = false
|
|
clone.Background = false
|
|
clone.UpdateStickersetsOrder = false
|
|
clone.MultiMedia = []tg.InputSingleMedia{item}
|
|
return rpcRequestFingerprint(&clone)
|
|
}
|
|
|
|
// forwardMessagesItemIdempotencyFingerprint makes the source message id and its
|
|
// paired random_id the unit of idempotency. Hashing the full ID/RandomID vectors
|
|
// incorrectly rejects a legal retry that contains only a failed subset.
|
|
func forwardMessagesItemIdempotencyFingerprint(req *tg.MessagesForwardMessagesRequest, messageID int, randomID int64) ([]byte, error) {
|
|
if req == nil {
|
|
return nil, fmt.Errorf("fingerprint messages.forwardMessages item: nil request")
|
|
}
|
|
clone := *req
|
|
clone.Flags = 0
|
|
clone.Background = false
|
|
clone.ID = []int{messageID}
|
|
clone.RandomID = []int64{randomID}
|
|
return rpcRequestFingerprint(&clone)
|
|
}
|