Skip to main content

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                        created_at: Default::default(),
205                        updated_at: Default::default(),
206                    }),
207                },
208                self.base.auth_header(
209                    Default::default(),
210                    Some(SIPGrants { admin: true, ..Default::default() }),
211                )?,
212            )
213            .await
214            .map_err(Into::into)
215    }
216
217    pub async fn create_sip_outbound_trunk(
218        &self,
219        name: String,
220        address: String,
221        numbers: Vec<String>,
222        options: CreateSIPOutboundTrunkOptions,
223    ) -> ServiceResult<proto::SipOutboundTrunkInfo> {
224        self.client
225            .request(
226                SVC,
227                "CreateSIPOutboundTrunk",
228                proto::CreateSipOutboundTrunkRequest {
229                    trunk: Some(proto::SipOutboundTrunkInfo {
230                        sip_trunk_id: Default::default(),
231                        name,
232                        address,
233                        numbers,
234                        transport: options.transport as i32,
235                        metadata: options.metadata,
236
237                        auth_username: options.auth_username.to_owned(),
238                        auth_password: options.auth_password.to_owned(),
239
240                        headers: options.headers.unwrap_or_default(),
241                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
242                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
243
244                        // TODO: support these attributes
245                        include_headers: Default::default(),
246                        media_encryption: Default::default(),
247                        destination_country: Default::default(),
248                        created_at: Default::default(),
249                        updated_at: Default::default(),
250                        from_host: Default::default(),
251                    }),
252                },
253                self.base.auth_header(
254                    Default::default(),
255                    Some(SIPGrants { admin: true, ..Default::default() }),
256                )?,
257            )
258            .await
259            .map_err(Into::into)
260    }
261
262    #[deprecated]
263    pub async fn list_sip_trunk(
264        &self,
265        filter: ListSIPTrunkFilter,
266    ) -> ServiceResult<Vec<proto::SipTrunkInfo>> {
267        let resp: proto::ListSipTrunkResponse = self
268            .client
269            .request(
270                SVC,
271                "ListSIPTrunk",
272                proto::ListSipTrunkRequest {
273                    // TODO support these attributes
274                    page: Default::default(),
275                },
276                self.base.auth_header(
277                    Default::default(),
278                    Some(SIPGrants { admin: true, ..Default::default() }),
279                )?,
280            )
281            .await?;
282
283        Ok(resp.items)
284    }
285
286    pub async fn list_sip_inbound_trunk(
287        &self,
288        filter: ListSIPInboundTrunkFilter,
289    ) -> ServiceResult<Vec<proto::SipInboundTrunkInfo>> {
290        let resp: proto::ListSipInboundTrunkResponse = self
291            .client
292            .request(
293                SVC,
294                "ListSIPInboundTrunk",
295                proto::ListSipInboundTrunkRequest {
296                    // TODO: support these attributes
297                    page: Default::default(),
298                    trunk_ids: Default::default(),
299                    numbers: Default::default(),
300                },
301                self.base.auth_header(
302                    Default::default(),
303                    Some(SIPGrants { admin: true, ..Default::default() }),
304                )?,
305            )
306            .await?;
307
308        Ok(resp.items)
309    }
310
311    pub async fn list_sip_outbound_trunk(
312        &self,
313        filter: ListSIPOutboundTrunkFilter,
314    ) -> ServiceResult<Vec<proto::SipOutboundTrunkInfo>> {
315        let resp: proto::ListSipOutboundTrunkResponse = self
316            .client
317            .request(
318                SVC,
319                "ListSIPOutboundTrunk",
320                proto::ListSipOutboundTrunkRequest {
321                    // TODO: support these attributes
322                    page: Default::default(),
323                    trunk_ids: Default::default(),
324                    numbers: Default::default(),
325                },
326                self.base.auth_header(
327                    Default::default(),
328                    Some(SIPGrants { admin: true, ..Default::default() }),
329                )?,
330            )
331            .await?;
332
333        Ok(resp.items)
334    }
335
336    pub async fn delete_sip_trunk(&self, sip_trunk_id: &str) -> ServiceResult<proto::SipTrunkInfo> {
337        self.client
338            .request(
339                SVC,
340                "DeleteSIPTrunk",
341                proto::DeleteSipTrunkRequest { sip_trunk_id: sip_trunk_id.to_owned() },
342                self.base.auth_header(
343                    Default::default(),
344                    Some(SIPGrants { admin: true, ..Default::default() }),
345                )?,
346            )
347            .await
348            .map_err(Into::into)
349    }
350
351    pub async fn create_sip_dispatch_rule(
352        &self,
353        rule: proto::sip_dispatch_rule::Rule,
354        options: CreateSIPDispatchRuleOptions,
355    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
356        self.client
357            .request(
358                SVC,
359                "CreateSIPDispatchRule",
360                proto::CreateSipDispatchRuleRequest {
361                    name: options.name,
362                    metadata: options.metadata,
363                    attributes: options.attributes,
364                    trunk_ids: options.trunk_ids.to_owned(),
365                    inbound_numbers: options.allowed_numbers.to_owned(),
366                    hide_phone_number: options.hide_phone_number,
367                    rule: Some(proto::SipDispatchRule { rule: Some(rule.to_owned()) }),
368
369                    ..Default::default()
370                },
371                self.base.auth_header(
372                    Default::default(),
373                    Some(SIPGrants { admin: true, ..Default::default() }),
374                )?,
375            )
376            .await
377            .map_err(Into::into)
378    }
379
380    pub async fn list_sip_dispatch_rule(
381        &self,
382        filter: ListSIPDispatchRuleFilter,
383    ) -> ServiceResult<Vec<proto::SipDispatchRuleInfo>> {
384        let resp: proto::ListSipDispatchRuleResponse = self
385            .client
386            .request(
387                SVC,
388                "ListSIPDispatchRule",
389                proto::ListSipDispatchRuleRequest {
390                    // TODO: support these attributes
391                    page: Default::default(),
392                    dispatch_rule_ids: Default::default(),
393                    trunk_ids: Default::default(),
394                },
395                self.base.auth_header(
396                    Default::default(),
397                    Some(SIPGrants { admin: true, ..Default::default() }),
398                )?,
399            )
400            .await?;
401
402        Ok(resp.items)
403    }
404
405    pub async fn delete_sip_dispatch_rule(
406        &self,
407        sip_dispatch_rule_id: &str,
408    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
409        self.client
410            .request(
411                SVC,
412                "DeleteSIPDispatchRule",
413                proto::DeleteSipDispatchRuleRequest {
414                    sip_dispatch_rule_id: sip_dispatch_rule_id.to_owned(),
415                },
416                self.base.auth_header(
417                    Default::default(),
418                    Some(SIPGrants { admin: true, ..Default::default() }),
419                )?,
420            )
421            .await
422            .map_err(Into::into)
423    }
424
425    pub async fn create_sip_participant(
426        &self,
427        sip_trunk_id: String,
428        call_to: String,
429        room_name: String,
430        options: CreateSIPParticipantOptions,
431        outbound_trunk_config: Option<proto::SipOutboundConfig>,
432    ) -> ServiceResult<proto::SipParticipantInfo> {
433        self.client
434            .request(
435                SVC,
436                "CreateSIPParticipant",
437                proto::CreateSipParticipantRequest {
438                    sip_trunk_id: sip_trunk_id.to_owned(),
439                    trunk: outbound_trunk_config,
440                    sip_call_to: call_to.to_owned(),
441                    sip_number: options.sip_number.to_owned().unwrap_or_default(),
442                    room_name: room_name.to_owned(),
443                    participant_identity: options.participant_identity.to_owned(),
444                    participant_name: options.participant_name.to_owned().unwrap_or_default(),
445                    participant_metadata: options
446                        .participant_metadata
447                        .to_owned()
448                        .unwrap_or_default(),
449                    participant_attributes: options
450                        .participant_attributes
451                        .to_owned()
452                        .unwrap_or_default(),
453                    dtmf: options.dtmf.to_owned().unwrap_or_default(),
454                    wait_until_answered: options.wait_until_answered.unwrap_or(false),
455                    play_ringtone: options.play_dialtone.unwrap_or(false),
456                    play_dialtone: options.play_dialtone.unwrap_or(false),
457                    hide_phone_number: options.hide_phone_number.unwrap_or(false),
458                    max_call_duration: Self::duration_to_proto(options.max_call_duration),
459                    ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
460
461                    // TODO: rename local proto as well
462                    krisp_enabled: options.enable_krisp.unwrap_or(false),
463
464                    // TODO: support these attributes
465                    headers: Default::default(),
466                    include_headers: Default::default(),
467                    media_encryption: Default::default(),
468                    ..Default::default()
469                },
470                self.base.auth_header(
471                    Default::default(),
472                    Some(SIPGrants { call: true, ..Default::default() }),
473                )?,
474            )
475            .await
476            .map_err(Into::into)
477    }
478}