feat(agent): preserve approved SIP route in ARI originate request

This commit is contained in:
2026-10-04 13:24:14 +08:00
parent 8f029bcf7a
commit a8a99eba1c
2 changed files with 71 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
package asterisk
import (
"errors"
"fmt"
"regexp"
"strings"
"time"
"github.com/CyCoreSystems/ari/v5"
)
var dialedRoute = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
// approvedOriginateRequest binds an already-approved Agent instruction to one
// native ARI channel. The Dispatcher, not the Agent, selected the trunk,
// caller and complete dialed number (including any trunk-specific prefix).
func approvedOriginateRequest(executionID, trunkID, dialedCallee, callerID string, answerTimeout time.Duration) (ari.OriginateRequest, error) {
if !trunkName.MatchString(executionID) || !trunkName.MatchString(trunkID) || !dialedRoute.MatchString(dialedCallee) || strings.TrimSpace(callerID) == "" || strings.ContainsAny(callerID, "\r\n") {
return ari.OriginateRequest{}, errors.New("approved ARI channel identity or route is incomplete")
}
seconds := int(answerTimeout / time.Second)
if seconds < 1 {
return ari.OriginateRequest{}, fmt.Errorf("approved ARI answer window is shorter than one second: %s", answerTimeout)
}
return ari.OriginateRequest{
ChannelID: executionID, Endpoint: "PJSIP/" + dialedCallee + "@" + trunkID,
CallerID: callerID, App: "go-sip-agent", Timeout: seconds,
}, nil
}
+41
View File
@@ -0,0 +1,41 @@
package asterisk
import (
"testing"
"time"
)
func TestOriginatePreservesApprovedRouteAndCaller(t *testing.T) {
for _, tc := range []struct {
trunk, dialed, caller, expected string
}{
{"shuqi", "708915000000001", "BD1234", "PJSIP/708915000000001@shuqi"},
{"zhongding", "15000000001", "mbkq", "PJSIP/15000000001@zhongding"},
{"baiying", "mka75515000000001", "KQ1234", "PJSIP/mka75515000000001@baiying"},
} {
request, err := approvedOriginateRequest("execution-123", tc.trunk, tc.dialed, tc.caller, 7*time.Second+900*time.Millisecond)
if err != nil {
t.Fatal(err)
}
if request.Endpoint != tc.expected || request.CallerID != tc.caller || request.ChannelID != "execution-123" || request.Timeout != 7 || request.App != "go-sip-agent" {
t.Fatalf("Agent changed approved SIP route/caller or exceeded answer deadline: %+v", request)
}
}
}
func TestOriginateRejectsMissingOrUnrepresentableSignedRoute(t *testing.T) {
for _, tc := range []struct {
executionID, trunk, dialed, caller string
answer time.Duration
}{
{"", "shuqi", "708915000000001", "BD1234", time.Second},
{"execution-123", "../shuqi", "708915000000001", "BD1234", time.Second},
{"execution-123", "shuqi", "7089/15000000001", "BD1234", time.Second},
{"execution-123", "shuqi", "708915000000001", "BD1234\n", time.Second},
{"execution-123", "shuqi", "708915000000001", "BD1234", 900 * time.Millisecond},
} {
if _, err := approvedOriginateRequest(tc.executionID, tc.trunk, tc.dialed, tc.caller, tc.answer); err == nil {
t.Fatal("invalid signed route or subsecond answer timeout cannot create a channel")
}
}
}