231 lines
5.1 KiB
Go
231 lines
5.1 KiB
Go
package sip
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// CallFlow represents a SIP call with all its packets
|
|
type CallFlow struct {
|
|
CallID string
|
|
Packets []*Packet
|
|
StartTime time.Time
|
|
EndTime time.Time
|
|
|
|
// Summary info
|
|
From string
|
|
To string
|
|
State CallState
|
|
}
|
|
|
|
// CallState represents the current state of a call
|
|
type CallState string
|
|
|
|
const (
|
|
CallStateInitial CallState = "Initial"
|
|
CallStateRinging CallState = "Ringing"
|
|
CallStateConnected CallState = "Connected"
|
|
CallStateTerminated CallState = "Terminated"
|
|
CallStateFailed CallState = "Failed"
|
|
)
|
|
|
|
// CallFlowStore stores and manages call flows
|
|
type CallFlowStore struct {
|
|
mu sync.RWMutex
|
|
flows map[string]*CallFlow
|
|
}
|
|
|
|
// NewCallFlowStore creates a new call flow store
|
|
func NewCallFlowStore() *CallFlowStore {
|
|
return &CallFlowStore{
|
|
flows: make(map[string]*CallFlow),
|
|
}
|
|
}
|
|
|
|
// AddPacket adds a packet to the appropriate call flow
|
|
func (s *CallFlowStore) AddPacket(p *Packet) *CallFlow {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if p.CallID == "" {
|
|
return nil
|
|
}
|
|
|
|
flow, exists := s.flows[p.CallID]
|
|
|
|
// Determine timestamp to use
|
|
ts := p.Timestamp
|
|
if ts.IsZero() {
|
|
ts = time.Now()
|
|
}
|
|
|
|
if !exists {
|
|
flow = &CallFlow{
|
|
CallID: p.CallID,
|
|
Packets: make([]*Packet, 0),
|
|
StartTime: ts,
|
|
From: p.From,
|
|
To: p.To,
|
|
State: CallStateInitial,
|
|
}
|
|
s.flows[p.CallID] = flow
|
|
}
|
|
|
|
flow.Packets = append(flow.Packets, p)
|
|
// Always update EndTime to the latest packet's timestamp
|
|
if ts.After(flow.EndTime) {
|
|
flow.EndTime = ts
|
|
}
|
|
|
|
// Update call state based on packet
|
|
s.updateState(flow, p)
|
|
|
|
return flow
|
|
}
|
|
|
|
// updateState updates the call state based on the packet
|
|
func (s *CallFlowStore) updateState(flow *CallFlow, p *Packet) {
|
|
if p.IsRequest {
|
|
switch p.Method {
|
|
case MethodINVITE:
|
|
if flow.State == CallStateInitial {
|
|
flow.State = CallStateInitial
|
|
}
|
|
case MethodBYE, MethodCANCEL:
|
|
flow.State = CallStateTerminated
|
|
}
|
|
} else {
|
|
// Response
|
|
switch {
|
|
case p.StatusCode >= 100 && p.StatusCode < 200:
|
|
if p.StatusCode == 180 || p.StatusCode == 183 {
|
|
flow.State = CallStateRinging
|
|
}
|
|
case p.StatusCode >= 200 && p.StatusCode < 300:
|
|
flow.State = CallStateConnected
|
|
case p.StatusCode >= 400:
|
|
flow.State = CallStateFailed
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetFlow returns a call flow by Call-ID
|
|
func (s *CallFlowStore) GetFlow(callID string) *CallFlow {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.flows[callID]
|
|
}
|
|
|
|
// GetAllFlows returns all call flows
|
|
func (s *CallFlowStore) GetAllFlows() []*CallFlow {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
flows := make([]*CallFlow, 0, len(s.flows))
|
|
for _, f := range s.flows {
|
|
flows = append(flows, f)
|
|
}
|
|
return flows
|
|
}
|
|
|
|
// GetRecentFlows returns the N most recent call flows sorted by StartTime (oldest first)
|
|
func (s *CallFlowStore) GetRecentFlows(n int) []*CallFlow {
|
|
flows := s.GetAllFlows()
|
|
|
|
// Sort by start time ascending (oldest first), then by CallID for stable order
|
|
for i := 0; i < len(flows)-1; i++ {
|
|
for j := i + 1; j < len(flows); j++ {
|
|
// Compare by StartTime first
|
|
if flows[i].StartTime.After(flows[j].StartTime) {
|
|
flows[i], flows[j] = flows[j], flows[i]
|
|
} else if flows[i].StartTime.Equal(flows[j].StartTime) {
|
|
// If same time, sort by CallID for stable order
|
|
if flows[i].CallID > flows[j].CallID {
|
|
flows[i], flows[j] = flows[j], flows[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(flows) > n {
|
|
flows = flows[:n]
|
|
}
|
|
return flows
|
|
}
|
|
|
|
// GetSortedFlows returns all call flows sorted by StartTime (oldest first)
|
|
func (s *CallFlowStore) GetSortedFlows() []*CallFlow {
|
|
flows := s.GetAllFlows()
|
|
|
|
// Sort by start time ascending (oldest first), then by CallID for stable order
|
|
for i := 0; i < len(flows)-1; i++ {
|
|
for j := i + 1; j < len(flows); j++ {
|
|
// Compare by StartTime first
|
|
if flows[i].StartTime.After(flows[j].StartTime) {
|
|
flows[i], flows[j] = flows[j], flows[i]
|
|
} else if flows[i].StartTime.Equal(flows[j].StartTime) {
|
|
// If same time, sort by CallID for stable order
|
|
if flows[i].CallID > flows[j].CallID {
|
|
flows[i], flows[j] = flows[j], flows[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return flows
|
|
}
|
|
|
|
// Count returns the number of call flows
|
|
func (s *CallFlowStore) Count() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return len(s.flows)
|
|
}
|
|
|
|
// Summary returns a string summary of a packet for display
|
|
func (p *Packet) Summary() string {
|
|
if p.IsRequest {
|
|
return string(p.Method)
|
|
}
|
|
return formatStatusCode(p.StatusCode, p.StatusText)
|
|
}
|
|
|
|
func formatStatusCode(code int, text string) string {
|
|
if text != "" {
|
|
return text
|
|
}
|
|
switch code {
|
|
case 100:
|
|
return "100 Trying"
|
|
case 180:
|
|
return "180 Ringing"
|
|
case 183:
|
|
return "183 Session Progress"
|
|
case 200:
|
|
return "200 OK"
|
|
case 400:
|
|
return "400 Bad Request"
|
|
case 401:
|
|
return "401 Unauthorized"
|
|
case 403:
|
|
return "403 Forbidden"
|
|
case 404:
|
|
return "404 Not Found"
|
|
case 408:
|
|
return "408 Request Timeout"
|
|
case 480:
|
|
return "480 Temporarily Unavailable"
|
|
case 486:
|
|
return "486 Busy Here"
|
|
case 487:
|
|
return "487 Request Terminated"
|
|
case 488:
|
|
return "488 Not Acceptable Here"
|
|
case 500:
|
|
return "500 Server Internal Error"
|
|
case 503:
|
|
return "503 Service Unavailable"
|
|
default:
|
|
return string(rune('0'+code/100)) + "xx"
|
|
}
|
|
}
|