diff options
Diffstat (limited to 'vendor/github.com/pion/stun/agent.go')
-rw-r--r-- | vendor/github.com/pion/stun/agent.go | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/vendor/github.com/pion/stun/agent.go b/vendor/github.com/pion/stun/agent.go new file mode 100644 index 0000000..6a8a473 --- /dev/null +++ b/vendor/github.com/pion/stun/agent.go @@ -0,0 +1,228 @@ +package stun + +import ( + "errors" + "sync" + "time" +) + +// NoopHandler just discards any event. +var NoopHandler Handler = func(e Event) {} + +// NewAgent initializes and returns new Agent with provided handler. +// If h is nil, the NoopHandler will be used. +func NewAgent(h Handler) *Agent { + if h == nil { + h = NoopHandler + } + a := &Agent{ + transactions: make(map[transactionID]agentTransaction), + handler: h, + } + return a +} + +// Agent is low-level abstraction over transaction list that +// handles concurrency (all calls are goroutine-safe) and +// time outs (via Collect call). +type Agent struct { + // transactions is map of transactions that are currently + // in progress. Event handling is done in such way when + // transaction is unregistered before agentTransaction access, + // minimizing mux lock and protecting agentTransaction from + // data races via unexpected concurrent access. + transactions map[transactionID]agentTransaction + closed bool // all calls are invalid if true + mux sync.Mutex // protects transactions and closed + handler Handler // handles transactions +} + +// Handler handles state changes of transaction. +// +// Handler is called on transaction state change. +// Usage of e is valid only during call, user must +// copy needed fields explicitly. +type Handler func(e Event) + +// Event is passed to Handler describing the transaction event. +// Do not reuse outside Handler. +type Event struct { + TransactionID [TransactionIDSize]byte + Message *Message + Error error +} + +// agentTransaction represents transaction in progress. +// Concurrent access is invalid. +type agentTransaction struct { + id transactionID + deadline time.Time +} + +var ( + // ErrTransactionStopped indicates that transaction was manually stopped. + ErrTransactionStopped = errors.New("transaction is stopped") + // ErrTransactionNotExists indicates that agent failed to find transaction. + ErrTransactionNotExists = errors.New("transaction not exists") + // ErrTransactionExists indicates that transaction with same id is already + // registered. + ErrTransactionExists = errors.New("transaction exists with same id") +) + +// StopWithError removes transaction from list and calls handler with +// provided error. Can return ErrTransactionNotExists and ErrAgentClosed. +func (a *Agent) StopWithError(id [TransactionIDSize]byte, err error) error { + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + t, exists := a.transactions[id] + delete(a.transactions, id) + h := a.handler + a.mux.Unlock() + if !exists { + return ErrTransactionNotExists + } + h(Event{ + TransactionID: t.id, + Error: err, + }) + return nil +} + +// Stop stops transaction by id with ErrTransactionStopped, blocking +// until handler returns. +func (a *Agent) Stop(id [TransactionIDSize]byte) error { + return a.StopWithError(id, ErrTransactionStopped) +} + +// ErrAgentClosed indicates that agent is in closed state and is unable +// to handle transactions. +var ErrAgentClosed = errors.New("agent is closed") + +// Start registers transaction with provided id and deadline. +// Could return ErrAgentClosed, ErrTransactionExists. +// +// Agent handler is guaranteed to be eventually called. +func (a *Agent) Start(id [TransactionIDSize]byte, deadline time.Time) error { + a.mux.Lock() + defer a.mux.Unlock() + if a.closed { + return ErrAgentClosed + } + _, exists := a.transactions[id] + if exists { + return ErrTransactionExists + } + a.transactions[id] = agentTransaction{ + id: id, + deadline: deadline, + } + return nil +} + +// agentCollectCap is initial capacity for Agent.Collect slices, +// sufficient to make function zero-alloc in most cases. +const agentCollectCap = 100 + +// ErrTransactionTimeOut indicates that transaction has reached deadline. +var ErrTransactionTimeOut = errors.New("transaction is timed out") + +// Collect terminates all transactions that have deadline before provided +// time, blocking until all handlers will process ErrTransactionTimeOut. +// Will return ErrAgentClosed if agent is already closed. +// +// It is safe to call Collect concurrently but makes no sense. +func (a *Agent) Collect(gcTime time.Time) error { + toRemove := make([]transactionID, 0, agentCollectCap) + a.mux.Lock() + if a.closed { + // Doing nothing if agent is closed. + // All transactions should be already closed + // during Close() call. + a.mux.Unlock() + return ErrAgentClosed + } + // Adding all transactions with deadline before gcTime + // to toCall and toRemove slices. + // No allocs if there are less than agentCollectCap + // timed out transactions. + for id, t := range a.transactions { + if t.deadline.Before(gcTime) { + toRemove = append(toRemove, id) + } + } + // Un-registering timed out transactions. + for _, id := range toRemove { + delete(a.transactions, id) + } + // Calling handler does not require locked mutex, + // reducing lock time. + h := a.handler + a.mux.Unlock() + // Sending ErrTransactionTimeOut to handler for all transactions, + // blocking until last one. + event := Event{ + Error: ErrTransactionTimeOut, + } + for _, id := range toRemove { + event.TransactionID = id + h(event) + } + return nil +} + +// Process incoming message, synchronously passing it to handler. +func (a *Agent) Process(m *Message) error { + e := Event{ + TransactionID: m.TransactionID, + Message: m, + } + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + h := a.handler + delete(a.transactions, m.TransactionID) + a.mux.Unlock() + h(e) + return nil +} + +// SetHandler sets agent handler to h. +func (a *Agent) SetHandler(h Handler) error { + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + a.handler = h + a.mux.Unlock() + return nil +} + +// Close terminates all transactions with ErrAgentClosed and renders Agent to +// closed state. +func (a *Agent) Close() error { + e := Event{ + Error: ErrAgentClosed, + } + a.mux.Lock() + if a.closed { + a.mux.Unlock() + return ErrAgentClosed + } + for _, t := range a.transactions { + e.TransactionID = t.id + a.handler(e) + } + a.transactions = nil + a.closed = true + a.handler = nil + a.mux.Unlock() + return nil +} + +type transactionID [TransactionIDSize]byte |