owpengram-server/internal/rpc/router.go
2026-06-04 01:37:39 +08:00

416 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"encoding/hex"
"fmt"
"sync"
"time"
"go.uber.org/zap"
"golang.org/x/sync/singleflight"
"github.com/gotd/td/bin"
"github.com/gotd/td/clock"
"github.com/gotd/td/tg"
)
// maxWrapperDepth 限制 invokeWithLayer/initConnection 等 wrapper 的嵌套深度,防御恶意构造。
const maxWrapperDepth = 4
var (
tlTypeNamesOnce sync.Once
tlTypeNames map[uint32]string
)
// Config 是 Router 所需的服务端信息。
type Config struct {
DC int
IP string // 对外公布的 DC IP写入 DCOptions
Port int // 对外公布的 DC 端口
OutboundPushTimeout time.Duration
}
// Router 把解密后的 RPC 请求按 TypeID 路由到 typed handlertg.ServerDispatcher
//
// handler 输入输出均为 gotd/td/tg 类型,各业务域的 handler
// 与注册见 help.go / auth.go / users.go / updates.go。Router 本身只负责协议外壳:
// 剥离 invokeWithLayer / initConnection / invokeWithoutUpdates并兜底未注册 RPC。
type Router struct {
cfg Config
log *zap.Logger
clock clock.Clock
deps Deps
dispatcher *tg.ServerDispatcher
clientInfoMu sync.RWMutex
clientInfo map[clientInfoSessionKey]ClientInfo
authUserMu sync.RWMutex
authUsers map[[8]byte]authUserCacheEntry
authUserSF singleflight.Group
presence *presenceTracker
}
type clientInfoSessionKey struct {
rawAuthKeyID [8]byte
sessionID int64
}
type authUserCacheEntry struct {
userID int64
found bool
}
// New 创建 Router由各业务域自行注册其 RPC handlerregisterHelp/Auth/Users/Updates
func New(cfg Config, deps Deps, log *zap.Logger, clk clock.Clock) *Router {
r := &Router{cfg: cfg, log: log, clock: clk, deps: deps, presence: newPresenceTracker()}
d := tg.NewServerDispatcher(r.fallback)
r.registerHelp(d)
r.registerAuth(d)
r.registerUsers(d)
r.registerUpdates(d)
r.registerAccount(d)
r.registerMessages(d)
r.registerChannels(d)
r.registerUpload(d)
r.registerPhotos(d)
r.registerFolders(d)
r.registerContacts(d)
r.registerLangpack(d)
r.registerStories(d)
r.registerPayments(d)
r.registerStats(d)
r.registerPremium(d)
r.registerAiCompose(d)
r.dispatcher = d
return r
}
// Dispatch 路由一条 RPC 请求:先剥离 invokeWithLayer / initConnection /
// invokeWithoutUpdates 等 wrapper注入 layer / 客户端信息到 ctx
// 再按 TypeID 路由到 typed handler。满足 mtprotoedge.RPCHandler。
func (r *Router) Dispatch(ctx context.Context, authKeyID [8]byte, sessionID int64, b *bin.Buffer) (bin.Encoder, error) {
ctx = WithRawAuthKeyID(ctx, authKeyID)
effectiveAuthKeyID, err := r.effectiveAuthKeyID(ctx, authKeyID, sessionID)
if err != nil {
return nil, internalErr()
}
ctx = WithAuthKeyID(ctx, effectiveAuthKeyID)
ctx = WithSessionID(ctx, sessionID)
userID, hasUserID, err := r.effectiveUserID(ctx, authKeyID, effectiveAuthKeyID, sessionID)
if err != nil {
return nil, internalErr()
}
if hasUserID {
ctx = WithUserID(ctx, userID)
}
if info, ok := r.clientInfoForSession(ctx); ok {
ctx = WithClientInfo(ctx, info)
}
return r.dispatch(ctx, b, 0)
}
func (r *Router) effectiveAuthKeyID(ctx context.Context, rawAuthKeyID [8]byte, sessionID int64) ([8]byte, error) {
if r.deps.Sessions != nil {
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
if id, ok := scoped.AuthKeyIDForSession(rawAuthKeyID, sessionID); ok {
return id, nil
}
} else if id, ok := r.deps.Sessions.AuthKeyID(sessionID); ok {
return id, nil
}
}
effective := rawAuthKeyID
if r.deps.Auth != nil {
resolved, ok, err := r.deps.Auth.ResolveAuthKey(ctx, rawAuthKeyID)
if err != nil {
return [8]byte{}, err
}
if ok {
effective = resolved
}
}
if r.deps.Sessions != nil {
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
scoped.BindAuthKeyForSession(rawAuthKeyID, sessionID, effective)
} else {
r.deps.Sessions.BindAuthKey(sessionID, effective)
}
}
return effective, nil
}
func (r *Router) effectiveUserID(ctx context.Context, rawAuthKeyID, authKeyID [8]byte, sessionID int64) (int64, bool, error) {
if userID, ok := UserIDFrom(ctx); ok {
if scoped, ok := r.scopedSessions(); ok {
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
} else if r.deps.Sessions != nil {
r.deps.Sessions.BindUser(sessionID, userID)
}
return userID, true, nil
}
if r.deps.Sessions != nil {
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
if userID, resolved := scoped.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
return userID, userID != 0, nil
}
} else if userID, resolved := r.deps.Sessions.UserIDResolved(sessionID); resolved {
return userID, userID != 0, nil
}
}
if r.deps.Auth == nil {
return 0, false, nil
}
userID, found, err := r.lookupAuthUser(ctx, authKeyID)
if err != nil {
return 0, false, err
}
if r.deps.Sessions != nil {
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
if cachedUserID, resolved := scoped.UserIDResolvedForAuthKey(rawAuthKeyID, sessionID); resolved {
return cachedUserID, cachedUserID != 0, nil
}
} else if cachedUserID, resolved := r.deps.Sessions.UserIDResolved(sessionID); resolved {
return cachedUserID, cachedUserID != 0, nil
}
if found {
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, userID)
} else {
r.deps.Sessions.BindUser(sessionID, userID)
}
r.announceSessionOnline(ctx, userID)
} else {
if scoped, ok := r.deps.Sessions.(ScopedSessionBinder); ok {
scoped.BindUserForAuthKey(rawAuthKeyID, sessionID, 0)
} else {
r.deps.Sessions.BindUser(sessionID, 0)
}
}
}
return userID, found, nil
}
func (r *Router) lookupAuthUser(ctx context.Context, authKeyID [8]byte) (int64, bool, error) {
if userID, found, ok := r.cachedAuthUser(authKeyID); ok {
return userID, found, nil
}
key := string(authKeyID[:])
v, err, _ := r.authUserSF.Do(key, func() (any, error) {
if userID, found, ok := r.cachedAuthUser(authKeyID); ok {
return authUserCacheEntry{userID: userID, found: found}, nil
}
userID, found, err := r.deps.Auth.UserID(ctx, authKeyID)
if err != nil {
return authUserCacheEntry{}, err
}
r.setAuthUserCache(authKeyID, userID, found)
return authUserCacheEntry{userID: userID, found: found}, nil
})
if err != nil {
return 0, false, err
}
entry := v.(authUserCacheEntry)
return entry.userID, entry.found, nil
}
func (r *Router) cachedAuthUser(authKeyID [8]byte) (int64, bool, bool) {
r.authUserMu.RLock()
defer r.authUserMu.RUnlock()
entry, ok := r.authUsers[authKeyID]
if !ok {
return 0, false, false
}
return entry.userID, entry.found, true
}
func (r *Router) setAuthUserCache(authKeyID [8]byte, userID int64, found bool) {
r.authUserMu.Lock()
defer r.authUserMu.Unlock()
if r.authUsers == nil {
r.authUsers = make(map[[8]byte]authUserCacheEntry)
}
r.authUsers[authKeyID] = authUserCacheEntry{userID: userID, found: found}
}
func (r *Router) invalidateAuthUserCache(authKeyID [8]byte) {
r.authUserMu.Lock()
delete(r.authUsers, authKeyID)
r.authUserMu.Unlock()
r.authUserSF.Forget(string(authKeyID[:]))
}
func (r *Router) scopedSessions() (ScopedSessionBinder, bool) {
if r.deps.Sessions == nil {
return nil, false
}
scoped, ok := r.deps.Sessions.(ScopedSessionBinder)
return scoped, ok
}
func (r *Router) dispatch(ctx context.Context, b *bin.Buffer, depth int) (bin.Encoder, error) {
if depth > maxWrapperDepth {
return nil, wrapperTooDeepErr()
}
id, err := b.PeekID()
if err != nil {
return nil, err
}
switch id {
case tg.InvokeWithLayerRequestTypeID:
if err := b.ConsumeID(id); err != nil {
return nil, err
}
layer, err := b.Int()
if err != nil {
return nil, fmt.Errorf("decode invokeWithLayer layer: %w", err)
}
// query 紧跟 layerbuffer 剩余即内层请求。
return r.dispatch(WithLayer(ctx, layer), b, depth+1)
case tg.InvokeWithoutUpdatesRequestTypeID:
if err := b.ConsumeID(id); err != nil {
return nil, err
}
return r.dispatch(ctx, b, depth+1)
case tg.InitConnectionRequestTypeID:
req := &tg.InitConnectionRequest{Query: &rawObject{}}
if err := req.Decode(b); err != nil {
return nil, fmt.Errorf("decode initConnection: %w", err)
}
info := ClientInfo{
APIID: req.APIID,
DeviceModel: req.DeviceModel,
SystemVersion: req.SystemVersion,
AppVersion: req.AppVersion,
SystemLangCode: req.SystemLangCode,
LangPack: req.LangPack,
LangCode: req.LangCode,
}
ctx = WithClientInfo(ctx, info)
r.rememberClientInfo(ctx, info)
r.log.Debug("initConnection",
zap.Int("api_id", req.APIID),
zap.String("device", req.DeviceModel),
zap.String("app", req.AppVersion),
zap.Int("layer", LayerFrom(ctx)),
)
inner, ok := req.Query.(*rawObject)
if !ok {
return nil, fmt.Errorf("initConnection query: unexpected type %T", req.Query)
}
return r.dispatch(ctx, &bin.Buffer{Buf: inner.data}, depth+1)
default:
start := time.Now()
enc, err := r.dispatcher.Handle(ctx, b)
dur := time.Since(start)
fields := append([]zap.Field{
zap.String("method", tlTypeName(id)),
zap.String("type_id", fmt.Sprintf("%#x", id)),
zap.Duration("dur", dur),
}, r.contextLogFields(ctx)...)
if err != nil || dur > 100*time.Millisecond {
if err != nil {
fields = append(fields, zap.Error(err))
}
r.log.Info("RPC inner handled", fields...)
} else {
r.log.Debug("RPC inner handled", fields...)
}
return enc, err
}
}
func tlTypeName(id uint32) string {
tlTypeNamesOnce.Do(func() {
names := tg.NamesMap()
tlTypeNames = make(map[uint32]string, len(names))
for name, typeID := range names {
tlTypeNames[typeID] = name
}
})
if name, ok := tlTypeNames[id]; ok {
return name
}
return fmt.Sprintf("%#x", id)
}
func (r *Router) rememberClientInfo(ctx context.Context, info ClientInfo) {
rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx)
if !ok {
return
}
sessionID, ok := SessionIDFrom(ctx)
if !ok {
return
}
r.clientInfoMu.Lock()
defer r.clientInfoMu.Unlock()
if r.clientInfo == nil {
r.clientInfo = make(map[clientInfoSessionKey]ClientInfo)
}
r.clientInfo[clientInfoSessionKey{rawAuthKeyID: rawAuthKeyID, sessionID: sessionID}] = info
}
func (r *Router) clientInfoForSession(ctx context.Context) (ClientInfo, bool) {
rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx)
if !ok {
return ClientInfo{}, false
}
sessionID, ok := SessionIDFrom(ctx)
if !ok {
return ClientInfo{}, false
}
r.clientInfoMu.RLock()
defer r.clientInfoMu.RUnlock()
info, ok := r.clientInfo[clientInfoSessionKey{rawAuthKeyID: rawAuthKeyID, sessionID: sessionID}]
return info, ok
}
// fallback 处理未注册的 RPC记录到 compatibility trace落兼容矩阵
// 返回 NOT_IMPLEMENTED rpc_error 让客户端继续运行而非断连。
func (r *Router) fallback(ctx context.Context, b *bin.Buffer) (bin.Encoder, error) {
id, _ := b.PeekID()
fields := append([]zap.Field{zap.String("type_id", fmt.Sprintf("%#x", id))}, r.contextLogFields(ctx)...)
r.log.Warn("Unhandled RPC (compatibility trace)", fields...)
return nil, notImplementedErr()
}
func (r *Router) contextLogFields(ctx context.Context) []zap.Field {
fields := []zap.Field{zap.Int("layer", LayerFrom(ctx))}
if sessionID, ok := SessionIDFrom(ctx); ok {
fields = append(fields, zap.Int64("session_id", sessionID))
}
if rawAuthKeyID, ok := RawAuthKeyIDFrom(ctx); ok {
fields = append(fields, zap.String("raw_auth_key_id", hex.EncodeToString(rawAuthKeyID[:])))
}
if authKeyID, ok := AuthKeyIDFrom(ctx); ok {
fields = append(fields, zap.String("auth_key_id", hex.EncodeToString(authKeyID[:])))
}
if userID, ok := UserIDFrom(ctx); ok {
fields = append(fields, zap.Int64("user_id", userID))
}
return fields
}
// rawObject 在解码 wrapper 时按原样捕获内层 query 的 TL 字节,供递归分发。
// 它实现 bin.ObjectEncode/Decode但只搬运字节、不解释内容。
type rawObject struct {
data []byte
}
func (o *rawObject) Decode(b *bin.Buffer) error {
o.data = append(o.data[:0], b.Buf...)
b.Skip(len(b.Buf))
return nil
}
func (o *rawObject) Encode(b *bin.Buffer) error {
b.Put(o.data)
return nil
}