feat: sync multilayer td integration
This commit is contained in:
parent
20a310f6ca
commit
766c5db992
491 changed files with 26235 additions and 35340 deletions
520
internal/rpc/layer_dispatch.go
Normal file
520
internal/rpc/layer_dispatch.go
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/iamxvbaba/td/bin"
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"go.uber.org/zap"
|
||||
|
||||
compatandroid "telesrv/internal/compat/android"
|
||||
"telesrv/internal/observability/dbtrace"
|
||||
)
|
||||
|
||||
type layerWrappersAppliedKey struct{}
|
||||
type layerAdmissionSequenceKey struct{}
|
||||
type layerRPCProfileEvidenceFreshKey struct{}
|
||||
|
||||
// WithLayerRPCProfileEvidenceFresh records whether an admitted request's
|
||||
// explicit selector is inside MTProto's mutable msg_id freshness window. A
|
||||
// stale selector still owns its immutable request/result codec, but wrapper
|
||||
// metadata, readiness and auth-key default effects must not escape into shared
|
||||
// state. mtprotoedge discovers this method through a structural interface.
|
||||
func (r *Router) WithLayerRPCProfileEvidenceFresh(ctx context.Context, fresh bool) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
return context.WithValue(ctx, layerRPCProfileEvidenceFreshKey{}, fresh)
|
||||
}
|
||||
|
||||
func layerRPCProfileEvidenceFresh(ctx context.Context) bool {
|
||||
fresh, specified := ctx.Value(layerRPCProfileEvidenceFreshKey{}).(bool)
|
||||
return !specified || fresh
|
||||
}
|
||||
|
||||
func withLayerAdmissionSequence(ctx context.Context, sequence uint64) context.Context {
|
||||
return context.WithValue(ctx, layerAdmissionSequenceKey{}, sequence)
|
||||
}
|
||||
|
||||
func layerAdmissionSequenceFrom(ctx context.Context) uint64 {
|
||||
sequence, _ := ctx.Value(layerAdmissionSequenceKey{}).(uint64)
|
||||
return sequence
|
||||
}
|
||||
|
||||
type layerRPCWrapperApplyMode uint8
|
||||
|
||||
const (
|
||||
layerRPCWrapperApplyDispatch layerRPCWrapperApplyMode = iota
|
||||
layerRPCWrapperApplyReplayRestore
|
||||
)
|
||||
|
||||
const layerRPCReplayRestoreTimeout = 5 * time.Second
|
||||
|
||||
// AdmitLayer performs generated, bounded, exact-profile admission without
|
||||
// touching auth/session stores. The MTProto edge must call it before acquiring
|
||||
// an RPC flight/cache slot or scheduling business work.
|
||||
func (r *Router) AdmitLayer(profile tg.LayerProfile, b *bin.Buffer, limits tg.LayerDecodeLimits) (tg.LayerRequest, error) {
|
||||
if r == nil || r.dispatcher == nil {
|
||||
return tg.LayerRequest{}, internalErr()
|
||||
}
|
||||
if b == nil {
|
||||
return tg.LayerRequest{}, inputRequestInvalidErr()
|
||||
}
|
||||
return r.dispatcher.AdmitLayerWithLimits(profile, b, limits)
|
||||
}
|
||||
|
||||
// AdmitDefaultLayer admits a request using an inherited auth-key profile as
|
||||
// its effective codec while still allowing an explicit invokeWithLayer in the
|
||||
// same wrapper chain to correct that default. Generated admission preserves
|
||||
// the distinction through EffectiveProfile and ProfileEvidence.
|
||||
func (r *Router) AdmitDefaultLayer(profile tg.LayerProfile, b *bin.Buffer, limits tg.LayerDecodeLimits) (tg.LayerRequest, error) {
|
||||
if r == nil || r.dispatcher == nil {
|
||||
return tg.LayerRequest{}, internalErr()
|
||||
}
|
||||
if b == nil {
|
||||
return tg.LayerRequest{}, inputRequestInvalidErr()
|
||||
}
|
||||
return r.dispatcher.AdmitDefaultLayerWithLimits(profile, b, limits)
|
||||
}
|
||||
|
||||
// registerAndroidLayerRPCAdapter installs the only client-private schema seam.
|
||||
// The generated exact switch invokes it only for an unknown innermost terminal
|
||||
// after recursively peeling every official wrapper. AdaptCanonical runs this
|
||||
// dispatcher's semantic field policies before the first generated typed
|
||||
// materialization, then core revalidates exact wire with the adapter disabled.
|
||||
func (r *Router) registerAndroidLayerRPCAdapter(d *tg.ServerDispatcher) {
|
||||
if d == nil {
|
||||
panic("rpc: register Android layer RPC adapter on nil dispatcher")
|
||||
}
|
||||
d.OnLayerRPCUnknownMethod(func(view tg.LayerRPCUnknownMethodView) (tg.LayerOutboundCall, bool, error) {
|
||||
outbound, handled, err := compatandroid.AdaptPrivateLayerRPC(view)
|
||||
if !handled {
|
||||
return tg.LayerOutboundCall{}, false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return tg.LayerOutboundCall{}, true, err
|
||||
}
|
||||
if r.log != nil {
|
||||
_, method, _ := tg.LayerSemanticName(outbound.Method())
|
||||
r.log.Info("Android private RPC admitted through generated exact adapter",
|
||||
zap.Int("profile", int(view.Profile())),
|
||||
zap.String("method", method),
|
||||
zap.String("private_wire_id", fmt.Sprintf("%#x", view.WireID())),
|
||||
zap.String("exact_wire_id", fmt.Sprintf("%#x", outbound.WireID())))
|
||||
}
|
||||
return outbound, true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// PrepareAdmittedReplay restores wrapper/client metadata and stages only the
|
||||
// connection-local delivery effects of a successful cached request. It never
|
||||
// calls the generated handler and therefore cannot repeat business side
|
||||
// effects or advance a difference cursor a second time.
|
||||
func (r *Router) PrepareAdmittedReplay(
|
||||
ctx context.Context,
|
||||
rawAuthKeyID [8]byte,
|
||||
sessionID int64,
|
||||
msgID int64,
|
||||
admissionSeq uint64,
|
||||
request tg.LayerRequest,
|
||||
) (func() error, error) {
|
||||
if r == nil || r.dispatcher == nil || request.Prepared().WireSize() <= 0 {
|
||||
return nil, inputRequestInvalidErr()
|
||||
}
|
||||
_, method, ok := tg.LayerSemanticName(request.Call().Method())
|
||||
if !ok || method == "" {
|
||||
return nil, inputRequestInvalidErr()
|
||||
}
|
||||
effects, err := r.snapshotLayerRPCWrapperEffects(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wireSize := request.Prepared().WireSize()
|
||||
profile := request.Call().Profile()
|
||||
_, profileKnown := request.EffectiveProfile()
|
||||
identity := request.Prepared().Identity()
|
||||
// The callback owns every cache/binder/store side effect and is attached to
|
||||
// the concrete rpc_result write attempt. Admission and protocol-plan commit
|
||||
// can therefore fail without publishing client metadata or readiness for a
|
||||
// response that never reached the replacement transport.
|
||||
var once sync.Once
|
||||
return func() error {
|
||||
var restoreErr error
|
||||
once.Do(func() {
|
||||
// A replay may happen after the request/read context is canceled, but it
|
||||
// must never turn slow auth/store/membership work into an unbounded
|
||||
// delivery hook. One deadline covers the complete restore transaction.
|
||||
replayCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), layerRPCReplayRestoreTimeout)
|
||||
defer cancel()
|
||||
replayCtx = withLayerAdmissionSequence(replayCtx, admissionSeq)
|
||||
replayCtx, updatesDelivery, prepareErr := r.prepareRPCDispatchContext(
|
||||
replayCtx,
|
||||
rawAuthKeyID,
|
||||
sessionID,
|
||||
wireSize,
|
||||
method,
|
||||
)
|
||||
if prepareErr != nil {
|
||||
r.log.Warn("prepare delivered exact RPC replay metadata",
|
||||
zap.String("method", method),
|
||||
zap.Int64("session_id", sessionID),
|
||||
zap.Error(prepareErr))
|
||||
restoreErr = fmt.Errorf("prepare delivered exact RPC replay metadata: %w", prepareErr)
|
||||
return
|
||||
}
|
||||
replayCtx = r.applyLayerRPCWrapperEffects(replayCtx, profile, profileKnown, identity, effects, msgID, admissionSeq, layerRPCWrapperApplyReplayRestore)
|
||||
if profileKnown && layerRPCProfileEvidenceFresh(replayCtx) {
|
||||
r.maybeMarkSessionReceivesUpdates(replayCtx)
|
||||
} else {
|
||||
updatesDelivery.suppressSessionActivation()
|
||||
}
|
||||
if updatesDelivery.hasWork() {
|
||||
// prepareRPCDispatchContext normally snapshots a cancellation-free
|
||||
// base for ordinary post-response work. Replay restoration is a
|
||||
// stricter ordered barrier, so every phase shares the overall deadline.
|
||||
updatesDelivery.baseCtx = replayCtx
|
||||
r.runUpdatesDeliveryPlan(updatesDelivery.snapshot())
|
||||
}
|
||||
if err := replayCtx.Err(); err != nil {
|
||||
restoreErr = fmt.Errorf("restore delivered exact RPC replay metadata: %w", err)
|
||||
}
|
||||
})
|
||||
return restoreErr
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AdmitUnprofiled is the only first-request admission path. Generated code
|
||||
// either obtains authoritative profile evidence from invokeWithLayer or admits
|
||||
// a closed terminal whose complete request and result wire graphs were proven
|
||||
// invariant across every generated profile. The latter never freezes a layer.
|
||||
func (r *Router) AdmitUnprofiled(b *bin.Buffer, limits tg.LayerDecodeLimits) (tg.LayerRequest, error) {
|
||||
if r == nil || r.dispatcher == nil {
|
||||
return tg.LayerRequest{}, internalErr()
|
||||
}
|
||||
return r.dispatcher.AdmitUnprofiledWithLimits(b, limits)
|
||||
}
|
||||
|
||||
// DispatchAdmitted executes one generated admission lease. invokeAfterMsg(s)
|
||||
// dependencies must already have reached a terminal business outcome at the
|
||||
// MTProto edge before this method is called.
|
||||
func (r *Router) DispatchAdmitted(
|
||||
ctx context.Context,
|
||||
rawAuthKeyID [8]byte,
|
||||
sessionID int64,
|
||||
msgID int64,
|
||||
admissionSeq uint64,
|
||||
request tg.LayerRequest,
|
||||
) (tg.LayerRPCResult, string, error) {
|
||||
if r == nil || r.dispatcher == nil {
|
||||
return nil, "", internalErr()
|
||||
}
|
||||
prepared := request.Prepared()
|
||||
call := request.Call()
|
||||
category, method, ok := tg.LayerSemanticName(call.Method())
|
||||
if !ok || category != "function" || method == "" || prepared.WireSize() <= 0 {
|
||||
return nil, "", inputRequestInvalidErr()
|
||||
}
|
||||
_, profileKnown := request.EffectiveProfile()
|
||||
profileEvidenceFresh := layerRPCProfileEvidenceFresh(ctx)
|
||||
ctx = withLayerAdmissionSequence(ctx, admissionSeq)
|
||||
ctx, updatesDelivery, err := r.prepareRPCDispatchContext(ctx, rawAuthKeyID, sessionID, prepared.WireSize(), method)
|
||||
if err != nil {
|
||||
return nil, method, err
|
||||
}
|
||||
ctx, err = r.applyLayerRPCWrappers(ctx, msgID, admissionSeq, request)
|
||||
if err != nil {
|
||||
return nil, method, err
|
||||
}
|
||||
if !r.dispatcher.HasLayerRPCHandler(call.Method()) {
|
||||
fields := append([]zap.Field{
|
||||
zap.String("method", method),
|
||||
zap.String("type_id", fmt.Sprintf("%#x", call.WireID())),
|
||||
zap.Int("profile", int(call.Profile())),
|
||||
}, r.contextLogFields(ctx)...)
|
||||
if r.log != nil {
|
||||
r.log.Warn("Unhandled RPC (compatibility trace)", fields...)
|
||||
}
|
||||
return nil, method, notImplementedErr()
|
||||
}
|
||||
canonicalID, hasCanonicalID := tg.LayerWireID(tg.LayerProfileCanonical, call.Method())
|
||||
if !hasCanonicalID {
|
||||
return nil, method, inputRequestInvalidErr()
|
||||
}
|
||||
if r.deps.Auth != nil {
|
||||
if _, authorized := UserIDFrom(ctx); !authorized && !rpcAllowedWithoutAuthorization(canonicalID) {
|
||||
fields := append([]zap.Field{
|
||||
zap.String("method", method),
|
||||
zap.String("type_id", fmt.Sprintf("%#x", call.WireID())),
|
||||
zap.Int("profile", int(call.Profile())),
|
||||
}, r.contextLogFields(ctx)...)
|
||||
r.log.Info("RPC rejected before authorization", fields...)
|
||||
return nil, method, authKeyUnregisteredErr()
|
||||
}
|
||||
}
|
||||
if profileKnown && profileEvidenceFresh {
|
||||
r.maybeMarkSessionReceivesUpdates(ctx)
|
||||
}
|
||||
dbBefore := dbtrace.SnapshotFromContext(ctx)
|
||||
start := time.Now()
|
||||
result, err := r.dispatcher.DispatchAdmitted(ctx, request)
|
||||
dur := time.Since(start)
|
||||
dbDelta := dbtrace.SnapshotFromContext(ctx).Sub(dbBefore)
|
||||
fields := append([]zap.Field{
|
||||
zap.String("method", method),
|
||||
zap.String("type_id", fmt.Sprintf("%#x", call.WireID())),
|
||||
zap.Int("profile", int(call.Profile())),
|
||||
zap.Duration("dur", dur),
|
||||
}, r.contextLogFields(ctx)...)
|
||||
fields = dbtrace.AppendZapFields(fields, "handler_", dbDelta)
|
||||
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...)
|
||||
}
|
||||
if err == nil && result != nil {
|
||||
if !profileKnown || !profileEvidenceFresh {
|
||||
// A generated wire-invariant terminal can safely complete before the
|
||||
// client profile is known, but it is not evidence that this physical
|
||||
// session can consume proactive updates. Preserve delivery-gated cursor
|
||||
// and secret-event facts while withholding readiness/bootstrap effects.
|
||||
updatesDelivery.suppressSessionActivation()
|
||||
}
|
||||
r.registerUpdatesDeliveryPlan(ctx, updatesDelivery)
|
||||
}
|
||||
return result, method, err
|
||||
}
|
||||
|
||||
type layerRPCWrapperEffect struct {
|
||||
semantic tg.LayerSemanticID
|
||||
layer int
|
||||
info ClientInfo
|
||||
}
|
||||
|
||||
func (r *Router) snapshotLayerRPCWrapperEffects(ctx context.Context, request tg.LayerRequest) ([]layerRPCWrapperEffect, error) {
|
||||
if err := r.validateLayerRPCWrappers(ctx, request); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profile := request.Call().Profile()
|
||||
evidenceProfile, hasProfileEvidence := request.ProfileEvidence()
|
||||
if hasProfileEvidence && evidenceProfile != profile {
|
||||
return nil, inputRequestInvalidErr()
|
||||
}
|
||||
effectiveProfile, hasEffectiveProfile := request.EffectiveProfile()
|
||||
if hasEffectiveProfile && effectiveProfile != profile {
|
||||
return nil, inputRequestInvalidErr()
|
||||
}
|
||||
if !hasEffectiveProfile && request.WrapperCount() != 0 {
|
||||
// The generated no-profile route is deliberately a closed terminal. A
|
||||
// wrapper chain without an authoritative selector must never smuggle the
|
||||
// canonical representative into client/session metadata.
|
||||
return nil, inputRequestInvalidErr()
|
||||
}
|
||||
effects := make([]layerRPCWrapperEffect, 0, request.WrapperCount())
|
||||
for index := 0; index < request.WrapperCount(); index++ {
|
||||
wrapper, _ := request.Wrapper(index)
|
||||
effect := layerRPCWrapperEffect{semantic: wrapper.Semantic()}
|
||||
switch wrapper.Semantic() {
|
||||
case tg.LayerSemanticMethodInvokeWithLayer:
|
||||
layer, err := layerWrapperRequired[int](wrapper, "layer")
|
||||
if err != nil || layer != int(profile) {
|
||||
return nil, inputRequestInvalidErr()
|
||||
}
|
||||
effect.layer = layer
|
||||
case tg.LayerSemanticMethodInitConnection:
|
||||
info, err := clientInfoFromLayerWrapper(wrapper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
effect.info = info
|
||||
case tg.LayerSemanticMethodInvokeAfterMsg, tg.LayerSemanticMethodInvokeAfterMsgs:
|
||||
// Dependency completion is an MTProto message-lifecycle fact. The edge
|
||||
// validates it before scheduling this one-shot admission lease.
|
||||
}
|
||||
effects = append(effects, effect)
|
||||
}
|
||||
return effects, nil
|
||||
}
|
||||
|
||||
func (r *Router) applyLayerRPCWrapperEffects(
|
||||
ctx context.Context,
|
||||
profile tg.LayerProfile,
|
||||
profileKnown bool,
|
||||
identity tg.LayerPreparedCallIdentity,
|
||||
effects []layerRPCWrapperEffect,
|
||||
msgID int64,
|
||||
admissionSeq uint64,
|
||||
mode layerRPCWrapperApplyMode,
|
||||
) context.Context {
|
||||
mutableEffectsCurrent := layerRPCProfileEvidenceFresh(ctx)
|
||||
rawAuthKeyID, hasRawAuthKeyID := RawAuthKeyIDFrom(ctx)
|
||||
sessionID, hasSessionID := SessionIDFrom(ctx)
|
||||
if msgID > 0 && hasRawAuthKeyID && hasSessionID {
|
||||
if currentLayer, currentMsgID, exists := r.NegotiatedSessionLayerEvidence(rawAuthKeyID, sessionID); exists {
|
||||
if currentMsgID > msgID || (currentMsgID == msgID && currentLayer != int(profile)) {
|
||||
mutableEffectsCurrent = false
|
||||
}
|
||||
}
|
||||
if mutableEffectsCurrent && hasMutableLayerRPCWrapperEffect(effects) {
|
||||
mutableEffectsCurrent = r.claimSessionWrapperEffects(rawAuthKeyID, sessionID, msgID)
|
||||
}
|
||||
}
|
||||
if profileKnown {
|
||||
// Handler and wrapper metadata always see the immutable admitted request
|
||||
// profile. This may intentionally differ from a later correction to the
|
||||
// mutable session default while an older request is still executing.
|
||||
ctx = WithLayer(ctx, int(profile))
|
||||
} else {
|
||||
// A wire-invariant terminal admitted with no effective profile must not
|
||||
// inherit an incidental metadata value loaded during context preparation.
|
||||
ctx = WithLayer(ctx, 0)
|
||||
}
|
||||
for _, effect := range effects {
|
||||
switch effect.semantic {
|
||||
case tg.LayerSemanticMethodInvokeWithLayer:
|
||||
ctx = WithLayer(ctx, effect.layer)
|
||||
// Shared Layer publication is an admission-time protocol effect. It has
|
||||
// already been linearized by admissionSeq before the scheduler/rewrap
|
||||
// split; handler execution and physical replay are intentionally unable
|
||||
// to move that auth-key-wide default.
|
||||
case tg.LayerSemanticMethodInvokeWithoutUpdates:
|
||||
ctx = withInvokeWithoutUpdates(ctx)
|
||||
case tg.LayerSemanticMethodInitConnection:
|
||||
if mode == layerRPCWrapperApplyReplayRestore {
|
||||
if _, exists := ClientInfoFrom(ctx); exists {
|
||||
// prepareRPCDispatchContext restored newer session/auth metadata.
|
||||
// Do not overwrite it with an older cached initConnection.
|
||||
continue
|
||||
}
|
||||
}
|
||||
ctx = WithClientInfo(ctx, effect.info)
|
||||
if !mutableEffectsCurrent {
|
||||
// Preserve request-local wrapper data for the delayed handler while
|
||||
// refusing to publish stale session/auth metadata.
|
||||
continue
|
||||
}
|
||||
r.rememberClientInfoAt(ctx, effect.info, admissionSeq)
|
||||
if r.log != nil {
|
||||
r.log.Debug("initConnection",
|
||||
zap.Int("api_id", effect.info.APIID),
|
||||
zap.String("device", effect.info.DeviceModel),
|
||||
zap.String("app_version", effect.info.AppVersion),
|
||||
zap.Int("layer", int(profile)),
|
||||
zap.String("client_type", string(ClientTypeFrom(ctx))),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.WithValue(ctx, layerWrappersAppliedKey{}, identity)
|
||||
}
|
||||
|
||||
func (r *Router) applyLayerRPCWrappers(ctx context.Context, msgID int64, admissionSeq uint64, request tg.LayerRequest) (context.Context, error) {
|
||||
effects, err := r.snapshotLayerRPCWrapperEffects(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, profileKnown := request.EffectiveProfile()
|
||||
return r.applyLayerRPCWrapperEffects(ctx, request.Call().Profile(), profileKnown, request.Prepared().Identity(), effects, msgID, admissionSeq, layerRPCWrapperApplyDispatch), nil
|
||||
}
|
||||
|
||||
func hasMutableLayerRPCWrapperEffect(effects []layerRPCWrapperEffect) bool {
|
||||
for _, effect := range effects {
|
||||
switch effect.semantic {
|
||||
case tg.LayerSemanticMethodInvokeWithLayer, tg.LayerSemanticMethodInitConnection:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Router) validateLayerRPCWrappers(ctx context.Context, request tg.LayerRequest) error {
|
||||
for index := 0; index < request.WrapperCount(); index++ {
|
||||
wrapper, _ := request.Wrapper(index)
|
||||
switch wrapper.Semantic() {
|
||||
case tg.LayerSemanticMethodInvokeWithLayer,
|
||||
tg.LayerSemanticMethodInvokeWithoutUpdates,
|
||||
tg.LayerSemanticMethodInitConnection,
|
||||
tg.LayerSemanticMethodInvokeAfterMsg,
|
||||
tg.LayerSemanticMethodInvokeAfterMsgs:
|
||||
continue
|
||||
default:
|
||||
_, name, _ := tg.LayerSemanticName(wrapper.Semantic())
|
||||
fields := append([]zap.Field{
|
||||
zap.String("wrapper", name),
|
||||
zap.String("type_id", fmt.Sprintf("%#x", wrapper.WireID())),
|
||||
zap.Int("profile", int(wrapper.Profile())),
|
||||
}, r.contextLogFields(ctx)...)
|
||||
if r.log != nil {
|
||||
r.log.Warn("Unhandled RPC wrapper (compatibility trace)", fields...)
|
||||
}
|
||||
return notImplementedErr()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) consumeLayerRPCWrappers(ctx context.Context, request tg.LayerRequest, next tg.LayerRPCNext) error {
|
||||
applied, ok := ctx.Value(layerWrappersAppliedKey{}).(tg.LayerPreparedCallIdentity)
|
||||
if !ok || applied != request.Prepared().Identity() {
|
||||
return fmt.Errorf("rpc: exact wrapper context was not applied to admitted request")
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
func layerWrapperRequired[T any](wrapper tg.LayerRPCWrapper, name string) (T, error) {
|
||||
var zero T
|
||||
value, present, ok, err := wrapper.Value(name)
|
||||
if err != nil || !ok || !present {
|
||||
return zero, inputRequestInvalidErr()
|
||||
}
|
||||
typed, ok := value.(T)
|
||||
if !ok {
|
||||
return zero, inputRequestInvalidErr()
|
||||
}
|
||||
return typed, nil
|
||||
}
|
||||
|
||||
func clientInfoFromLayerWrapper(wrapper tg.LayerRPCWrapper) (ClientInfo, error) {
|
||||
apiID, err := layerWrapperRequired[int](wrapper, "api_id")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
deviceModel, err := layerWrapperRequired[string](wrapper, "device_model")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
systemVersion, err := layerWrapperRequired[string](wrapper, "system_version")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
appVersion, err := layerWrapperRequired[string](wrapper, "app_version")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
systemLangCode, err := layerWrapperRequired[string](wrapper, "system_lang_code")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
langPack, err := layerWrapperRequired[string](wrapper, "lang_pack")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
langCode, err := layerWrapperRequired[string](wrapper, "lang_code")
|
||||
if err != nil {
|
||||
return ClientInfo{}, err
|
||||
}
|
||||
return ClientInfo{
|
||||
APIID: apiID,
|
||||
DeviceModel: deviceModel,
|
||||
SystemVersion: systemVersion,
|
||||
AppVersion: appVersion,
|
||||
SystemLangCode: systemLangCode,
|
||||
LangPack: langPack,
|
||||
LangCode: langCode,
|
||||
}, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue