package mtprotoedge import ( "context" "fmt" "math" "github.com/iamxvbaba/td/tlprofile" "go.uber.org/zap" ) // LayerProfileOrigin records why a Conn currently uses a wire profile. The // distinction is protocol-significant: inherited auth-key metadata is a useful // availability default, while explicit invokeWithLayer evidence may correct it. type LayerProfileOrigin uint8 const ( LayerProfileUnknown LayerProfileOrigin = iota LayerProfileInherited LayerProfileExplicit ) // LayerProfileSnapshot is one atomic observation of a connection's current // profile. Epoch advances on every effective correction, including promotion // from inherited to explicit evidence at the same numeric layer. type LayerProfileSnapshot struct { Profile tlprofile.Profile Origin LayerProfileOrigin Epoch uint32 } const ( layerProfileValueBits = 16 layerProfileOriginBits = 8 layerProfileOriginShift = layerProfileValueBits layerProfileEpochShift = 32 layerProfileValueMask = uint64(1<> layerProfileOriginShift) & layerProfileOriginMask), Epoch: uint32(raw >> layerProfileEpochShift), } } // LayerProfileState returns profile, provenance and epoch from one atomic load. func (c *Conn) LayerProfileState() LayerProfileSnapshot { if c == nil { return LayerProfileSnapshot{} } return unpackLayerProfileState(c.layerProfileState.Load()) } func (c *Conn) setLayerProfile(profile tlprofile.Profile, origin LayerProfileOrigin, replace bool) (bool, error) { if err := validateLayerProfile(profile); err != nil { return false, err } if origin != LayerProfileInherited && origin != LayerProfileExplicit { return false, fmt.Errorf("invalid layer profile origin %d", origin) } c.layerProfileMu.Lock() defer c.layerProfileMu.Unlock() for { raw := c.layerProfileState.Load() current := unpackLayerProfileState(raw) if origin == LayerProfileInherited && c.layerProfileEvidenceMsgID > 0 { return false, nil } if current.Origin != LayerProfileUnknown { if !replace { return false, nil } if current.Profile == profile && current.Origin == origin { // The force-style compatibility APIs carry no ordered msg_id. // Clearing the cursor keeps a subsequent production observation // eligible instead of comparing it with unrelated test/recovery state. c.layerProfileEvidenceMsgID = 0 c.layerProfileEvidenceLayer = 0 return false, nil } } if current.Epoch == math.MaxUint32 { return false, ErrLayerProfileEpochExhausted } next := LayerProfileSnapshot{Profile: profile, Origin: origin, Epoch: current.Epoch + 1} if c.layerProfileState.CompareAndSwap(raw, packLayerProfileState(next)) { c.layerProfileEvidenceMsgID = 0 c.layerProfileEvidenceLayer = 0 c.setLegacyClientLayer(int(profile)) return true, nil } } } func validateLayerProfile(profile tlprofile.Profile) error { resolved, ok := tlprofile.ResolveProfile(int(profile)) if !ok || resolved != profile || uint64(profile) > layerProfileValueMask { return fmt.Errorf("%w: %d", ErrLayerProfileUnsupported, profile) } return nil } // layerProfileEvidenceState observes the packed wire state and its ordering // cursor under one read lock. LayerProfileState remains the allocation-free // atomic hot-path accessor used by outbound encoding. func (c *Conn) layerProfileEvidenceState() (LayerProfileSnapshot, int64) { state, _, msgID := c.layerProfileRawEvidenceState() return state, msgID } func (c *Conn) layerProfileRawEvidenceState() (LayerProfileSnapshot, int, int64) { if c == nil { return LayerProfileSnapshot{}, 0, 0 } c.layerProfileMu.RLock() state := unpackLayerProfileState(c.layerProfileState.Load()) layer := c.layerProfileEvidenceLayer msgID := c.layerProfileEvidenceMsgID c.layerProfileMu.RUnlock() if layer == 0 && state.Origin == LayerProfileExplicit { layer = int(state.Profile) } return state, layer, msgID } // freezeLayerProfileAt is the production explicit-evidence transition. The // positive client msg_id is the protocol ordering authority across TCP // reconnects and retained request replays. func (c *Conn) freezeLayerProfileAt(profile tlprofile.Profile, msgID int64) (bool, error) { if c == nil { return false, fmt.Errorf("nil connection layer profile") } if msgID <= 0 { return false, fmt.Errorf("invalid layer evidence msg_id %d", msgID) } if err := validateLayerProfile(profile); err != nil { return false, err } return c.freezeRawLayerProfileAt(int(profile), msgID) } func (c *Conn) freezeRawLayerProfileAt(layer int, msgID int64) (bool, error) { if c == nil { return false, fmt.Errorf("nil connection layer profile") } if layer <= 0 || msgID <= 0 { return false, fmt.Errorf("invalid raw layer evidence layer=%d msg_id=%d", layer, msgID) } profile, supported := tlprofile.ResolveProfile(layer) c.layerProfileMu.Lock() defer c.layerProfileMu.Unlock() current := unpackLayerProfileState(c.layerProfileState.Load()) if c.layerProfileEvidenceMsgID > 0 { switch { case msgID < c.layerProfileEvidenceMsgID: return false, nil case msgID == c.layerProfileEvidenceMsgID: if c.layerProfileEvidenceLayer != layer { return false, fmt.Errorf("%w: msg_id %d selected both layer %d and %d", ErrLayerProfileConflict, msgID, c.layerProfileEvidenceLayer, layer) } return false, nil } } desired := LayerProfileSnapshot{Epoch: current.Epoch} if supported { desired.Profile = profile desired.Origin = LayerProfileExplicit } // A newer proof at the same Layer advances only the ordering cursor. No wire // bytes prepared under this profile become stale, so rotating epoch would be // unnecessary push churn. if current.Profile == desired.Profile && current.Origin == desired.Origin { c.layerProfileEvidenceLayer = layer c.layerProfileEvidenceMsgID = msgID return true, nil } if current.Epoch == math.MaxUint32 { return false, ErrLayerProfileEpochExhausted } next := desired next.Epoch = current.Epoch + 1 c.layerProfileState.Store(packLayerProfileState(next)) c.layerProfileEvidenceLayer = layer c.layerProfileEvidenceMsgID = msgID if supported { c.setLegacyClientLayer(layer) } else { c.setLegacyClientLayer(0) } return true, nil } // seedOrderedLayerProfile restores exact-session evidence atomically before // any request on a replacement physical connection is admitted. func (c *Conn) seedOrderedLayerProfile(profile tlprofile.Profile, msgID int64) error { if c == nil { return nil } if msgID < 0 { return fmt.Errorf("invalid restored layer evidence msg_id %d", msgID) } if err := validateLayerProfile(profile); err != nil { return err } if msgID > 0 { _, err := c.freezeRawLayerProfileAt(int(profile), msgID) return err } c.layerProfileMu.Lock() defer c.layerProfileMu.Unlock() current := unpackLayerProfileState(c.layerProfileState.Load()) if current.Profile != profile || current.Origin != LayerProfileExplicit { if current.Epoch == math.MaxUint32 { return ErrLayerProfileEpochExhausted } current = LayerProfileSnapshot{Profile: profile, Origin: LayerProfileExplicit, Epoch: current.Epoch + 1} c.layerProfileState.Store(packLayerProfileState(current)) c.setLegacyClientLayer(int(profile)) } c.layerProfileEvidenceMsgID = msgID c.layerProfileEvidenceLayer = 0 return nil } func (c *Conn) seedRawLayerEvidence(layer int, msgID int64) error { if layer <= 0 || msgID <= 0 { return fmt.Errorf("invalid restored raw layer evidence layer=%d msg_id=%d", layer, msgID) } _, err := c.freezeRawLayerProfileAt(layer, msgID) return err } // refreshInheritedLayerProfile is reserved for identity normalization at // auth.bindTempAuthKey: once a raw temporary key is resolved to its permanent // key, the permanent key's default supersedes an older raw-key shadow. Explicit // evidence on the concrete session is never overwritten. func (c *Conn) refreshInheritedLayerProfile(profile tlprofile.Profile) (bool, error) { if c == nil { return false, nil } if err := validateLayerProfile(profile); err != nil { return false, err } c.layerProfileMu.Lock() defer c.layerProfileMu.Unlock() current := unpackLayerProfileState(c.layerProfileState.Load()) if current.Origin == LayerProfileExplicit || c.layerProfileEvidenceMsgID > 0 { return false, nil } if current.Origin == LayerProfileInherited && current.Profile == profile { return false, nil } if current.Epoch == math.MaxUint32 { return false, ErrLayerProfileEpochExhausted } next := LayerProfileSnapshot{Profile: profile, Origin: LayerProfileInherited, Epoch: current.Epoch + 1} c.layerProfileState.Store(packLayerProfileState(next)) c.layerProfileEvidenceMsgID = 0 c.layerProfileEvidenceLayer = 0 c.setLegacyClientLayer(int(profile)) return true, nil } func (c *Conn) clearInheritedLayerProfileState() (bool, error) { if c == nil { return false, nil } c.layerProfileMu.Lock() defer c.layerProfileMu.Unlock() current := unpackLayerProfileState(c.layerProfileState.Load()) if current.Origin != LayerProfileInherited { return false, nil } if current.Epoch == math.MaxUint32 { return false, ErrLayerProfileEpochExhausted } c.layerProfileState.Store(packLayerProfileState(LayerProfileSnapshot{Epoch: current.Epoch + 1})) c.layerProfileEvidenceMsgID = 0 c.layerProfileEvidenceLayer = 0 c.setLegacyClientLayer(0) return true, nil } func (c *Conn) clearInheritedLayerProfile() error { _, err := c.clearInheritedLayerProfileState() return err } // seedInitialLayerProfile applies recovery sources in descending authority. // Unsupported metadata never clamps to a nearby generated layer: the Conn stays // unknown so the client can explicitly renegotiate. func (s *Server) seedInitialLayerProfile( ctx context.Context, c *Conn, fetchedLayer int, previous LayerProfileSnapshot, ) error { if s == nil || c == nil { return nil } durableResolver, hasDurableResolver := s.layerRPC.(LayerRPCDurableSessionProfileResolver) if hasDurableResolver { layer, msgID, found, err := durableResolver.ResolveNegotiatedSessionLayerEvidence(ctx, c.authKeyID, c.sessionID) if err != nil { if isLayerEvidenceDurabilityUnavailable(err) { s.log.Warn("Resolve durable exact session Layer during connection seed unavailable; continuing with auth-key default", zap.String("auth_key_id", c.authKeyHex), zap.Int64("session_id", c.sessionID), zap.Error(err)) // Exact-session proof is the strongest recovery source, but its // availability failure must not discard a permanent auth_keys.layer // already loaded with the key on this same first frame. Continue to the // auth-key default below; never reuse previous connection-local evidence // in durable mode. found = false } else { return fmt.Errorf("resolve durable exact session Layer during connection seed: %w", err) } } if found { if layer <= 0 || msgID < 0 { return fmt.Errorf("invalid durable exact session Layer seed layer=%d msg_id=%d", layer, msgID) } if msgID > 0 { return c.seedRawLayerEvidence(layer, msgID) } // Older in-process exact-session registries did not retain a message // watermark. Keep that compatibility-only seed usable without treating // it as durable ordered evidence; real durable stores never persist zero. profile, supported := tlprofile.ResolveProfile(layer) if !supported { return nil } return c.seedOrderedLayerProfile(profile, 0) } } else if resolver, ok := s.layerRPC.(LayerRPCOrderedSessionProfileResolver); ok { if layer, msgID, found := resolver.NegotiatedSessionLayerEvidence(c.authKeyID, c.sessionID); found { if layer <= 0 || msgID < 0 { return nil } if msgID > 0 { return c.seedRawLayerEvidence(layer, msgID) } profile, supported := tlprofile.ResolveProfile(layer) if !supported { return nil } return c.seedOrderedLayerProfile(profile, 0) } } else if resolver, ok := s.layerRPC.(LayerRPCSessionProfileResolver); ok { if layer, found := resolver.NegotiatedSessionLayer(c.authKeyID, c.sessionID); found { profile, supported := tlprofile.ResolveProfile(layer) if !supported { return nil } return c.SeedLayerProfile(profile) } } // A freshly fetched permanent-key row is already the canonical auth-key // record. Prefer its non-zero Layer without making Router query the same PG // row again. Unsupported metadata remains unknown and must not fall through // to a weaker mirror. if c.authKeyExpiresAt == 0 && fetchedLayer != 0 { profile, supported := tlprofile.ResolveProfile(fetchedLayer) if !supported { return nil } return c.SeedInheritedLayerProfile(profile) } // Temporary keys resolve through their bound permanent key before consulting // the raw temp-key shadow, which may predate a client upgrade. if resolver, ok := s.layerRPC.(LayerRPCInheritedAuthKeyProfileResolver); ok { layer, found, err := resolver.ResolveInheritedAuthKeyLayer(ctx, c.authKeyID) if err != nil { if !isLayerEvidenceDurabilityUnavailable(err) { // A binding conflict, missing/destroyed key, or malformed durable // value is not an availability hint. Stay unknown and never let a // stale raw temp-key shadow outrank that structural failure. if s.log != nil { s.log.Warn("Resolve inherited auth-key Layer failed; awaiting explicit evidence", zap.String("auth_key_id", c.authKeyHex), zap.Error(err)) } return nil } // The same first frame already authenticated and loaded fetchedLayer // from this raw temp key. During a transient permanent-identity lookup // outage it is safe only as this physical Conn's inherited shadow; it // is never published back to the shared permanent default. if s.log != nil { s.log.Warn("Resolve inherited auth-key Layer unavailable; continuing with raw auth-key shadow", zap.String("auth_key_id", c.authKeyHex), zap.Error(err)) } } else if !found { // Fall through to a raw auth-key shadow when the resolver has no // canonical permanent-key default (for example an unbound temp key). } else { profile, supported := tlprofile.ResolveProfile(layer) if !supported { return nil } return c.SeedInheritedLayerProfile(profile) } } if fetchedLayer != 0 { profile, supported := tlprofile.ResolveProfile(fetchedLayer) if !supported { return nil } return c.SeedInheritedLayerProfile(profile) } if hasDurableResolver { // previous belongs to the old logical Conn which occupied this physical // transport. In durable mode only an exact session row or auth-key default // may cross that boundary. In particular, a connection-local selector used // during a store outage must not leak into a newly selected session. return nil } if previous.Origin != LayerProfileUnknown { return c.SeedInheritedLayerProfile(previous.Profile) } return nil } // refreshActivatedInheritedLayerProfile closes the auth.bindTempAuthKey race // after BeginActivation has made this Conn visible in claimsByAuth. If bind won // first, the second resolver read sees the permanent-key default; if the claim // won first, bind's SessionManager refresh sees and updates this Conn. Explicit // session evidence always wins either ordering. func (s *Server) refreshActivatedInheritedLayerProfile(ctx context.Context, c *Conn, fetchedLayer int) error { if s == nil || c == nil || c.LayerProfileState().Origin == LayerProfileExplicit { return nil } if c.authKeyExpiresAt == 0 { if fetchedLayer == 0 { return nil } profile, ok := tlprofile.ResolveProfile(fetchedLayer) if !ok { return c.clearInheritedLayerProfile() } _, err := c.refreshInheritedLayerProfile(profile) return err } if resolver, ok := s.layerRPC.(LayerRPCInheritedAuthKeyProfileResolver); ok { layer, found, err := resolver.ResolveInheritedAuthKeyLayer(ctx, c.authKeyID) if err != nil { if !isLayerEvidenceDurabilityUnavailable(err) { if s.log != nil { s.log.Warn("Re-resolve inherited auth-key Layer after activation claim failed", zap.String("auth_key_id", c.authKeyHex), zap.Error(err)) } // A pre-claim raw temp shadow may have been installed before a // concurrent bind became visible. Structural identity/key failures // must revoke that weaker inherited evidence; only an explicit // selector (guarded above) is allowed to survive this branch. return c.clearInheritedLayerProfile() } if s.log != nil { s.log.Warn("Re-resolve inherited auth-key Layer unavailable; keeping raw auth-key shadow", zap.String("auth_key_id", c.authKeyHex), zap.Error(err)) } } else if found { profile, supported := tlprofile.ResolveProfile(layer) if !supported { return c.clearInheritedLayerProfile() } _, err = c.refreshInheritedLayerProfile(profile) return err } } if fetchedLayer == 0 { return nil } profile, ok := tlprofile.ResolveProfile(fetchedLayer) if !ok { return c.clearInheritedLayerProfile() } _, err := c.refreshInheritedLayerProfile(profile) return err } // lockSessionLayerBinding linearizes a proactive update's final validation and // physical write with profile correction. If correction wins first, validation // observes the new epoch and drops the update. If the writer wins first, the // correction becomes visible only after those bytes have landed. Returning a // bool avoids allocating a release closure on the outbound hot path. func (c *Conn) lockSessionLayerBinding(binding *outboundLayerBinding) bool { if c == nil || binding == nil || binding.wireInvariant || binding.kind == outboundLayerBindingRequest || binding.epoch == 0 { return false } c.layerProfileMu.RLock() return true }