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