feat: sync multilayer td integration
This commit is contained in:
parent
20a310f6ca
commit
766c5db992
491 changed files with 26235 additions and 35340 deletions
506
internal/mtprotoedge/conn_layer_profile.go
Normal file
506
internal/mtprotoedge/conn_layer_profile.go
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
package mtprotoedge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/iamxvbaba/td/tg"
|
||||
"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 tg.LayerProfile
|
||||
Origin LayerProfileOrigin
|
||||
Epoch uint32
|
||||
}
|
||||
|
||||
const (
|
||||
layerProfileValueBits = 16
|
||||
layerProfileOriginBits = 8
|
||||
layerProfileOriginShift = layerProfileValueBits
|
||||
layerProfileEpochShift = 32
|
||||
layerProfileValueMask = uint64(1<<layerProfileValueBits - 1)
|
||||
layerProfileOriginMask = uint64(1<<layerProfileOriginBits - 1)
|
||||
)
|
||||
|
||||
func packLayerProfileState(state LayerProfileSnapshot) uint64 {
|
||||
return uint64(state.Epoch)<<layerProfileEpochShift |
|
||||
(uint64(state.Origin)&layerProfileOriginMask)<<layerProfileOriginShift |
|
||||
(uint64(state.Profile) & layerProfileValueMask)
|
||||
}
|
||||
|
||||
func unpackLayerProfileState(raw uint64) LayerProfileSnapshot {
|
||||
if raw == 0 {
|
||||
return LayerProfileSnapshot{}
|
||||
}
|
||||
return LayerProfileSnapshot{
|
||||
Profile: tg.LayerProfile(raw & layerProfileValueMask),
|
||||
Origin: LayerProfileOrigin((raw >> 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 tg.LayerProfile, 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 tg.LayerProfile) error {
|
||||
resolved, ok := tg.ResolveLayerProfile(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 cached request replays.
|
||||
func (c *Conn) freezeLayerProfileAt(profile tg.LayerProfile, 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 := tg.ResolveLayerProfile(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 tg.LayerProfile, 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 tg.LayerProfile) (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 := tg.ResolveLayerProfile(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 := tg.ResolveLayerProfile(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 := tg.ResolveLayerProfile(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 := tg.ResolveLayerProfile(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 := tg.ResolveLayerProfile(layer)
|
||||
if !supported {
|
||||
return nil
|
||||
}
|
||||
return c.SeedInheritedLayerProfile(profile)
|
||||
}
|
||||
}
|
||||
if fetchedLayer != 0 {
|
||||
profile, supported := tg.ResolveLayerProfile(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 := tg.ResolveLayerProfile(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 := tg.ResolveLayerProfile(layer)
|
||||
if !supported {
|
||||
return c.clearInheritedLayerProfile()
|
||||
}
|
||||
_, err = c.refreshInheritedLayerProfile(profile)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if fetchedLayer == 0 {
|
||||
return nil
|
||||
}
|
||||
profile, ok := tg.ResolveLayerProfile(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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue