// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package client

import (
	
	
	
	
	

	
	
	
	
)

// AllocationConfig is a set of configuration params use by NewUDPConn and NewTCPAllocation.
type AllocationConfig struct {
	Client      Client
	RelayedAddr net.Addr
	ServerAddr  net.Addr
	Integrity   stun.MessageIntegrity
	Nonce       stun.Nonce
	Username    stun.Username
	Realm       stun.Realm
	Lifetime    time.Duration
	Net         transport.Net
	Log         logging.LeveledLogger
}

type allocation struct {
	client            Client                // Read-only
	relayedAddr       net.Addr              // Read-only
	serverAddr        net.Addr              // Read-only
	permMap           *permissionMap        // Thread-safe
	integrity         stun.MessageIntegrity // Read-only
	username          stun.Username         // Read-only
	realm             stun.Realm            // Read-only
	_nonce            stun.Nonce            // Needs mutex x
	_lifetime         time.Duration         // Needs mutex x
	net               transport.Net         // Thread-safe
	refreshAllocTimer *PeriodicTimer        // Thread-safe
	refreshPermsTimer *PeriodicTimer        // Thread-safe
	readTimer         *time.Timer           // Thread-safe
	mutex             sync.RWMutex          // Thread-safe
	log               logging.LeveledLogger // Read-only
}

func ( *allocation) ( *stun.Message) {
	// Update nonce
	var  stun.Nonce
	if  := .GetFrom();  == nil {
		.setNonce()
		.log.Debug("Refresh allocation: 438, got new nonce.")
	} else {
		.log.Warn("Refresh allocation: 438 but no nonce.")
	}
}

func ( *allocation) ( time.Duration,  bool) error {
	,  := stun.Build(
		stun.TransactionID,
		stun.NewType(stun.MethodRefresh, stun.ClassRequest),
		proto.Lifetime{Duration: },
		.username,
		.realm,
		.nonce(),
		.integrity,
		stun.Fingerprint,
	)
	if  != nil {
		return fmt.Errorf("%w: %s", errFailedToBuildRefreshRequest, .Error())
	}

	.log.Debugf("Send refresh request (dontWait=%v)", )
	,  := .client.PerformTransaction(, .serverAddr, )
	if  != nil {
		return fmt.Errorf("%w: %s", errFailedToRefreshAllocation, .Error())
	}

	if  {
		.log.Debug("Refresh request sent")

		return nil
	}

	.log.Debug("Refresh request sent, and waiting response")

	 := .Msg
	if .Type.Class == stun.ClassErrorResponse {
		var  stun.ErrorCodeAttribute
		if  = .GetFrom();  == nil {
			if .Code == stun.CodeStaleNonce {
				.setNonceFromMsg()

				return errTryAgain
			}

			return 
		}

		return fmt.Errorf("%s", .Type) //nolint:goerr113
	}

	// Getting lifetime from response
	var  proto.Lifetime
	if  := .GetFrom();  != nil {
		return fmt.Errorf("%w: %s", errFailedToGetLifetime, .Error())
	}

	.setLifetime(.Duration)
	.log.Debugf("Updated lifetime: %d seconds", int(.lifetime().Seconds()))

	return nil
}

func ( *allocation) () error {
	 := .permMap.addrs()
	if len() == 0 {
		.log.Debug("No permission to refresh")

		return nil
	}
	if  := .CreatePermissions(...);  != nil {
		if errors.Is(, errTryAgain) {
			return errTryAgain
		}
		.log.Errorf("Fail to refresh permissions: %s", )

		return 
	}
	.log.Debug("Refresh permissions successful")

	return nil
}

func ( *allocation) ( int) {
	.log.Debugf("Refresh timer %d expired", )
	switch  {
	case timerIDRefreshAlloc:
		var  error
		 := .lifetime()
		// Limit the max retries on errTryAgain to 3
		// when stale nonce returns, sencond retry should succeed
		for  := 0;  < maxRetryAttempts; ++ {
			 = .refreshAllocation(, false)
			if !errors.Is(, errTryAgain) {
				break
			}
		}
		if  != nil {
			.log.Warnf("Failed to refresh allocation: %s", )
		}
	case timerIDRefreshPerms:
		var  error
		for  := 0;  < maxRetryAttempts; ++ {
			 = .refreshPermissions()
			if !errors.Is(, errTryAgain) {
				break
			}
		}
		if  != nil {
			.log.Warnf("Failed to refresh permissions: %s", )
		}
	}
}

func ( *allocation) () stun.Nonce {
	.mutex.RLock()
	defer .mutex.RUnlock()

	return ._nonce
}

func ( *allocation) ( stun.Nonce) {
	.mutex.Lock()
	defer .mutex.Unlock()

	.log.Debugf("Set new nonce with %d bytes", len())
	._nonce = 
}

func ( *allocation) () time.Duration {
	.mutex.RLock()
	defer .mutex.RUnlock()

	return ._lifetime
}

func ( *allocation) ( time.Duration) {
	.mutex.Lock()
	defer .mutex.Unlock()

	._lifetime = 
}