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    /// Authentication realm advertised on inbound SIP invites.
82    pub auth_realm: Option<String>,
83}
84
85#[derive(Default, Clone, Debug)]
86pub struct CreateSIPOutboundTrunkOptions {
87    pub transport: proto::SipTransport,
88    /// Optional free-form metadata.
89    pub metadata: String,
90    /// Username and password used to authenticate outbound SIP invites
91    /// May be empty to have no Authentication
92    pub auth_username: String,
93    pub auth_password: String,
94
95    pub headers: Option<HashMap<String, String>>,
96    pub headers_to_attributes: Option<HashMap<String, String>>,
97    pub attributes_to_headers: Option<HashMap<String, String>>,
98}
99
100#[deprecated]
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum ListSIPTrunkFilter {
103    All,
104}
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum ListSIPInboundTrunkFilter {
107    All,
108}
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub enum ListSIPOutboundTrunkFilter {
111    All,
112}
113
114#[derive(Default, Clone, Debug)]
115pub struct CreateSIPDispatchRuleOptions {
116    pub name: String,
117    pub metadata: String,
118    pub attributes: HashMap<String, String>,
119    /// What trunks are accepted for this dispatch rule
120    /// If empty all trunks will match this dispatch rule
121    pub trunk_ids: Vec<String>,
122    pub allowed_numbers: Vec<String>,
123    pub hide_phone_number: bool,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub enum ListSIPDispatchRuleFilter {
128    All,
129}
130
131#[derive(Default, Clone, Debug)]
132pub struct CreateSIPParticipantOptions {
133    /// Optional identity of the participant in LiveKit room
134    pub participant_identity: String,
135    /// Optionally set the name of the participant in a LiveKit room
136    pub participant_name: Option<String>,
137    /// Optionally set the free-form metadata of the participant in a LiveKit room
138    pub participant_metadata: Option<String>,
139    pub participant_attributes: Option<HashMap<String, String>>,
140    // What number should be dialed via SIP
141    pub sip_number: Option<String>,
142    /// Optionally send following DTMF digits (extension codes) when making a call.
143    /// Character 'w' can be used to add a 0.5 sec delay.
144    pub dtmf: Option<String>,
145    /// Wait for the call to be answered before returning.
146    ///
147    /// When `true`, the request blocks until the call is answered or fails,
148    /// and returns SIP error codes (e.g., 486 Busy, 603 Decline) on failure.
149    /// When `false` (default), returns immediately while the call is still dialing.
150    pub wait_until_answered: Option<bool>,
151    /// Optionally play dialtone in the room as an audible indicator for existing participants
152    pub play_dialtone: Option<bool>,
153    pub hide_phone_number: Option<bool>,
154    pub ringing_timeout: Option<Duration>,
155    pub max_call_duration: Option<Duration>,
156    pub enable_krisp: Option<bool>,
157}
158
159impl SIPClient {
160    pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
161        Self {
162            base: ServiceBase::with_api_key(api_key, api_secret),
163            client: TwirpClient::new(host, LIVEKIT_PACKAGE, None),
164        }
165    }
166
167    pub fn new(host: &str) -> ServiceResult<Self> {
168        let (api_key, api_secret) = get_env_keys()?;
169        Ok(Self::with_api_key(host, &api_key, &api_secret))
170    }
171
172    fn duration_to_proto(d: Option<Duration>) -> Option<ProtoDuration> {
173        d.map(|d| ProtoDuration { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32 })
174    }
175
176    pub async fn create_sip_inbound_trunk(
177        &self,
178        name: String,
179        numbers: Vec<String>,
180        options: CreateSIPInboundTrunkOptions,
181    ) -> ServiceResult<proto::SipInboundTrunkInfo> {
182        self.client
183            .request(
184                SVC,
185                "CreateSIPInboundTrunk",
186                proto::CreateSipInboundTrunkRequest {
187                    trunk: Some(proto::SipInboundTrunkInfo {
188                        sip_trunk_id: Default::default(),
189                        name,
190                        numbers,
191                        metadata: options.metadata.unwrap_or_default(),
192                        allowed_numbers: options.allowed_numbers.unwrap_or_default(),
193                        allowed_addresses: options.allowed_addresses.unwrap_or_default(),
194                        auth_username: options.auth_username.unwrap_or_default(),
195                        auth_password: options.auth_password.unwrap_or_default(),
196                        auth_realm: options.auth_realm.unwrap_or_default(),
197                        headers: options.headers.unwrap_or_default(),
198                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
199                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
200                        krisp_enabled: options.krisp_enabled.unwrap_or(false),
201                        max_call_duration: Self::duration_to_proto(options.max_call_duration),
202                        ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
203
204                        // TODO: support these attributes
205                        include_headers: Default::default(),
206                        media_encryption: Default::default(),
207                        created_at: Default::default(),
208                        updated_at: Default::default(),
209                        media: Default::default(),
210                    }),
211                },
212                self.base.auth_header(
213                    Default::default(),
214                    Some(SIPGrants { admin: true, ..Default::default() }),
215                )?,
216            )
217            .await
218            .map_err(Into::into)
219    }
220
221    pub async fn create_sip_outbound_trunk(
222        &self,
223        name: String,
224        address: String,
225        numbers: Vec<String>,
226        options: CreateSIPOutboundTrunkOptions,
227    ) -> ServiceResult<proto::SipOutboundTrunkInfo> {
228        self.client
229            .request(
230                SVC,
231                "CreateSIPOutboundTrunk",
232                proto::CreateSipOutboundTrunkRequest {
233                    trunk: Some(proto::SipOutboundTrunkInfo {
234                        sip_trunk_id: Default::default(),
235                        name,
236                        address,
237                        numbers,
238                        transport: options.transport as i32,
239                        metadata: options.metadata,
240
241                        auth_username: options.auth_username.to_owned(),
242                        auth_password: options.auth_password.to_owned(),
243
244                        headers: options.headers.unwrap_or_default(),
245                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
246                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
247
248                        // TODO: support these attributes
249                        include_headers: Default::default(),
250                        media_encryption: Default::default(),
251                        destination_country: Default::default(),
252                        created_at: Default::default(),
253                        updated_at: Default::default(),
254                        from_host: Default::default(),
255                        media: Default::default(),
256                    }),
257                },
258                self.base.auth_header(
259                    Default::default(),
260                    Some(SIPGrants { admin: true, ..Default::default() }),
261                )?,
262            )
263            .await
264            .map_err(Into::into)
265    }
266
267    #[deprecated]
268    pub async fn list_sip_trunk(
269        &self,
270        filter: ListSIPTrunkFilter,
271    ) -> ServiceResult<Vec<proto::SipTrunkInfo>> {
272        let resp: proto::ListSipTrunkResponse = self
273            .client
274            .request(
275                SVC,
276                "ListSIPTrunk",
277                proto::ListSipTrunkRequest {
278                    // TODO support these attributes
279                    page: Default::default(),
280                },
281                self.base.auth_header(
282                    Default::default(),
283                    Some(SIPGrants { admin: true, ..Default::default() }),
284                )?,
285            )
286            .await?;
287
288        Ok(resp.items)
289    }
290
291    pub async fn list_sip_inbound_trunk(
292        &self,
293        filter: ListSIPInboundTrunkFilter,
294    ) -> ServiceResult<Vec<proto::SipInboundTrunkInfo>> {
295        let resp: proto::ListSipInboundTrunkResponse = self
296            .client
297            .request(
298                SVC,
299                "ListSIPInboundTrunk",
300                proto::ListSipInboundTrunkRequest {
301                    // TODO: support these attributes
302                    page: Default::default(),
303                    trunk_ids: Default::default(),
304                    numbers: Default::default(),
305                },
306                self.base.auth_header(
307                    Default::default(),
308                    Some(SIPGrants { admin: true, ..Default::default() }),
309                )?,
310            )
311            .await?;
312
313        Ok(resp.items)
314    }
315
316    pub async fn list_sip_outbound_trunk(
317        &self,
318        filter: ListSIPOutboundTrunkFilter,
319    ) -> ServiceResult<Vec<proto::SipOutboundTrunkInfo>> {
320        let resp: proto::ListSipOutboundTrunkResponse = self
321            .client
322            .request(
323                SVC,
324                "ListSIPOutboundTrunk",
325                proto::ListSipOutboundTrunkRequest {
326                    // TODO: support these attributes
327                    page: Default::default(),
328                    trunk_ids: Default::default(),
329                    numbers: Default::default(),
330                },
331                self.base.auth_header(
332                    Default::default(),
333                    Some(SIPGrants { admin: true, ..Default::default() }),
334                )?,
335            )
336            .await?;
337
338        Ok(resp.items)
339    }
340
341    pub async fn delete_sip_trunk(&self, sip_trunk_id: &str) -> ServiceResult<proto::SipTrunkInfo> {
342        self.client
343            .request(
344                SVC,
345                "DeleteSIPTrunk",
346                proto::DeleteSipTrunkRequest { sip_trunk_id: sip_trunk_id.to_owned() },
347                self.base.auth_header(
348                    Default::default(),
349                    Some(SIPGrants { admin: true, ..Default::default() }),
350                )?,
351            )
352            .await
353            .map_err(Into::into)
354    }
355
356    pub async fn create_sip_dispatch_rule(
357        &self,
358        rule: proto::sip_dispatch_rule::Rule,
359        options: CreateSIPDispatchRuleOptions,
360    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
361        self.client
362            .request(
363                SVC,
364                "CreateSIPDispatchRule",
365                proto::CreateSipDispatchRuleRequest {
366                    name: options.name,
367                    metadata: options.metadata,
368                    attributes: options.attributes,
369                    trunk_ids: options.trunk_ids.to_owned(),
370                    inbound_numbers: options.allowed_numbers.to_owned(),
371                    hide_phone_number: options.hide_phone_number,
372                    rule: Some(proto::SipDispatchRule { rule: Some(rule.to_owned()) }),
373
374                    ..Default::default()
375                },
376                self.base.auth_header(
377                    Default::default(),
378                    Some(SIPGrants { admin: true, ..Default::default() }),
379                )?,
380            )
381            .await
382            .map_err(Into::into)
383    }
384
385    pub async fn list_sip_dispatch_rule(
386        &self,
387        filter: ListSIPDispatchRuleFilter,
388    ) -> ServiceResult<Vec<proto::SipDispatchRuleInfo>> {
389        let resp: proto::ListSipDispatchRuleResponse = self
390            .client
391            .request(
392                SVC,
393                "ListSIPDispatchRule",
394                proto::ListSipDispatchRuleRequest {
395                    // TODO: support these attributes
396                    page: Default::default(),
397                    dispatch_rule_ids: Default::default(),
398                    trunk_ids: Default::default(),
399                },
400                self.base.auth_header(
401                    Default::default(),
402                    Some(SIPGrants { admin: true, ..Default::default() }),
403                )?,
404            )
405            .await?;
406
407        Ok(resp.items)
408    }
409
410    pub async fn delete_sip_dispatch_rule(
411        &self,
412        sip_dispatch_rule_id: &str,
413    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
414        self.client
415            .request(
416                SVC,
417                "DeleteSIPDispatchRule",
418                proto::DeleteSipDispatchRuleRequest {
419                    sip_dispatch_rule_id: sip_dispatch_rule_id.to_owned(),
420                },
421                self.base.auth_header(
422                    Default::default(),
423                    Some(SIPGrants { admin: true, ..Default::default() }),
424                )?,
425            )
426            .await
427            .map_err(Into::into)
428    }
429
430    pub async fn create_sip_participant(
431        &self,
432        sip_trunk_id: String,
433        call_to: String,
434        room_name: String,
435        options: CreateSIPParticipantOptions,
436        outbound_trunk_config: Option<proto::SipOutboundConfig>,
437    ) -> ServiceResult<proto::SipParticipantInfo> {
438        self.client
439            .request(
440                SVC,
441                "CreateSIPParticipant",
442                proto::CreateSipParticipantRequest {
443                    sip_trunk_id: sip_trunk_id.to_owned(),
444                    trunk: outbound_trunk_config,
445                    sip_call_to: call_to.to_owned(),
446                    sip_number: options.sip_number.to_owned().unwrap_or_default(),
447                    room_name: room_name.to_owned(),
448                    participant_identity: options.participant_identity.to_owned(),
449                    participant_name: options.participant_name.to_owned().unwrap_or_default(),
450                    participant_metadata: options
451                        .participant_metadata
452                        .to_owned()
453                        .unwrap_or_default(),
454                    participant_attributes: options
455                        .participant_attributes
456                        .to_owned()
457                        .unwrap_or_default(),
458                    dtmf: options.dtmf.to_owned().unwrap_or_default(),
459                    wait_until_answered: options.wait_until_answered.unwrap_or(false),
460                    play_ringtone: options.play_dialtone.unwrap_or(false),
461                    play_dialtone: options.play_dialtone.unwrap_or(false),
462                    hide_phone_number: options.hide_phone_number.unwrap_or(false),
463                    max_call_duration: Self::duration_to_proto(options.max_call_duration),
464                    ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
465
466                    // TODO: rename local proto as well
467                    krisp_enabled: options.enable_krisp.unwrap_or(false),
468
469                    // TODO: support these attributes
470                    headers: Default::default(),
471                    include_headers: Default::default(),
472                    media_encryption: Default::default(),
473                    ..Default::default()
474                },
475                self.base.auth_header(
476                    Default::default(),
477                    Some(SIPGrants { call: true, ..Default::default() }),
478                )?,
479            )
480            .await
481            .map_err(Into::into)
482    }
483}