From f18a2d11070952495fedc43139a5a1ce2631de87 Mon Sep 17 00:00:00 2001 From: Denys Smirnov Date: Tue, 25 Feb 2025 12:37:42 +0200 Subject: [PATCH] Expose and improve SIP number normalization. (#986) --- .changeset/sip-num-norm.md | 5 +++++ sip/sip.go | 35 ++++++++++++++++++++++++----------- sip/sip_test.go | 19 +++++++++++++++++++ 3 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 .changeset/sip-num-norm.md diff --git a/.changeset/sip-num-norm.md b/.changeset/sip-num-norm.md new file mode 100644 index 00000000..e56ece84 --- /dev/null +++ b/.changeset/sip-num-norm.md @@ -0,0 +1,5 @@ +--- +"github.com/livekit/protocol": minor +--- + +Expose and improve SIP number normalization. \ No newline at end of file diff --git a/sip/sip.go b/sip/sip.go index 3b4a4197..28a261ad 100644 --- a/sip/sip.go +++ b/sip/sip.go @@ -22,6 +22,7 @@ import ( "maps" "math" "net/netip" + "regexp" "sort" "strings" @@ -201,7 +202,7 @@ func (v *DispatchRuleValidator) Validate(r *livekit.SIPDispatchRuleInfo) error { } for _, trunk := range trunks { for _, number := range numbers { - key := dispatchRuleKey{Pin: pin, Trunk: trunk, Number: normalizeNumber(number)} + key := dispatchRuleKey{Pin: pin, Trunk: trunk, Number: NormalizeNumber(number)} r2 := v.byRuleKey[key] if r2 != nil { v.opt.Conflict(r, r2, DispatchRuleConflictGeneric) @@ -273,14 +274,26 @@ func printNumbers(numbers []string) string { return fmt.Sprintf("%q", numbers) } -func normalizeNumber(num string) string { +var ( + reNumber = regexp.MustCompile(`^\+?[\d\- ()]+$`) + reNumberRepl = strings.NewReplacer( + " ", "", + "-", "", + "(", "", + ")", "", + ) +) + +func NormalizeNumber(num string) string { if num == "" { return "" } - // TODO: Always keep "number" as-is if it's not E.164. - // This will only matter for native SIP clients which have '+' in the username. - if !strings.HasPrefix(num, `+`) { - num = "+" + num + if !reNumber.MatchString(num) { + return num + } + num = reNumberRepl.Replace(num) + if !strings.HasPrefix(num, "+") { + return "+" + num } return num } @@ -298,7 +311,7 @@ func validateTrunkInbound(byInbound map[string]*livekit.SIPInboundTrunkInfo, t * byInbound[""] = t } else { for _, num := range t.AllowedNumbers { - inboundKey := normalizeNumber(num) + inboundKey := NormalizeNumber(num) t2 := byInbound[inboundKey] if t2 != nil { opt.Conflict(t, t2, TrunkConflictCallingNumber) @@ -425,9 +438,9 @@ func matchNumbers(num string, allowed []string) bool { if len(allowed) == 0 { return true } - num = normalizeNumber(num) + norm := NormalizeNumber(num) for _, allow := range allowed { - if num == normalizeNumber(allow) { + if num == allow || norm == NormalizeNumber(allow) { return true } } @@ -518,7 +531,7 @@ func MatchTrunkIter(it iters.Iter[*livekit.SIPInboundTrunkInfo], srcIP netip.Add defaultTrunkPrev *livekit.SIPInboundTrunkInfo defaultTrunkCnt int // to error in case there are multiple ones ) - calledNorm := normalizeNumber(called) + calledNorm := NormalizeNumber(called) for { tr, err := it.Next() if err == io.EOF { @@ -542,7 +555,7 @@ func MatchTrunkIter(it iters.Iter[*livekit.SIPInboundTrunkInfo], srcIP netip.Add defaultTrunkCnt++ } else { for _, num := range tr.Numbers { - if normalizeNumber(num) == calledNorm { + if num == called || NormalizeNumber(num) == calledNorm { // Trunk specific to the number. if selectedTrunk != nil { opt.Conflict(selectedTrunk, tr, TrunkConflictCalledNumber) diff --git a/sip/sip_test.go b/sip/sip_test.go index eccacfaf..1d5d6391 100644 --- a/sip/sip_test.go +++ b/sip/sip_test.go @@ -27,6 +27,25 @@ import ( "github.com/livekit/protocol/rpc" ) +func TestNormalizeNumber(t *testing.T) { + cases := []struct { + name string + num string + exp string + }{ + {"empty", "", ""}, + {"number", "123", "+123"}, + {"plus", "+123", "+123"}, + {"user", "user", "user"}, + {"human", "(123) 456 7890", "+1234567890"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + require.Equal(t, c.exp, NormalizeNumber(c.num)) + }) + } +} + const ( sipNumber1 = "1111 1111" sipNumber2 = "2222 2222"