Skip to content

Commit ed81a8f

Browse files
committed
Squash
1 parent da74f3f commit ed81a8f

10 files changed

Lines changed: 158 additions & 26 deletions

File tree

.changeset/khaki-stamps-punch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"github.com/livekit/protocol": minor
3+
---
4+
5+
Adding ability to specify media timeout per SIP trunk

livekit/sip.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,35 @@ import (
77
"slices"
88
"strconv"
99
"strings"
10+
"time"
1011

1112
"golang.org/x/text/language"
1213
"google.golang.org/grpc/codes"
1314
"google.golang.org/grpc/status"
1415
"google.golang.org/protobuf/proto"
16+
"google.golang.org/protobuf/types/known/durationpb"
1517

1618
"github.com/livekit/protocol/utils/xtwirp"
1719
)
1820

21+
// MaxSIPMediaTimeout is the maximum allowed trunk / API value for media_timeout
22+
// (no incoming RTP before the RTP path is torn down)
23+
const MaxSIPMediaTimeout = 10 * time.Minute
24+
25+
func validateDuration(name string, d *durationpb.Duration, min, max *time.Duration) error {
26+
if d == nil {
27+
return nil
28+
}
29+
dur := d.AsDuration()
30+
if min != nil && dur < *min {
31+
return fmt.Errorf("%s must not be less than %v", name, *min)
32+
}
33+
if max != nil && dur > *max {
34+
return fmt.Errorf("%s must not be greater than %v", name, *max)
35+
}
36+
return nil
37+
}
38+
1939
var (
2040
_ xtwirp.ErrorMeta = (*SIPStatus)(nil)
2141
_ error = (*SIPStatus)(nil)
@@ -452,6 +472,10 @@ func (p *SIPInboundTrunkInfo) Validate() error {
452472
if err := validateHeaderToAttributes(p.HeadersToAttributes); err != nil {
453473
return err
454474
}
475+
timeout := MaxSIPMediaTimeout
476+
if err := validateDuration("media_timeout", p.MediaTimeout, nil, &timeout); err != nil {
477+
return err
478+
}
455479
return nil
456480
}
457481

@@ -465,6 +489,10 @@ func (p *SIPInboundTrunkUpdate) Validate() error {
465489
if err := p.AllowedNumbers.Validate(); err != nil {
466490
return err
467491
}
492+
timeout := MaxSIPMediaTimeout
493+
if err := validateDuration("media_timeout", p.MediaTimeout, nil, &timeout); err != nil {
494+
return err
495+
}
468496
return nil
469497
}
470498

@@ -480,6 +508,7 @@ func (p *SIPInboundTrunkUpdate) Apply(info *SIPInboundTrunkInfo) error {
480508
applyUpdate(&info.Name, p.Name)
481509
applyUpdate(&info.Metadata, p.Metadata)
482510
applyUpdate(&info.MediaEncryption, p.MediaEncryption)
511+
applyUpdatePtr(&info.MediaTimeout, p.MediaTimeout)
483512
return info.Validate()
484513
}
485514

@@ -541,6 +570,10 @@ func (p *SIPOutboundTrunkInfo) Validate() error {
541570
if err := validateHeaderToAttributes(p.HeadersToAttributes); err != nil {
542571
return err
543572
}
573+
timeout := MaxSIPMediaTimeout
574+
if err := validateDuration("media_timeout", p.MediaTimeout, nil, &timeout); err != nil {
575+
return err
576+
}
544577
return nil
545578
}
546579

@@ -560,13 +593,21 @@ func (p *SIPOutboundConfig) Validate() error {
560593
if err := validateHeaderToAttributes(p.HeadersToAttributes); err != nil {
561594
return err
562595
}
596+
timeout := MaxSIPMediaTimeout
597+
if err := validateDuration("media_timeout", p.MediaTimeout, nil, &timeout); err != nil {
598+
return err
599+
}
563600
return nil
564601
}
565602

566603
func (p *SIPOutboundTrunkUpdate) Validate() error {
567604
if err := p.Numbers.Validate(); err != nil {
568605
return err
569606
}
607+
timeout := MaxSIPMediaTimeout
608+
if err := validateDuration("media_timeout", p.MediaTimeout, nil, &timeout); err != nil {
609+
return err
610+
}
570611
return nil
571612
}
572613

@@ -584,6 +625,7 @@ func (p *SIPOutboundTrunkUpdate) Apply(info *SIPOutboundTrunkInfo) error {
584625
applyUpdate(&info.Metadata, p.Metadata)
585626
applyUpdate(&info.MediaEncryption, p.MediaEncryption)
586627
applyUpdate(&info.FromHost, p.FromHost)
628+
applyUpdatePtr(&info.MediaTimeout, p.MediaTimeout)
587629
return info.Validate()
588630
}
589631

livekit/sip_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package livekit
33
import (
44
"slices"
55
"testing"
6+
"time"
67

78
"github.com/stretchr/testify/require"
89
"google.golang.org/grpc/codes"
910
"google.golang.org/grpc/status"
1011
"google.golang.org/protobuf/proto"
12+
"google.golang.org/protobuf/types/known/durationpb"
1113
)
1214

1315
func TestSIPTrunkAs(t *testing.T) {
@@ -191,6 +193,64 @@ func TestSIPValidate(t *testing.T) {
191193
},
192194
exp: false,
193195
},
196+
{
197+
name: "inbound media_timeout over max",
198+
req: &SIPInboundTrunkInfo{
199+
Numbers: []string{"+1111"},
200+
MediaTimeout: durationpb.New(20 * time.Minute),
201+
},
202+
exp: false,
203+
},
204+
{
205+
name: "inbound media_timeout ok",
206+
req: &SIPInboundTrunkInfo{
207+
Numbers: []string{"+1111"},
208+
MediaTimeout: durationpb.New(5 * time.Minute),
209+
},
210+
exp: true,
211+
},
212+
{
213+
name: "outbound media_timeout over max",
214+
req: &SIPOutboundTrunkInfo{
215+
Address: "sip.example.com",
216+
Numbers: []string{"+2222"},
217+
MediaTimeout: durationpb.New(20 * time.Minute),
218+
},
219+
exp: false,
220+
},
221+
{
222+
name: "outbound media_timeout ok",
223+
req: &SIPOutboundTrunkInfo{
224+
Address: "sip.example.com",
225+
Numbers: []string{"+2222"},
226+
MediaTimeout: durationpb.New(5 * time.Minute),
227+
},
228+
exp: true,
229+
},
230+
{
231+
name: "CreateSIPParticipantRequest media_timeout ok",
232+
req: &CreateSIPParticipantRequest{
233+
SipCallTo: "+3333",
234+
RoomName: "room",
235+
Trunk: &SIPOutboundConfig{
236+
MediaTimeout: durationpb.New(5 * time.Minute),
237+
Hostname: "sip.example.com",
238+
},
239+
},
240+
exp: true,
241+
},
242+
{
243+
name: "CreateSIPParticipantRequest media_timeout invalid",
244+
req: &CreateSIPParticipantRequest{
245+
SipCallTo: "+3333",
246+
RoomName: "room",
247+
Trunk: &SIPOutboundConfig{
248+
MediaTimeout: durationpb.New(20 * time.Minute),
249+
Hostname: "sip.example.com",
250+
},
251+
},
252+
exp: false,
253+
},
194254
}
195255
for _, c := range cases {
196256
t.Run(c.name, func(t *testing.T) {

protobufs/livekit_sip.proto

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,10 @@ message SIPInboundTrunkInfo {
368368
google.protobuf.Timestamp created_at = 17;
369369
google.protobuf.Timestamp updated_at = 18;
370370

371-
// NEXT ID: 19
371+
// Max time a call can last without incoming RTP data received. If unset, defaults are used.
372+
google.protobuf.Duration media_timeout = 19;
373+
374+
// NEXT ID: 20
372375
}
373376

374377
message SIPInboundTrunkUpdate {
@@ -383,6 +386,7 @@ message SIPInboundTrunkUpdate {
383386
(logger.redact_format) = "<redacted ({{ .Size }} bytes)>"
384387
];
385388
optional SIPMediaEncryption media_encryption = 8;
389+
optional google.protobuf.Duration media_timeout = 9;
386390
}
387391

388392
message CreateSIPOutboundTrunkRequest {
@@ -456,7 +460,10 @@ message SIPOutboundTrunkInfo {
456460
google.protobuf.Timestamp created_at = 16;
457461
google.protobuf.Timestamp updated_at = 17;
458462

459-
// NEXT ID: 18
463+
// Max time a call can last without incoming RTP data received. If unset, defaults are used.
464+
google.protobuf.Duration media_timeout = 18;
465+
466+
// NEXT ID: 19
460467
}
461468

462469
message SIPOutboundTrunkUpdate {
@@ -473,8 +480,9 @@ message SIPOutboundTrunkUpdate {
473480
];
474481
optional SIPMediaEncryption media_encryption = 8;
475482
optional string from_host = 10;
483+
optional google.protobuf.Duration media_timeout = 11;
476484

477-
// NEXT ID: 11
485+
// NEXT ID: 12
478486
}
479487

480488
message GetSIPInboundTrunkRequest {
@@ -731,7 +739,10 @@ message SIPOutboundConfig {
731739
// Optional custom hostname for the 'From' SIP header. When set, outbound calls use this host instead of the default project SIP domain.
732740
string from_host = 8;
733741

734-
// NEXT ID: 9
742+
// Max time a call can last without incoming RTP data received. If unset, defaults are used.
743+
google.protobuf.Duration media_timeout = 9;
744+
745+
// NEXT ID: 10
735746
}
736747

737748
// A SIP Participant is a singular SIP session connected to a LiveKit room via

protobufs/rpc/io.proto

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ message EvaluateSIPDispatchRulesResponse {
241241

242242
map<string, string> feature_flags = 23;
243243

244-
// NEXT ID: 25
244+
// Per-call RTP media timeout; if unset, SIP service defaults apply.
245+
google.protobuf.Duration media_timeout = 25;
246+
247+
// NEXT ID: 26
245248
}
246249

247250
message UpdateSIPCallStateRequest {

protobufs/rpc/sip.proto

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ message InternalCreateSIPParticipantRequest {
144144
// Project-level feature flags from ProjectSettings.FeatureFlags
145145
map<string, string> feature_flags = 33;
146146

147-
// NEXT ID: 35
147+
// Per-call RTP media timeout; if unset, SIP service defaults apply.
148+
google.protobuf.Duration media_timeout = 35;
149+
150+
// NEXT ID: 36
148151
}
149152

150153
message InternalCreateSIPParticipantResponse {

rpc/io.pb.go

Lines changed: 8 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rpc/sip.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"google.golang.org/protobuf/proto"
1111

1212
"github.com/livekit/protocol/livekit"
13+
"google.golang.org/protobuf/types/known/durationpb"
1314
)
1415

1516
func (p *GetSIPTrunkAuthenticationRequest) SIPCall() *SIPCall {
@@ -88,6 +89,7 @@ func NewCreateSIPParticipantRequest(
8889
authPass string
8990
hdrToAttr map[string]string
9091
attrToHdr map[string]string
92+
mediaTimeout *durationpb.Duration
9193
)
9294
if trunk != nil {
9395
hostname = trunk.Address
@@ -100,6 +102,7 @@ func NewCreateSIPParticipantRequest(
100102
authPass = trunk.AuthPassword
101103
hdrToAttr = trunk.HeadersToAttributes
102104
attrToHdr = trunk.AttributesToHeaders
105+
mediaTimeout = trunk.MediaTimeout
103106
} else if t := req.Trunk; t != nil {
104107
hostname = t.Hostname
105108
transport = t.Transport
@@ -108,6 +111,7 @@ func NewCreateSIPParticipantRequest(
108111
authPass = t.AuthPassword
109112
hdrToAttr = t.HeadersToAttributes
110113
attrToHdr = t.AttributesToHeaders
114+
mediaTimeout = t.MediaTimeout
111115
}
112116

113117
outboundNumber := req.SipNumber
@@ -201,6 +205,7 @@ func NewCreateSIPParticipantRequest(
201205
WaitUntilAnswered: req.WaitUntilAnswered,
202206
DisplayName: req.DisplayName,
203207
Destination: req.Destination,
208+
MediaTimeout: mediaTimeout,
204209
}, nil
205210
}
206211

0 commit comments

Comments
 (0)