package asterisk import ( "context" "errors" "fmt" "strings" "github.com/CyCoreSystems/ari/v5" ) // awaitStasisStart only marks the exact originated channel answered after it // enters our ARI application. A generic Up event is not proof of media setup. func awaitStasisStart(ctx context.Context, subscription ari.Subscription, channelID string) error { if ctx == nil || subscription == nil || channelID == "" { return errors.New("ARI answer subscription and channel identity required") } recent := make([]string, 0, 8) for { select { case <-ctx.Done(): return fmt.Errorf("ARI answer deadline or cancellation (recent_events=%s): %w", strings.Join(recent, ","), ctx.Err()) case event, open := <-subscription.Events(): if !open { return errors.New("ARI event subscription closed before answer") } if event == nil { return errors.New("ARI answer subscription delivered an empty event") } typ := event.GetType() if len(recent) == cap(recent) { recent = recent[1:] } recent = append(recent, typ) if !eventBelongsToChannel(event, channelID) { continue } switch typ { case "StasisStart": if _, ok := event.(*ari.StasisStart); ok { return nil } case "StasisEnd": return errors.New("ARI channel left the application before StasisStart") case "ChannelHangupRequest": if hangup, ok := event.(*ari.ChannelHangupRequest); ok { return fmt.Errorf("ARI channel ended before StasisStart: hangup_cause=%d", hangup.Cause) } return errors.New("ARI channel ended before StasisStart: hangup request") case "ChannelDestroyed": if destroyed, ok := event.(*ari.ChannelDestroyed); ok { return fmt.Errorf("ARI channel ended before StasisStart: destroy_cause=%d", destroyed.Cause) } return errors.New("ARI channel ended before StasisStart: destroyed") } } } } func eventBelongsToChannel(event ari.Event, channelID string) bool { for _, key := range event.Keys() { if key != nil && key.Kind == "channel" && key.ID == channelID { return true } } return false }