189 lines
4.2 KiB
Go
189 lines
4.2 KiB
Go
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
// 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)
|
|
}
|