livekit_api/services/
sip.rs

1// Copyright 2024 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    /// Optionally play dialtone in the room as an audible indicator for existing participants
144    pub play_dialtone: Option<bool>,
145    pub hide_phone_number: Option<bool>,
146    pub ringing_timeout: Option<Duration>,
147    pub max_call_duration: Option<Duration>,
148    pub enable_krisp: Option<bool>,
149}
150
151impl SIPClient {
152    pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
153        Self {
154            base: ServiceBase::with_api_key(api_key, api_secret),
155            client: TwirpClient::new(host, LIVEKIT_PACKAGE, None),
156        }
157    }
158
159    pub fn new(host: &str) -> ServiceResult<Self> {
160        let (api_key, api_secret) = get_env_keys()?;
161        Ok(Self::with_api_key(host, &api_key, &api_secret))
162    }
163
164    fn duration_to_proto(d: Option<Duration>) -> Option<ProtoDuration> {
165        d.map(|d| ProtoDuration { seconds: d.as_secs() as i64, nanos: d.subsec_nanos() as i32 })
166    }
167
168    pub async fn create_sip_inbound_trunk(
169        &self,
170        name: String,
171        numbers: Vec<String>,
172        options: CreateSIPInboundTrunkOptions,
173    ) -> ServiceResult<proto::SipInboundTrunkInfo> {
174        self.client
175            .request(
176                SVC,
177                "CreateSIPInboundTrunk",
178                proto::CreateSipInboundTrunkRequest {
179                    trunk: Some(proto::SipInboundTrunkInfo {
180                        sip_trunk_id: Default::default(),
181                        name,
182                        numbers,
183                        metadata: options.metadata.unwrap_or_default(),
184                        allowed_numbers: options.allowed_numbers.unwrap_or_default(),
185                        allowed_addresses: options.allowed_addresses.unwrap_or_default(),
186                        auth_username: options.auth_username.unwrap_or_default(),
187                        auth_password: options.auth_password.unwrap_or_default(),
188                        headers: options.headers.unwrap_or_default(),
189                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
190                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
191                        krisp_enabled: options.krisp_enabled.unwrap_or(false),
192                        max_call_duration: Self::duration_to_proto(options.max_call_duration),
193                        ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
194
195                        // TODO: support these attributes
196                        include_headers: Default::default(),
197                        media_encryption: Default::default(),
198                    }),
199                },
200                self.base.auth_header(
201                    Default::default(),
202                    Some(SIPGrants { admin: true, ..Default::default() }),
203                )?,
204            )
205            .await
206            .map_err(Into::into)
207    }
208
209    pub async fn create_sip_outbound_trunk(
210        &self,
211        name: String,
212        address: String,
213        numbers: Vec<String>,
214        options: CreateSIPOutboundTrunkOptions,
215    ) -> ServiceResult<proto::SipOutboundTrunkInfo> {
216        self.client
217            .request(
218                SVC,
219                "CreateSIPOutboundTrunk",
220                proto::CreateSipOutboundTrunkRequest {
221                    trunk: Some(proto::SipOutboundTrunkInfo {
222                        sip_trunk_id: Default::default(),
223                        name,
224                        address,
225                        numbers,
226                        transport: options.transport as i32,
227                        metadata: options.metadata,
228
229                        auth_username: options.auth_username.to_owned(),
230                        auth_password: options.auth_password.to_owned(),
231
232                        headers: options.headers.unwrap_or_default(),
233                        headers_to_attributes: options.headers_to_attributes.unwrap_or_default(),
234                        attributes_to_headers: options.attributes_to_headers.unwrap_or_default(),
235
236                        // TODO: support these attributes
237                        include_headers: Default::default(),
238                        media_encryption: Default::default(),
239                        destination_country: Default::default(),
240                    }),
241                },
242                self.base.auth_header(
243                    Default::default(),
244                    Some(SIPGrants { admin: true, ..Default::default() }),
245                )?,
246            )
247            .await
248            .map_err(Into::into)
249    }
250
251    #[deprecated]
252    pub async fn list_sip_trunk(
253        &self,
254        filter: ListSIPTrunkFilter,
255    ) -> ServiceResult<Vec<proto::SipTrunkInfo>> {
256        let resp: proto::ListSipTrunkResponse = self
257            .client
258            .request(
259                SVC,
260                "ListSIPTrunk",
261                proto::ListSipTrunkRequest {
262                    // TODO support these attributes
263                    page: Default::default(),
264                },
265                self.base.auth_header(
266                    Default::default(),
267                    Some(SIPGrants { admin: true, ..Default::default() }),
268                )?,
269            )
270            .await?;
271
272        Ok(resp.items)
273    }
274
275    pub async fn list_sip_inbound_trunk(
276        &self,
277        filter: ListSIPInboundTrunkFilter,
278    ) -> ServiceResult<Vec<proto::SipInboundTrunkInfo>> {
279        let resp: proto::ListSipInboundTrunkResponse = self
280            .client
281            .request(
282                SVC,
283                "ListSIPInboundTrunk",
284                proto::ListSipInboundTrunkRequest {
285                    // TODO: support these attributes
286                    page: Default::default(),
287                    trunk_ids: Default::default(),
288                    numbers: Default::default(),
289                },
290                self.base.auth_header(
291                    Default::default(),
292                    Some(SIPGrants { admin: true, ..Default::default() }),
293                )?,
294            )
295            .await?;
296
297        Ok(resp.items)
298    }
299
300    pub async fn list_sip_outbound_trunk(
301        &self,
302        filter: ListSIPOutboundTrunkFilter,
303    ) -> ServiceResult<Vec<proto::SipOutboundTrunkInfo>> {
304        let resp: proto::ListSipOutboundTrunkResponse = self
305            .client
306            .request(
307                SVC,
308                "ListSIPOutboundTrunk",
309                proto::ListSipOutboundTrunkRequest {
310                    // TODO: support these attributes
311                    page: Default::default(),
312                    trunk_ids: Default::default(),
313                    numbers: Default::default(),
314                },
315                self.base.auth_header(
316                    Default::default(),
317                    Some(SIPGrants { admin: true, ..Default::default() }),
318                )?,
319            )
320            .await?;
321
322        Ok(resp.items)
323    }
324
325    pub async fn delete_sip_trunk(&self, sip_trunk_id: &str) -> ServiceResult<proto::SipTrunkInfo> {
326        self.client
327            .request(
328                SVC,
329                "DeleteSIPTrunk",
330                proto::DeleteSipTrunkRequest { sip_trunk_id: sip_trunk_id.to_owned() },
331                self.base.auth_header(
332                    Default::default(),
333                    Some(SIPGrants { admin: true, ..Default::default() }),
334                )?,
335            )
336            .await
337            .map_err(Into::into)
338    }
339
340    pub async fn create_sip_dispatch_rule(
341        &self,
342        rule: proto::sip_dispatch_rule::Rule,
343        options: CreateSIPDispatchRuleOptions,
344    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
345        self.client
346            .request(
347                SVC,
348                "CreateSIPDispatchRule",
349                proto::CreateSipDispatchRuleRequest {
350                    name: options.name,
351                    metadata: options.metadata,
352                    attributes: options.attributes,
353                    trunk_ids: options.trunk_ids.to_owned(),
354                    inbound_numbers: options.allowed_numbers.to_owned(),
355                    hide_phone_number: options.hide_phone_number,
356                    rule: Some(proto::SipDispatchRule { rule: Some(rule.to_owned()) }),
357
358                    ..Default::default()
359                },
360                self.base.auth_header(
361                    Default::default(),
362                    Some(SIPGrants { admin: true, ..Default::default() }),
363                )?,
364            )
365            .await
366            .map_err(Into::into)
367    }
368
369    pub async fn list_sip_dispatch_rule(
370        &self,
371        filter: ListSIPDispatchRuleFilter,
372    ) -> ServiceResult<Vec<proto::SipDispatchRuleInfo>> {
373        let resp: proto::ListSipDispatchRuleResponse = self
374            .client
375            .request(
376                SVC,
377                "ListSIPDispatchRule",
378                proto::ListSipDispatchRuleRequest {
379                    // TODO: support these attributes
380                    page: Default::default(),
381                    dispatch_rule_ids: Default::default(),
382                    trunk_ids: Default::default(),
383                },
384                self.base.auth_header(
385                    Default::default(),
386                    Some(SIPGrants { admin: true, ..Default::default() }),
387                )?,
388            )
389            .await?;
390
391        Ok(resp.items)
392    }
393
394    pub async fn delete_sip_dispatch_rule(
395        &self,
396        sip_dispatch_rule_id: &str,
397    ) -> ServiceResult<proto::SipDispatchRuleInfo> {
398        self.client
399            .request(
400                SVC,
401                "DeleteSIPDispatchRule",
402                proto::DeleteSipDispatchRuleRequest {
403                    sip_dispatch_rule_id: sip_dispatch_rule_id.to_owned(),
404                },
405                self.base.auth_header(
406                    Default::default(),
407                    Some(SIPGrants { admin: true, ..Default::default() }),
408                )?,
409            )
410            .await
411            .map_err(Into::into)
412    }
413
414    pub async fn create_sip_participant(
415        &self,
416        sip_trunk_id: String,
417        call_to: String,
418        room_name: String,
419        options: CreateSIPParticipantOptions,
420    ) -> ServiceResult<proto::SipParticipantInfo> {
421        self.client
422            .request(
423                SVC,
424                "CreateSIPParticipant",
425                proto::CreateSipParticipantRequest {
426                    sip_trunk_id: sip_trunk_id.to_owned(),
427                    sip_call_to: call_to.to_owned(),
428                    sip_number: options.sip_number.to_owned().unwrap_or_default(),
429                    room_name: room_name.to_owned(),
430                    participant_identity: options.participant_identity.to_owned(),
431                    participant_name: options.participant_name.to_owned().unwrap_or_default(),
432                    participant_metadata: options
433                        .participant_metadata
434                        .to_owned()
435                        .unwrap_or_default(),
436                    participant_attributes: options
437                        .participant_attributes
438                        .to_owned()
439                        .unwrap_or_default(),
440                    dtmf: options.dtmf.to_owned().unwrap_or_default(),
441                    play_ringtone: options.play_dialtone.unwrap_or(false),
442                    play_dialtone: options.play_dialtone.unwrap_or(false),
443                    hide_phone_number: options.hide_phone_number.unwrap_or(false),
444                    max_call_duration: Self::duration_to_proto(options.max_call_duration),
445                    ringing_timeout: Self::duration_to_proto(options.ringing_timeout),
446
447                    // TODO: rename local proto as well
448                    krisp_enabled: options.enable_krisp.unwrap_or(false),
449
450                    // TODO: support these attributes
451                    headers: Default::default(),
452                    include_headers: Default::default(),
453                    media_encryption: Default::default(),
454                    ..Default::default()
455                },
456                self.base.auth_header(
457                    Default::default(),
458                    Some(SIPGrants { call: true, ..Default::default() }),
459                )?,
460            )
461            .await
462            .map_err(Into::into)
463    }
464}