package sip
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"maps"
"math"
"net/netip"
"sort"
"strings"
"github.com/dennwc/iters"
"github.com/twitchtv/twirp"
"golang.org/x/exp/slices"
"github.com/livekit/protocol/livekit"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/rpc"
"github.com/livekit/protocol/utils"
"github.com/livekit/protocol/utils/guid"
)
func NewCallID() string {
return guid.New(utils.SIPCallPrefix)
}
type ErrNoDispatchMatched struct {
NoRules bool
NoTrunks bool
CalledNumber string
}
func (e *ErrNoDispatchMatched) Error() string {
if e.NoRules {
return "No SIP Dispatch Rules defined"
}
if e.NoTrunks {
return fmt.Sprintf("No SIP Trunk or Dispatch Rules matched for %q", e.CalledNumber)
}
return fmt.Sprintf("No SIP Dispatch Rules matched for %q", e.CalledNumber)
}
func DispatchRulePriority(info *livekit.SIPDispatchRuleInfo) int32 {
const (
last = math.MaxInt32
)
priority := int32(0)
switch rule := info.GetRule().GetRule().(type) {
default:
return last
case *livekit.SIPDispatchRule_DispatchRuleDirect:
if rule.DispatchRuleDirect.GetPin() != "" {
priority = 0
} else {
priority = 100
}
case *livekit.SIPDispatchRule_DispatchRuleIndividual:
if rule.DispatchRuleIndividual.GetPin() != "" {
priority = 1
} else {
priority = 101
}
case *livekit.SIPDispatchRule_DispatchRuleCallee:
if rule.DispatchRuleCallee.GetPin() != "" {
priority = 2
} else {
priority = 102
}
}
if len(info.InboundNumbers) == 0 {
priority += 1000
}
if len(info.Numbers) == 0 {
priority += 1000
}
return priority
}
func hasHigherPriority(r1, r2 *livekit.SIPDispatchRuleInfo) bool {
p1, p2 := DispatchRulePriority(r1), DispatchRulePriority(r2)
if p1 < p2 {
return true
} else if p1 > p2 {
return false
}
room1, _, _ := GetPinAndRoom(r1)
room2, _, _ := GetPinAndRoom(r2)
return room1 < room2
}
func SortDispatchRules(rules []*livekit.SIPDispatchRuleInfo) {
sort.Slice(rules, func(i, j int) bool {
return hasHigherPriority(rules[i], rules[j])
})
}
func printID(s string) string {
if s == "" {
return "<new>"
}
return s
}
func printName(s string) string {
if s == "" {
return "<blank name>"
}
return s
}
func ValidateDispatchRules(rules []*livekit.SIPDispatchRuleInfo, opts ...MatchDispatchRuleOpt) error {
_, err := ValidateDispatchRulesIter(iters.Slice(rules), opts...)
return err
}
func ValidateDispatchRulesIter(it iters.Iter[*livekit.SIPDispatchRuleInfo], opts ...MatchDispatchRuleOpt) (best *livekit.SIPDispatchRuleInfo, _ error) {
it = NewDispatchRuleValidator(opts...).ValidateIter(it)
defer it.Close()
for {
r, err := it.Next()
if err == io.EOF {
break
} else if err != nil {
return best, err
}
if best == nil || hasHigherPriority(r, best) {
best = r
}
}
return best, nil
}
func NewDispatchRuleValidator(opts ...MatchDispatchRuleOpt) *DispatchRuleValidator {
var opt matchDispatchRuleOpts
for _, fnc := range opts {
fnc(&opt)
}
opt.defaults()
return &DispatchRuleValidator{
opt: opt,
byRuleKey: make(map[dispatchRuleKey]*livekit.SIPDispatchRuleInfo),
}
}
type dispatchRuleKey struct {
Pin string
Trunk string
InboundNumber string
Number string
}
type DispatchRuleValidator struct {
opt matchDispatchRuleOpts
byRuleKey map[dispatchRuleKey]*livekit.SIPDispatchRuleInfo
}
func (v *DispatchRuleValidator) ValidateIter(it iters.Iter[*livekit.SIPDispatchRuleInfo]) iters.Iter[*livekit.SIPDispatchRuleInfo] {
return &dispatchRuleValidatorIter{v: v, it: it}
}
func (v *DispatchRuleValidator) Validate(r *livekit.SIPDispatchRuleInfo) error {
_, pin, err := GetPinAndRoom(r)
if err != nil {
return err
}
trunks := r.TrunkIds
if len(trunks) == 0 {
trunks = []string{""}
}
inboundNumbers := r.InboundNumbers
if len(inboundNumbers) == 0 {
inboundNumbers = []string{""}
}
numbers := r.Numbers
if len(numbers) == 0 {
numbers = []string{""}
}
for _, trunk := range trunks {
for _, inboundNumber := range inboundNumbers {
for _, number := range numbers {
key := dispatchRuleKey{Pin: pin, Trunk: trunk, Number: NormalizeNumber(number), InboundNumber: NormalizeNumber(inboundNumber)}
r2 := v.byRuleKey[key]
if r2 != nil {
v.opt.Conflict(r, r2, DispatchRuleConflictGeneric)
if v.opt.AllowConflicts {
continue
}
return twirp.NewErrorf(twirp.InvalidArgument,
"Dispatch rule for the same trunk, inbound number, number, and PIN combination already exists in dispatch rule %q %q",
printID(r2.SipDispatchRuleId), printName(r2.Name))
}
v.byRuleKey[key] = r
}
}
}
return nil
}
type dispatchRuleValidatorIter struct {
v *DispatchRuleValidator
it iters.Iter[*livekit.SIPDispatchRuleInfo]
}
func (v *dispatchRuleValidatorIter) Next() (*livekit.SIPDispatchRuleInfo, error) {
r, err := v.it.Next()
if err != nil {
return nil, err
}
r = v.v.opt.Replace(r)
if err = v.v.Validate(r); err != nil {
return nil, err
}
return r, nil
}
func (v *dispatchRuleValidatorIter) Close() {
v.it.Close()
}
func SelectDispatchRule(rules []*livekit.SIPDispatchRuleInfo, req *rpc.EvaluateSIPDispatchRulesRequest, opts ...MatchDispatchRuleOpt) (*livekit.SIPDispatchRuleInfo, error) {
return ValidateDispatchRulesIter(iters.Slice(rules), opts...)
}
func GetPinAndRoom(info *livekit.SIPDispatchRuleInfo) (room, pin string, err error) {
switch rule := info.GetRule().GetRule().(type) {
default:
return "", "", fmt.Errorf("Unsupported SIP Dispatch Rule: %T", rule)
case *livekit.SIPDispatchRule_DispatchRuleDirect:
pin = rule.DispatchRuleDirect.GetPin()
room = rule.DispatchRuleDirect.GetRoomName()
case *livekit.SIPDispatchRule_DispatchRuleIndividual:
pin = rule.DispatchRuleIndividual.GetPin()
room = rule.DispatchRuleIndividual.GetRoomPrefix()
case *livekit.SIPDispatchRule_DispatchRuleCallee:
pin = rule.DispatchRuleCallee.GetPin()
room = rule.DispatchRuleCallee.GetRoomPrefix()
}
return room, pin, nil
}
func printNumbers(numbers []string) string {
if len(numbers) == 0 {
return "<any>"
}
return fmt.Sprintf("%q", numbers)
}
func NormalizeNumber(num string) string {
return livekit.NormalizeNumber(num)
}
func validateTrunkInbound(byInbound map[string]*livekit.SIPInboundTrunkInfo, t *livekit.SIPInboundTrunkInfo, opt *matchTrunkOpts) error {
if len(t.AllowedNumbers) == 0 {
if t2 := byInbound[""]; t2 != nil {
opt.Conflict(t, t2, TrunkConflictCalledNumber)
if opt.AllowConflicts {
return nil
}
return twirp.NewErrorf(twirp.InvalidArgument, "Conflicting inbound SIP Trunks: %q and %q, using the same number(s) %s without AllowedNumbers set",
printID(t.SipTrunkId), printID(t2.SipTrunkId), printNumbers(t.Numbers))
}
byInbound[""] = t
} else {
for _, num := range t.AllowedNumbers {
inboundKey := NormalizeNumber(num)
t2 := byInbound[inboundKey]
if t2 != nil {
opt.Conflict(t, t2, TrunkConflictCallingNumber)
if opt.AllowConflicts {
continue
}
return twirp.NewErrorf(twirp.InvalidArgument, "Conflicting inbound SIP Trunks: %q and %q, using the same number(s) %s and AllowedNumber %q",
printID(t.SipTrunkId), printID(t2.SipTrunkId), printNumbers(t.Numbers), num)
}
byInbound[inboundKey] = t
}
}
return nil
}
func ValidateTrunks(trunks []*livekit.SIPInboundTrunkInfo, opts ...MatchTrunkOpt) error {
return ValidateTrunksIter(iters.Slice(trunks), opts...)
}
func ValidateTrunksIter(it iters.Iter[*livekit.SIPInboundTrunkInfo], opts ...MatchTrunkOpt) error {
defer it.Close()
var opt matchTrunkOpts
for _, fnc := range opts {
fnc(&opt)
}
opt.defaults()
byOutboundAndInbound := make(map[string]map[string]*livekit.SIPInboundTrunkInfo)
for {
t, err := it.Next()
if err == io.EOF {
break
} else if err != nil {
return err
}
t = opt.Replace(t)
if len(t.Numbers) == 0 {
byInbound := byOutboundAndInbound[""]
if byInbound == nil {
byInbound = make(map[string]*livekit.SIPInboundTrunkInfo)
byOutboundAndInbound[""] = byInbound
}
if err := validateTrunkInbound(byInbound, t, &opt); err != nil {
return err
}
} else {
for _, num := range t.Numbers {
byInbound := byOutboundAndInbound[num]
if byInbound == nil {
byInbound = make(map[string]*livekit.SIPInboundTrunkInfo)
byOutboundAndInbound[num] = byInbound
}
if err := validateTrunkInbound(byInbound, t, &opt); err != nil {
return err
}
}
}
}
return nil
}
func isValidMask(mask string) bool {
if strings.ContainsAny(mask, "()+*;, \t\n\r") {
return false
}
if strings.Contains(mask, "://") {
return false
}
return true
}
func filterInvalidAddrMasks(masks []string) []string {
if len(masks) == 0 {
return nil
}
out := make([]string, 0, len(masks))
for _, m := range masks {
if isValidMask(m) {
out = append(out, m)
}
}
return out
}
func matchAddrMask(ip netip.Addr, mask string) bool {
if !strings.Contains(mask, "/") {
expIP, err := netip.ParseAddr(mask)
if err != nil {
return false
}
return ip == expIP
}
pref, err := netip.ParsePrefix(mask)
if err != nil {
return false
}
return pref.Contains(ip)
}
func matchAddrMasks(addr string, host string, masks []string) bool {
ip, err := netip.ParseAddr(addr)
if err != nil {
return true
}
masks = filterInvalidAddrMasks(masks)
if len(masks) == 0 {
return true
}
for _, mask := range masks {
if mask == host || matchAddrMask(ip, mask) {
return true
}
}
return false
}
func matchNumbers(num string, allowed []string) bool {
if len(allowed) == 0 {
return true
}
norm := NormalizeNumber(num)
for _, allow := range allowed {
if num == allow || norm == NormalizeNumber(allow) {
return true
}
}
return false
}
type TrunkMatchType int
const (
TrunkMatchEmpty TrunkMatchType = iota
TrunkMatchNone
TrunkMatchDefault
TrunkMatchSpecific
)
type TrunkMatchResult struct {
Trunk *livekit.SIPInboundTrunkInfo
MatchType TrunkMatchType
DefaultTrunkCount int
}
func MatchTrunk(trunks []*livekit.SIPInboundTrunkInfo, call *rpc.SIPCall, opts ...MatchTrunkOpt) (*livekit.SIPInboundTrunkInfo, error) {
return MatchTrunkIter(iters.Slice(trunks), call, opts...)
}
func MatchTrunkDetailed(it iters.Iter[*livekit.SIPInboundTrunkInfo], call *rpc.SIPCall, opts ...MatchTrunkOpt) (*TrunkMatchResult, error) {
defer it.Close()
var opt matchTrunkOpts
for _, fnc := range opts {
fnc(&opt)
}
opt.defaults()
result := &TrunkMatchResult{
MatchType: TrunkMatchEmpty, }
var (
selectedTrunk *livekit.SIPInboundTrunkInfo
defaultTrunk *livekit.SIPInboundTrunkInfo
defaultTrunkPrev *livekit.SIPInboundTrunkInfo
sawAnyTrunk bool
)
calledNorm := NormalizeNumber(call.To.User)
for {
tr, err := it.Next()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if !sawAnyTrunk {
sawAnyTrunk = true
result.MatchType = TrunkMatchNone }
tr = opt.Replace(tr)
if !matchNumbers(call.From.User, tr.AllowedNumbers) {
if !opt.Filtered(tr, TrunkFilteredCallingNumberDisallowed) {
continue
}
}
if !matchAddrMasks(call.SourceIp, call.From.Host, tr.AllowedAddresses) {
if !opt.Filtered(tr, TrunkFilteredSourceAddressDisallowed) {
continue
}
}
if len(tr.Numbers) == 0 {
defaultTrunkPrev = defaultTrunk
defaultTrunk = tr
result.DefaultTrunkCount++
} else {
for _, num := range tr.Numbers {
if num == call.To.User || NormalizeNumber(num) == calledNorm {
if selectedTrunk != nil {
opt.Conflict(selectedTrunk, tr, TrunkConflictCalledNumber)
if opt.AllowConflicts {
continue
}
return nil, twirp.NewErrorf(twirp.FailedPrecondition, "Multiple SIP Trunks matched for %q", call.To.User)
}
selectedTrunk = tr
if opt.AllowConflicts {
result.Trunk = selectedTrunk
result.MatchType = TrunkMatchSpecific
return result, nil
}
} else {
opt.Filtered(tr, TrunkFilteredCalledNumberDisallowed)
}
}
}
}
if selectedTrunk != nil {
result.Trunk = selectedTrunk
result.MatchType = TrunkMatchSpecific
return result, nil
}
if result.DefaultTrunkCount > 1 {
opt.Conflict(defaultTrunk, defaultTrunkPrev, TrunkConflictDefault)
if !opt.AllowConflicts {
return nil, twirp.NewErrorf(twirp.FailedPrecondition, "Multiple default SIP Trunks matched for %q", call.To.User)
}
}
if defaultTrunk != nil {
result.Trunk = defaultTrunk
result.MatchType = TrunkMatchDefault
}
return result, nil
}
type matchTrunkOpts struct {
AllowConflicts bool
Filtered TrunkFilteredFunc
Conflict TrunkConflictFunc
Replace TrunkReplaceFunc
}
func (opt *matchTrunkOpts) defaults() {
if opt.Filtered == nil {
opt.Filtered = func(_ *livekit.SIPInboundTrunkInfo, _ TrunkFilteredReason) bool {
return false
}
}
if opt.Conflict == nil {
opt.Conflict = func(_, _ *livekit.SIPInboundTrunkInfo, _ TrunkConflictReason) {}
}
if opt.Replace == nil {
opt.Replace = func(t *livekit.SIPInboundTrunkInfo) *livekit.SIPInboundTrunkInfo {
return t
}
}
}
type MatchTrunkOpt func(opt *matchTrunkOpts)
type TrunkFilteredReason int
const (
TrunkFilteredInvalid = TrunkFilteredReason(iota)
TrunkFilteredCallingNumberDisallowed
TrunkFilteredCalledNumberDisallowed
TrunkFilteredSourceAddressDisallowed
)
type TrunkFilteredFunc func(tr *livekit.SIPInboundTrunkInfo, reason TrunkFilteredReason) bool
func WithTrunkFiltered(fnc TrunkFilteredFunc) MatchTrunkOpt {
return func(opt *matchTrunkOpts) {
opt.Filtered = fnc
}
}
type TrunkConflictReason int
const (
TrunkConflictDefault = TrunkConflictReason(iota)
TrunkConflictCalledNumber
TrunkConflictCallingNumber
)
type TrunkConflictFunc func(t1, t2 *livekit.SIPInboundTrunkInfo, reason TrunkConflictReason)
func WithAllowTrunkConflicts() MatchTrunkOpt {
return func(opt *matchTrunkOpts) {
opt.AllowConflicts = true
}
}
func WithTrunkConflict(fnc TrunkConflictFunc) MatchTrunkOpt {
return func(opt *matchTrunkOpts) {
opt.Conflict = fnc
}
}
type TrunkReplaceFunc func(t *livekit.SIPInboundTrunkInfo) *livekit.SIPInboundTrunkInfo
func WithTrunkReplace(fnc TrunkReplaceFunc) MatchTrunkOpt {
return func(opt *matchTrunkOpts) {
opt.Replace = fnc
}
}
func MatchTrunkIter(it iters.Iter[*livekit.SIPInboundTrunkInfo], call *rpc.SIPCall, opts ...MatchTrunkOpt) (*livekit.SIPInboundTrunkInfo, error) {
result, err := MatchTrunkDetailed(it, call, opts...)
if err != nil {
return nil, err
}
return result.Trunk, nil
}
func MatchDispatchRule(trunk *livekit.SIPInboundTrunkInfo, rules []*livekit.SIPDispatchRuleInfo, req *rpc.EvaluateSIPDispatchRulesRequest, opts ...MatchDispatchRuleOpt) (*livekit.SIPDispatchRuleInfo, error) {
return MatchDispatchRuleIter(trunk, iters.Slice(rules), req, opts...)
}
type matchDispatchRuleOpts struct {
AllowConflicts bool
Conflict DispatchRuleConflictFunc
Replace DispatchRuleReplaceFunc
}
func (opt *matchDispatchRuleOpts) defaults() {
if opt.Conflict == nil {
opt.Conflict = func(_, _ *livekit.SIPDispatchRuleInfo, _ DispatchRuleConflictReason) {}
}
if opt.Replace == nil {
opt.Replace = func(r *livekit.SIPDispatchRuleInfo) *livekit.SIPDispatchRuleInfo {
return r
}
}
}
type MatchDispatchRuleOpt func(opt *matchDispatchRuleOpts)
type DispatchRuleConflictReason int
const (
DispatchRuleConflictGeneric = DispatchRuleConflictReason(iota)
)
type DispatchRuleConflictFunc func(r1, r2 *livekit.SIPDispatchRuleInfo, reason DispatchRuleConflictReason)
func WithAllowDispatchRuleConflicts() MatchDispatchRuleOpt {
return func(opt *matchDispatchRuleOpts) {
opt.AllowConflicts = true
}
}
func WithDispatchRuleConflict(fnc DispatchRuleConflictFunc) MatchDispatchRuleOpt {
return func(opt *matchDispatchRuleOpts) {
opt.Conflict = fnc
}
}
type DispatchRuleReplaceFunc func(r *livekit.SIPDispatchRuleInfo) *livekit.SIPDispatchRuleInfo
func WithDispatchRuleReplace(fnc DispatchRuleReplaceFunc) MatchDispatchRuleOpt {
return func(opt *matchDispatchRuleOpts) {
opt.Replace = fnc
}
}
func MatchDispatchRuleIter(trunk *livekit.SIPInboundTrunkInfo, rules iters.Iter[*livekit.SIPDispatchRuleInfo], req *rpc.EvaluateSIPDispatchRulesRequest, opts ...MatchDispatchRuleOpt) (*livekit.SIPDispatchRuleInfo, error) {
rules = NewDispatchRuleValidator(opts...).ValidateIter(rules)
defer rules.Close()
var (
specificRule *livekit.SIPDispatchRuleInfo
specificRuleCnt int
defaultRule *livekit.SIPDispatchRuleInfo
defaultRuleCnt int
)
noPin := req.NoPin
sentPin := req.GetPin()
for {
info, err := rules.Next()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
if len(info.InboundNumbers) != 0 && !slices.Contains(info.InboundNumbers, req.CallingNumber) {
continue
}
if len(info.Numbers) != 0 && !slices.Contains(info.Numbers, req.CalledNumber) {
continue
}
_, rulePin, err := GetPinAndRoom(info)
if err != nil {
logger.Errorw("Invalid SIP Dispatch Rule", err, "dispatchRuleID", info.SipDispatchRuleId)
continue
}
if noPin {
if rulePin != "" {
continue
}
} else if sentPin != "" {
if rulePin == "" {
continue
}
if sentPin != rulePin {
continue
}
}
if len(info.TrunkIds) == 0 {
defaultRuleCnt++
if defaultRule == nil || hasHigherPriority(info, defaultRule) {
defaultRule = info
}
continue
}
if trunk == nil {
continue
}
if !slices.Contains(info.TrunkIds, trunk.SipTrunkId) {
continue
}
specificRuleCnt++
if specificRule == nil || hasHigherPriority(info, specificRule) {
specificRule = info
}
}
if specificRuleCnt == 0 && defaultRuleCnt == 0 {
err := &ErrNoDispatchMatched{NoRules: true, NoTrunks: trunk == nil, CalledNumber: req.CalledNumber}
return nil, twirp.WrapError(twirp.NewErrorf(twirp.FailedPrecondition, "%s", err.Error()), err)
}
if specificRule != nil {
return specificRule, nil
}
if defaultRule != nil {
return defaultRule, nil
}
err := &ErrNoDispatchMatched{NoRules: false, NoTrunks: trunk == nil, CalledNumber: req.CalledNumber}
return nil, twirp.WrapError(twirp.NewErrorf(twirp.FailedPrecondition, "%s", err.Error()), err)
}
func EvaluateDispatchRule(projectID string, trunk *livekit.SIPInboundTrunkInfo, rule *livekit.SIPDispatchRuleInfo, req *rpc.EvaluateSIPDispatchRulesRequest) (*rpc.EvaluateSIPDispatchRulesResponse, error) {
call := req.SIPCall()
sentPin := req.GetPin()
trunkID := req.SipTrunkId
if trunk != nil {
trunkID = trunk.SipTrunkId
}
enc := livekit.SIPMediaEncryption_SIP_MEDIA_ENCRYPT_DISABLE
if trunk != nil {
enc = trunk.MediaEncryption
}
attrs := maps.Clone(rule.Attributes)
if attrs == nil {
attrs = make(map[string]string)
}
for k, v := range req.ExtraAttributes {
attrs[k] = v
}
attrs[livekit.AttrSIPCallID] = call.LkCallId
attrs[livekit.AttrSIPTrunkID] = trunkID
to := call.To.User
from := call.From.User
fromName := "Phone " + from
fromID := "sip_" + from
if rule.HidePhoneNumber {
h := sha256.Sum256([]byte(call.From.User))
fromID = "sip_" + hex.EncodeToString(h[:8])
n := 4
if len(from) <= 4 {
n = 1
}
from = from[len(from)-n:]
fromName = "Phone " + from
} else {
attrs[livekit.AttrSIPPhoneNumber] = call.From.User
attrs[livekit.AttrSIPHostName] = call.From.Host
attrs[livekit.AttrSIPTrunkNumber] = call.To.User
}
room, rulePin, err := GetPinAndRoom(rule)
if err != nil {
return nil, err
}
if rulePin != "" {
if sentPin == "" {
return &rpc.EvaluateSIPDispatchRulesResponse{
ProjectId: projectID,
SipTrunkId: trunkID,
SipDispatchRuleId: rule.SipDispatchRuleId,
Result: rpc.SIPDispatchResult_REQUEST_PIN,
MediaEncryption: enc,
RequestPin: true,
}, nil
}
if rulePin != sentPin {
return nil, twirp.NewError(twirp.PermissionDenied, "Incorrect PIN for SIP room")
}
} else {
}
switch rule := rule.GetRule().GetRule().(type) {
case *livekit.SIPDispatchRule_DispatchRuleIndividual:
room = from
if pref := rule.DispatchRuleIndividual.GetRoomPrefix(); pref != "" {
room = pref + "_" + from
}
if !rule.DispatchRuleIndividual.NoRandomness {
room += "_" + guid.New("")
}
case *livekit.SIPDispatchRule_DispatchRuleCallee:
room = to
if pref := rule.DispatchRuleCallee.GetRoomPrefix(); pref != "" {
room = pref + "_" + to
}
if rule.DispatchRuleCallee.Randomize {
room += "_" + guid.New("")
}
}
attrs[livekit.AttrSIPDispatchRuleID] = rule.SipDispatchRuleId
resp := &rpc.EvaluateSIPDispatchRulesResponse{
ProjectId: projectID,
SipTrunkId: trunkID,
SipDispatchRuleId: rule.SipDispatchRuleId,
Result: rpc.SIPDispatchResult_ACCEPT,
RoomName: room,
ParticipantIdentity: fromID,
ParticipantName: fromName,
ParticipantMetadata: rule.Metadata,
ParticipantAttributes: attrs,
RoomPreset: rule.RoomPreset,
RoomConfig: rule.RoomConfig,
MediaEncryption: enc,
}
krispEnabled := false
if trunk != nil {
resp.Headers = trunk.Headers
resp.HeadersToAttributes = trunk.HeadersToAttributes
resp.AttributesToHeaders = trunk.AttributesToHeaders
resp.IncludeHeaders = trunk.IncludeHeaders
resp.RingingTimeout = trunk.RingingTimeout
resp.MaxCallDuration = trunk.MaxCallDuration
krispEnabled = krispEnabled || trunk.KrispEnabled
}
if rule != nil {
krispEnabled = krispEnabled || rule.KrispEnabled
if rule.MediaEncryption != 0 {
resp.MediaEncryption = rule.MediaEncryption
}
}
if krispEnabled {
resp.EnabledFeatures = append(resp.EnabledFeatures, livekit.SIPFeature_KRISP_ENABLED)
}
return resp, nil
}