livekit_api/services/
sip.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use livekit_protocol as proto;
16use std::collections::HashMap;
17use std::time::Duration;
18
19use crate::access_token::SIPGrants;
20use crate::get_env_keys;
21use crate::services::twirp_client::TwirpClient;
22use crate::services::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE};
23use pbjson_types::Duration as ProtoDuration;
24
25const SVC: &str = "SIP";
26
27#[derive(Debug)]
28pub struct SIPClient {
29    base: ServiceBase,
30    client: TwirpClient,
31}
32
33#[deprecated]
34#[derive(Default, Clone, Debug)]
35pub struct CreateSIPTrunkOptions {
36    /// Human-readable name for the Trunk.
37    pub name: String,
38    /// Optional free-form metadata.
39    pub metadata: String,
40    /// CIDR or IPs that traffic is accepted from
41    /// An empty list means all inbound traffic is accepted.
42    pub inbound_addresses: Vec<String>,
43    /// Accepted `To` values. This Trunk will only accept a call made to
44    /// these numbers. This allows you to have distinct Trunks for different phone
45    /// numbers at the same provider.
46    pub inbound_numbers: Vec<String>,
47    /// Username and password used to authenticate inbound SIP invites
48    /// May be empty to have no Authentication
49    pub inbound_username: String,
50    pub inbound_password: String,
51
52    /// IP that SIP INVITE is sent too
53    pub outbound_address: String,
54    /// Username and password used to authenticate outbound SIP invites
55    /// May be empty to have no Authentication
56    pub outbound_username: String,
57    pub outbound_password: String,
58}
59
60#[derive(Default, Clone, Debug)]
61pub struct CreateSIPInboundTrunkOptions {
62    /// Optional free-form metadata.
63    pub metadata: Option<String>,
64    /// CIDR or IPs that traffic is accepted from
65    /// An empty list means all inbound traffic is accepted.
66    pub allowed_addresses: Option<Vec<String>>,
67    /// Accepted `To` values. This Trunk will only accept a call made to
68    /// these numbers. This allows you to have distinct Trunks for different phone
69    /// numbers at the same provider.
70    pub allowed_numbers: Option<Vec<String>>,
71    /// Username and password used to authenticate inbound SIP invites
72    /// May be empty to have no Authentication
73    pub auth_username: Option<String>,
74    pub auth_password: Option<String>,
75    pub headers: Option<HashMap<String, String>>,
76    pub headers_to_attributes: Option<HashMap<String, String>>,
77    pub attributes_to_headers: Option<HashMap<String, String>>,
78    pub max_call_duration: Option<Duration>,
79    pub ringing_timeout: Option<Duration>,
80    pub krisp_enabled: Option<bool>,
81}
82
83#[derive(Default, Clone, Debug)]
84pub struct CreateSIPOutboundTrunkOptions {
85    pub transport: proto::SipTransport,
86    /// Optional free-form metadata.
87    pub metadata: String,
88    /// Username and password used to authenticate outbound SIP invites
89    /// May be empty to have no Authentication
90    pub auth_username: String,
91    pub auth_password: String,
92
93    pub headers: Option<HashMap<String, String>>,
94    pub headers_to_attributes: Option<HashMap<String, String>>,
95    pub attributes_to_headers: Option<HashMap<String, String>>,
96}
97
98#[deprecated]
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum ListSIPTrunkFilter {
101    All,
102}
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum ListSIPInboundTrunkFilter {
105    All,
106}
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub enum ListSIPOutboundTrunkFilter {
109    All,
110}
111
112#[derive(Default, Clone, Debug)]
113pub struct CreateSIPDispatchRuleOptions {
114    pub name: String,
115    pub metadata: String,
116    pub attributes: HashMap<String, String>,
117    /// What trunks are accepted for this dispatch rule
118    /// If empty all trunks will match this dispatch rule
119    pub trunk_ids: Vec<String>,
120    pub allowed_numbers: Vec<String>,
121    pub hide_phone_number: bool,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub enum ListSIPDispatchRuleFilter {
126    All,
127}
128
129#[derive(Default, Clone, Debug)]
130pub struct CreateSIPParticipantOptions {
131    /// Optional identity of the participant in LiveKit room
132    pub participant_identity: String,
133    /// Optionally set the name of the participant in a LiveKit room
134    pub participant_name: Option<String>,
135    /// Optionally set the free-form metadata of the participant in a LiveKit room
136    pub participant_metadata: Option<String>,
137    pub participant_attributes: Option<HashMap<String, String>>,
138    // What number should be dialed via SIP
139    pub sip_number: Option<String>,
140    /// Optionally send following DTMF digits (extension codes) when making a call.
141    /// Character 'w' can be used to add a 0.5 sec delay.
142    pub dtmf: Option<String>,
143    /// Wait for the call to be answered before returning.
144    ///
145    /// When `true`, the request blocks until the call is answered or fails,
146    /// and returns SIP error codes (e.g., 486 Busy, 603 Decline) on failure.
147    /// When `false` (default), returns immediately while the call is still dialing.
148    pub wait_until_answered: Option<bool>,
149    /// Optionally play dialtone in the room as an audible indicator for existing participants
150    pub play_dialtone: Option<bool>,
151    pub hide_phone_number: Option<bool>,
152    pub ringing_timeout: Option<Duration>,
153    pub max_call_duration: Option<Duration>,
154    pub enable_krisp: Option<bool>,
155}
156
157impl SIPClient {
158    pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
159        Self {
160            base: ServiceBase::with_api_key(api_key, api_secret),
161            client: TwirpClient::new(host, LIVEKIT_PACKAGE, None),
162        }
163    }
164
165    pub fn new(host: &str) -> ServiceResult<Self> {
166        let (api_key, api_secret) = get_env_keys()?;
167        Ok(Self::with_api_key(host, &api_key, &api_secret))
168    }
169
170    fn duration_to_proto(d: Option<Duration>) -> Option<ProtoDuration> {
171        d.map(|d| ProtoDuration { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32 })
172    }
173
174    pub async fn create_sip_inbound_trunk(
175        &self,
176        name: String,
177        numbers: Vec<String>,
178        options: CreateSIPInboundTrunkOptions,
179    ) -> ServiceResult<proto::SipInboundTrunkInfo> {
180        self.client
181            .request(
182                SVC,
183                "CreateSIPInboundTrunk",
184                proto::CreateSipInboundTrunkRequest {
185                    trunk: Some(proto::SipInboundTrunkInfo {
186                        sip_trunk_id: Default::default(),
187                        name,
188                        numbers,
189                        metadata: options.metadata.unwrap_or_default(),
190                        allowed_numbers: options.allowed_numbers.unwrap_or_default(),
191                        allowed_addresses: options.allowed_addresses.unwrap_or_default(),
192                        auth_username: options.auth_username.unwrap_or_default(),
193                        auth_password: options.auth_password.unwrap_or_default(),
194                        headers: options.headers.unwrap_or_default(),
195                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
196                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
197                        krisp_enabled: options.krisp_enabled.unwrap_or(false),
198                        max_call_duration: Self::duration_to_proto(options.max_call_duration),
199                        ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
200
201                        // TODO: support these attributes
202                        include_headers: Default::default(),
203                        media_encryption: Default::default(),
204                    }),
205                },
206                self.base.auth_header(
207                    Default::default(),
208                    Some(SIPGrants { admin: true, ..Default::default() }),
209                )?,
210            )
211            .await
212            .map_err(Into::into)
213    }
214
215    pub async fn create_sip_outbound_trunk(
216        &self,
217        name: String,
218        address: String,
219        numbers: Vec<String>,
220        options: CreateSIPOutboundTrunkOptions,
221    ) -> ServiceResult<proto::SipOutboundTrunkInfo> {
222        self.client
223            .request(
224                SVC,
225                "CreateSIPOutboundTrunk",
226                proto::CreateSipOutboundTrunkRequest {
227                    trunk: Some(proto::SipOutboundTrunkInfo {
228                        sip_trunk_id: Default::default(),
229                        name,
230                        address,
231                        numbers,
232                        transport: options.transport as i32,
233                        metadata: options.metadata,
234
235                        auth_username: options.auth_username.to_owned(),
236                        auth_password: options.auth_password.to_owned(),
237
238                        headers: options.headers.unwrap_or_default(),
239                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
240                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
241
242                        // TODO: support these attributes
243                        include_headers: Default::default(),
244                        media_encryption: Default::default(),
245                        destination_country: Default::default(),
246                    }),
247                },
248                self.base.auth_header(
249                    Default::default(),
250                    Some(SIPGrants { admin: true, ..Default::default() }),
251                )?,
252            )
253            .await
254            .map_err(Into::into)
255    }
256
257    #[deprecated]
258    pub async fn list_sip_trunk(
259        &self,
260        filter: ListSIPTrunkFilter,
261    ) -> ServiceResult<Vec<proto::SipTrunkInfo>> {
262        let resp: proto::ListSipTrunkResponse = self
263            .client
264            .request(
265                SVC,
266                "ListSIPTrunk",
267                proto::ListSipTrunkRequest {
268                    // TODO support these attributes
269                    page: Default::default(),
270                },
271                self.base.auth_header(
272                    Default::default(),
273                    Some(SIPGrants { admin: true, ..Default::default() }),
274                )?,
275            )
276            .await?;
277
278        Ok(resp.items)
279    }
280
281    pub async fn list_sip_inbound_trunk(
282        &self,
283        filter: ListSIPInboundTrunkFilter,
284    ) -> ServiceResult<Vec<proto::SipInboundTrunkInfo>> {
285        let resp: proto::ListSipInboundTrunkResponse = self
286            .client
287            .request(
288                SVC,
289                "ListSIPInboundTrunk",
290                proto::ListSipInboundTrunkRequest {
291                    // TODO: support these attributes
292                    page: Default::default(),
293                    trunk_ids: Default::default(),
294                    numbers: Default::default(),
295                },
296                self.base.auth_header(
297                    Default::default(),
298                    Some(SIPGrants { admin: true, ..Default::default() }),
299                )?,
300            )
301            .await?;
302
303        Ok(resp.items)
304    }
305
306    pub async fn list_sip_outbound_trunk(
307        &self,
308        filter: ListSIPOutboundTrunkFilter,
309    ) -> ServiceResult<Vec<proto::SipOutboundTrunkInfo>> {
310        let resp: proto::ListSipOutboundTrunkResponse = self
311            .client
312            .request(
313                SVC,
314                "ListSIPOutboundTrunk",
315                proto::ListSipOutboundTrunkRequest {
316                    // TODO: support these attributes
317                    page: Default::default(),
318                    trunk_ids: Default::default(),
319                    numbers: Default::default(),
320                },
321                self.base.auth_header(
322                    Default::default(),
323                    Some(SIPGrants { admin: true, ..Default::default() }),
324                )?,
325            )
326            .await?;
327
328        Ok(resp.items)
329    }
330
331    pub async fn delete_sip_trunk(&self, sip_trunk_id: &str) -> ServiceResult<proto::SipTrunkInfo> {
332        self.client
333            .request(
334                SVC,
335                "DeleteSIPTrunk",
336                proto::DeleteSipTrunkRequest { sip_trunk_id: sip_trunk_id.to_owned() },
337                self.base.auth_header(
338                    Default::default(),
339                    Some(SIPGrants { admin: true, ..Default::default() }),
340                )?,
341            )
342            .await
343            .map_err(Into::into)
344    }
345
346    pub async fn create_sip_dispatch_rule(
347        &self,
348        rule: proto::sip_dispatch_rule::Rule,
349        options: CreateSIPDispatchRuleOptions,
350    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
351        self.client
352            .request(
353                SVC,
354                "CreateSIPDispatchRule",
355                proto::CreateSipDispatchRuleRequest {
356                    name: options.name,
357                    metadata: options.metadata,
358                    attributes: options.attributes,
359                    trunk_ids: options.trunk_ids.to_owned(),
360                    inbound_numbers: options.allowed_numbers.to_owned(),
361                    hide_phone_number: options.hide_phone_number,
362                    rule: Some(proto::SipDispatchRule { rule: Some(rule.to_owned()) }),
363
364                    ..Default::default()
365                },
366                self.base.auth_header(
367                    Default::default(),
368                    Some(SIPGrants { admin: true, ..Default::default() }),
369                )?,
370            )
371            .await
372            .map_err(Into::into)
373    }
374
375    pub async fn list_sip_dispatch_rule(
376        &self,
377        filter: ListSIPDispatchRuleFilter,
378    ) -> ServiceResult<Vec<proto::SipDispatchRuleInfo>> {
379        let resp: proto::ListSipDispatchRuleResponse = self
380            .client
381            .request(
382                SVC,
383                "ListSIPDispatchRule",
384                proto::ListSipDispatchRuleRequest {
385                    // TODO: support these attributes
386                    page: Default::default(),
387                    dispatch_rule_ids: Default::default(),
388                    trunk_ids: Default::default(),
389                },
390                self.base.auth_header(
391                    Default::default(),
392                    Some(SIPGrants { admin: true, ..Default::default() }),
393                )?,
394            )
395            .await?;
396
397        Ok(resp.items)
398    }
399
400    pub async fn delete_sip_dispatch_rule(
401        &self,
402        sip_dispatch_rule_id: &str,
403    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
404        self.client
405            .request(
406                SVC,
407                "DeleteSIPDispatchRule",
408                proto::DeleteSipDispatchRuleRequest {
409                    sip_dispatch_rule_id: sip_dispatch_rule_id.to_owned(),
410                },
411                self.base.auth_header(
412                    Default::default(),
413                    Some(SIPGrants { admin: true, ..Default::default() }),
414                )?,
415            )
416            .await
417            .map_err(Into::into)
418    }
419
420    pub async fn create_sip_participant(
421        &self,
422        sip_trunk_id: String,
423        call_to: String,
424        room_name: String,
425        options: CreateSIPParticipantOptions,
426        outbound_trunk_config: Option<proto::SipOutboundConfig>,
427    ) -> ServiceResult<proto::SipParticipantInfo> {
428        self.client
429            .request(
430                SVC,
431                "CreateSIPParticipant",
432                proto::CreateSipParticipantRequest {
433                    sip_trunk_id: sip_trunk_id.to_owned(),
434                    trunk: outbound_trunk_config,
435                    sip_call_to: call_to.to_owned(),
436                    sip_number: options.sip_number.to_owned().unwrap_or_default(),
437                    room_name: room_name.to_owned(),
438                    participant_identity: options.participant_identity.to_owned(),
439                    participant_name: options.participant_name.to_owned().unwrap_or_default(),
440                    participant_metadata: options
441                        .participant_metadata
442                        .to_owned()
443                        .unwrap_or_default(),
444                    participant_attributes: options
445                        .participant_attributes
446                        .to_owned()
447                        .unwrap_or_default(),
448                    dtmf: options.dtmf.to_owned().unwrap_or_default(),
449                    wait_until_answered: options.wait_until_answered.unwrap_or(false),
450                    play_ringtone: options.play_dialtone.unwrap_or(false),
451                    play_dialtone: options.play_dialtone.unwrap_or(false),
452                    hide_phone_number: options.hide_phone_number.unwrap_or(false),
453                    max_call_duration: Self::duration_to_proto(options.max_call_duration),
454                    ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
455
456                    // TODO: rename local proto as well
457                    krisp_enabled: options.enable_krisp.unwrap_or(false),
458
459                    // TODO: support these attributes
460                    headers: Default::default(),
461                    include_headers: Default::default(),
462                    media_encryption: Default::default(),
463                    ..Default::default()
464                },
465                self.base.auth_header(
466                    Default::default(),
467                    Some(SIPGrants { call: true, ..Default::default() }),
468                )?,
469            )
470            .await
471            .map_err(Into::into)
472    }
473}