Skip to main content

tuwunel_api/client/
voip.rs

1use std::time::{Duration, SystemTime};
2
3use axum::extract::State;
4use base64::{Engine as _, engine::general_purpose};
5use hmac::{Hmac, Mac};
6use ruma::{SecondsSinceUnixEpoch, UserId, api::client::voip::get_turn_server_info};
7use sha1::Sha1;
8use tuwunel_core::{Err, Result, utils};
9
10use crate::Ruma;
11
12const RANDOM_USER_ID_LENGTH: usize = 10;
13
14type HmacSha1 = Hmac<Sha1>;
15
16/// # `GET /_matrix/client/r0/voip/turnServer`
17///
18/// TODO: Returns information about the recommended turn server.
19pub(crate) async fn turn_server_route(
20	State(services): State<crate::State>,
21	body: Ruma<get_turn_server_info::v3::Request>,
22) -> Result<get_turn_server_info::v3::Response> {
23	// MSC4166: return M_NOT_FOUND 404 if no TURN URIs are specified in any way
24	if services.server.config.turn_uris.is_empty() {
25		return Err!(Request(NotFound("Not Found")));
26	}
27
28	let turn_secret = &services.globals.turn_secret;
29
30	let (username, password) = if let Some(turn_secret) = turn_secret {
31		let expiry = SecondsSinceUnixEpoch::from_system_time(
32			SystemTime::now()
33				.checked_add(Duration::from_secs(services.config.turn_ttl))
34				.expect("TURN TTL should not get this high"),
35		)
36		.expect("time is valid");
37
38		let random_user_id = || {
39			UserId::parse_with_server_name(
40				utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
41				&services.server.name,
42			)
43		};
44
45		let user = body.sender_user.map_or_else(random_user_id, Ok)?;
46		let username: String = format!("{}:{}", expiry.get(), user);
47		let mut mac = HmacSha1::new_from_slice(turn_secret.as_bytes())
48			.expect("HMAC can take key of any size");
49
50		mac.update(username.as_bytes());
51		let password: String = general_purpose::STANDARD.encode(mac.finalize().into_bytes());
52
53		(username, password)
54	} else {
55		(services.config.turn_username.clone(), services.config.turn_password.clone())
56	};
57
58	Ok(get_turn_server_info::v3::Response {
59		username,
60		password,
61		uris: services.config.turn_uris.clone(),
62		ttl: Duration::from_secs(services.config.turn_ttl),
63	})
64}