// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package client import ( "net" "sync" "time" "github.com/pion/stun" ) const ( maxRtxInterval time.Duration = 1600 * time.Millisecond ) // TransactionResult is a bag of result values of a transaction type TransactionResult struct { Msg *stun.Message From net.Addr Retries int Err error } // TransactionConfig is a set of config params used by NewTransaction type TransactionConfig struct { Key string Raw []byte To net.Addr Interval time.Duration IgnoreResult bool // True to throw away the result of this transaction (it will not be readable using WaitForResult) } // Transaction represents a transaction type Transaction struct { Key string // Read-only Raw []byte // Read-only To net.Addr // Read-only nRtx int // Modified only by the timer thread interval time.Duration // Modified only by the timer thread timer *time.Timer // Thread-safe, set only by the creator, and stopper resultCh chan TransactionResult // Thread-safe mutex sync.RWMutex } // NewTransaction creates a new instance of Transaction func NewTransaction(config *TransactionConfig) *Transaction { var resultCh chan TransactionResult if !config.IgnoreResult { resultCh = make(chan TransactionResult) } return &Transaction{ Key: config.Key, // Read-only Raw: config.Raw, // Read-only To: config.To, // Read-only interval: config.Interval, // Modified only by the timer thread resultCh: resultCh, // Thread-safe } } // StartRtxTimer starts the transaction timer func (t *Transaction) StartRtxTimer(onTimeout func(trKey string, nRtx int)) { t.mutex.Lock() defer t.mutex.Unlock() t.timer = time.AfterFunc(t.interval, func() { t.mutex.Lock() t.nRtx++ nRtx := t.nRtx t.interval *= 2 if t.interval > maxRtxInterval { t.interval = maxRtxInterval } t.mutex.Unlock() onTimeout(t.Key, nRtx) }) } // StopRtxTimer stop the transaction timer func (t *Transaction) StopRtxTimer() { t.mutex.Lock() defer t.mutex.Unlock() if t.timer != nil { t.timer.Stop() } } // WriteResult writes the result to the result channel func (t *Transaction) WriteResult(res TransactionResult) bool { if t.resultCh == nil { return false } t.resultCh <- res return true } // WaitForResult waits for the transaction result func (t *Transaction) WaitForResult() TransactionResult { if t.resultCh == nil { return TransactionResult{ Err: errWaitForResultOnNonResultTransaction, } } result, ok := <-t.resultCh if !ok { result.Err = errTransactionClosed } return result } // Close closes the transaction func (t *Transaction) Close() { if t.resultCh != nil { close(t.resultCh) } } // Retries returns the number of retransmission it has made func (t *Transaction) Retries() int { t.mutex.RLock() defer t.mutex.RUnlock() return t.nRtx } // TransactionMap is a thread-safe transaction map type TransactionMap struct { trMap map[string]*Transaction mutex sync.RWMutex } // NewTransactionMap create a new instance of the transaction map func NewTransactionMap() *TransactionMap { return &TransactionMap{ trMap: map[string]*Transaction{}, } } // Insert inserts a transaction to the map func (m *TransactionMap) Insert(key string, tr *Transaction) bool { m.mutex.Lock() defer m.mutex.Unlock() m.trMap[key] = tr return true } // Find looks up a transaction by its key func (m *TransactionMap) Find(key string) (*Transaction, bool) { m.mutex.RLock() defer m.mutex.RUnlock() tr, ok := m.trMap[key] return tr, ok } // Delete deletes a transaction by its key func (m *TransactionMap) Delete(key string) { m.mutex.Lock() defer m.mutex.Unlock() delete(m.trMap, key) } // CloseAndDeleteAll closes and deletes all transactions func (m *TransactionMap) CloseAndDeleteAll() { m.mutex.Lock() defer m.mutex.Unlock() for trKey, tr := range m.trMap { tr.Close() delete(m.trMap, trKey) } } // Size returns the length of the transaction map func (m *TransactionMap) Size() int { m.mutex.RLock() defer m.mutex.RUnlock() return len(m.trMap) }