pub mod protos {
tonic::include_proto!("turn.server");
}
use std::{net::SocketAddr, ops::Deref};
use md5::{Digest, Md5};
use sha2::Sha256;
use tonic::{
Request, Response, Status,
transport::{Channel, Server},
};
use self::protos::{
GetTurnPasswordRequest, GetTurnPasswordResponse, Identifier, PasswordAlgorithm,
TurnAllocatedEvent, TurnChannelBindEvent, TurnCreatePermissionEvent, TurnDestroyEvent,
TurnRefreshEvent, TurnServerInfo, TurnSession, TurnSessionStatistics,
turn_hooks_service_server::{TurnHooksService, TurnHooksServiceServer},
turn_service_client::TurnServiceClient,
};
pub struct TurnService(TurnServiceClient<Channel>);
impl TurnService {
pub fn new(channel: Channel) -> Self {
Self(TurnServiceClient::new(channel))
}
pub async fn get_info(&mut self) -> Result<TurnServerInfo, Status> {
Ok(self.0.get_info(Request::new(())).await?.into_inner())
}
pub async fn get_session(&mut self, id: Identifier) -> Result<TurnSession, Status> {
Ok(self.0.get_session(Request::new(id)).await?.into_inner())
}
pub async fn get_session_statistics(
&mut self,
id: Identifier,
) -> Result<TurnSessionStatistics, Status> {
Ok(self
.0
.get_session_statistics(Request::new(id))
.await?
.into_inner())
}
pub async fn destroy_session(&mut self, id: Identifier) -> Result<(), Status> {
Ok(self.0.destroy_session(Request::new(id)).await?.into_inner())
}
}
pub struct Credential {
pub password: String,
pub realm: String,
}
struct TurnHooksServerInner<T>(T);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Password {
Md5([u8; 16]),
Sha256([u8; 32]),
}
impl Deref for Password {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match self {
Password::Md5(it) => it,
Password::Sha256(it) => it,
}
}
}
pub fn generate_password(
username: &str,
password: &str,
realm: &str,
algorithm: PasswordAlgorithm,
) -> Password {
match algorithm {
PasswordAlgorithm::Md5 => {
let mut hasher = Md5::new();
hasher.update([username, realm, password].join(":"));
Password::Md5(hasher.finalize().into())
}
PasswordAlgorithm::Sha256 => {
let mut hasher = Sha256::new();
hasher.update([username, realm, password].join(":").as_bytes());
let mut result = [0u8; 32];
result.copy_from_slice(&hasher.finalize());
Password::Sha256(result)
}
PasswordAlgorithm::Unspecified => {
panic!("Invalid password algorithm");
}
}
}
#[tonic::async_trait]
impl<T: TurnHooksServer + 'static> TurnHooksService for TurnHooksServerInner<T> {
async fn get_password(
&self,
request: Request<GetTurnPasswordRequest>,
) -> Result<Response<GetTurnPasswordResponse>, Status> {
let request = request.into_inner();
let algorithm = request.algorithm();
if let Ok(credential) = self
.0
.get_password(
request
.id
.ok_or_else(|| Status::invalid_argument("identifier is None"))?,
&request.realm,
&request.username,
algorithm,
)
.await
{
Ok(Response::new(GetTurnPasswordResponse {
password: generate_password(
&request.username,
&credential.password,
&credential.realm,
algorithm,
)
.to_vec(),
}))
} else {
Err(Status::not_found("Message integrity not found"))
}
}
async fn on_allocated_event(
&self,
request: Request<TurnAllocatedEvent>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
self.0
.on_allocated(
request
.id
.ok_or_else(|| Status::invalid_argument("identifier is None"))?,
request.username,
request.port as u16,
)
.await;
Ok(Response::new(()))
}
async fn on_channel_bind_event(
&self,
request: Request<TurnChannelBindEvent>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
self.0
.on_channel_bind(
request
.id
.ok_or_else(|| Status::invalid_argument("identifier is None"))?,
request.username,
request.channel as u16,
)
.await;
Ok(Response::new(()))
}
async fn on_create_permission_event(
&self,
request: Request<TurnCreatePermissionEvent>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
self.0
.on_create_permission(
request
.id
.ok_or_else(|| Status::invalid_argument("identifier is None"))?,
request.username,
request.ports.iter().map(|p| *p as u16).collect(),
)
.await;
Ok(Response::new(()))
}
async fn on_refresh_event(
&self,
request: Request<TurnRefreshEvent>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
self.0
.on_refresh(
request
.id
.ok_or_else(|| Status::invalid_argument("identifier is None"))?,
request.username,
request.lifetime as u32,
)
.await;
Ok(Response::new(()))
}
async fn on_destroy_event(
&self,
request: Request<TurnDestroyEvent>,
) -> Result<Response<()>, Status> {
let request = request.into_inner();
self.0
.on_destroy(
request
.id
.ok_or_else(|| Status::invalid_argument("identifier is None"))?,
request.username,
)
.await;
Ok(Response::new(()))
}
}
#[tonic::async_trait]
pub trait TurnHooksServer: Send + Sync {
#[allow(unused_variables)]
async fn get_password(
&self,
id: Identifier,
realm: &str,
username: &str,
algorithm: PasswordAlgorithm,
) -> Result<Credential, Status> {
Err(Status::unimplemented("get_password is not implemented"))
}
#[allow(unused_variables)]
async fn on_allocated(&self, id: Identifier, username: String, port: u16) {}
#[allow(unused_variables)]
async fn on_channel_bind(&self, id: Identifier, username: String, channel: u16) {}
#[allow(unused_variables)]
async fn on_create_permission(&self, id: Identifier, username: String, ports: Vec<u16>) {}
#[allow(unused_variables)]
async fn on_refresh(&self, id: Identifier, username: String, lifetime: u32) {}
#[allow(unused_variables)]
async fn on_destroy(&self, id: Identifier, username: String) {}
async fn start_with_server(
self,
server: &mut Server,
listen: SocketAddr,
) -> Result<(), tonic::transport::Error>
where
Self: Sized + 'static,
{
server
.add_service(TurnHooksServiceServer::<TurnHooksServerInner<Self>>::new(
TurnHooksServerInner(self),
))
.serve(listen)
.await?;
Ok(())
}
}