use crate::description::{RTCSessionDescription, UNSPECIFIED_STR};
use crate::server::certificate::RTCDtlsFingerprint;
use crate::types::{EndpointId, SessionId, UserName};
use base64::{prelude::BASE64_STANDARD, Engine};
use ring::rand::{SecureRandom, SystemRandom};
use sdp::util::ConnectionRole;
use sdp::SessionDescription;
use serde::{Deserialize, Serialize};
use shared::error::{Error, Result};
use std::fmt;
use std::time::Instant;
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DTLSRole {
#[default]
Unspecified = 0,
#[serde(rename = "auto")]
Auto = 1,
#[serde(rename = "client")]
Client = 2,
#[serde(rename = "server")]
Server = 3,
}
pub(crate) const DEFAULT_DTLS_ROLE_ANSWER: DTLSRole = DTLSRole::Client;
pub(crate) const DEFAULT_DTLS_ROLE_OFFER: DTLSRole = DTLSRole::Auto;
impl fmt::Display for DTLSRole {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DTLSRole::Auto => write!(f, "auto"),
DTLSRole::Client => write!(f, "client"),
DTLSRole::Server => write!(f, "server"),
_ => write!(f, "{}", UNSPECIFIED_STR),
}
}
}
impl From<&SessionDescription> for DTLSRole {
fn from(session_description: &SessionDescription) -> Self {
for media_section in &session_description.media_descriptions {
for attribute in &media_section.attributes {
if attribute.key == "setup" {
if let Some(value) = &attribute.value {
match value.as_str() {
"active" => return DTLSRole::Client,
"passive" => return DTLSRole::Server,
_ => return DTLSRole::Auto,
};
} else {
return DTLSRole::Auto;
}
}
}
}
DTLSRole::Auto
}
}
impl DTLSRole {
pub(crate) fn to_connection_role(self) -> ConnectionRole {
match self {
DTLSRole::Client => ConnectionRole::Active,
DTLSRole::Server => ConnectionRole::Passive,
DTLSRole::Auto => ConnectionRole::Actpass,
_ => ConnectionRole::Unspecified,
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct RTCIceParameters {
pub(crate) username_fragment: String,
pub(crate) password: String,
}
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub(crate) struct DTLSParameters {
pub(crate) role: DTLSRole,
pub(crate) fingerprints: Vec<RTCDtlsFingerprint>,
}
#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub(crate) struct ConnectionCredentials {
pub(crate) ice_params: RTCIceParameters,
pub(crate) dtls_params: DTLSParameters,
}
impl ConnectionCredentials {
pub(crate) fn new(fingerprints: Vec<RTCDtlsFingerprint>, remote_role: DTLSRole) -> Self {
let rng = SystemRandom::new();
let mut user = [0u8; 9];
let _ = rng.fill(&mut user);
let mut password = [0u8; 18];
let _ = rng.fill(&mut password);
Self {
ice_params: RTCIceParameters {
username_fragment: BASE64_STANDARD.encode(&user[..]),
password: BASE64_STANDARD.encode(&password[..]),
},
dtls_params: DTLSParameters {
fingerprints,
role: if remote_role == DTLSRole::Server {
DTLSRole::Client
} else {
DTLSRole::Server
},
},
}
}
pub(crate) fn from_sdp(sdp: &SessionDescription) -> Result<Self> {
let username_fragment = sdp
.media_descriptions
.iter()
.find_map(|m| m.attribute("ice-ufrag"))
.ok_or(Error::ErrAttributeNotFound)?
.ok_or(Error::ErrAttributeNotFound)?
.to_string();
let password = sdp
.media_descriptions
.iter()
.find_map(|m| m.attribute("ice-pwd"))
.ok_or(Error::ErrAttributeNotFound)?
.ok_or(Error::ErrAttributeNotFound)?
.to_string();
let fingerprint = if let Some(fingerprint) = sdp.attribute("fingerprint") {
fingerprint.try_into()?
} else {
sdp.media_descriptions
.iter()
.find_map(|m| m.attribute("fingerprint"))
.ok_or(Error::ErrAttributeNotFound)?
.ok_or(Error::ErrAttributeNotFound)?
.try_into()?
};
let role = DTLSRole::from(sdp);
Ok(Self {
ice_params: RTCIceParameters {
username_fragment,
password,
},
dtls_params: DTLSParameters {
role,
fingerprints: vec![fingerprint],
},
})
}
pub(crate) fn valid(&self) -> bool {
self.ice_params.username_fragment.len() >= 4
&& self.ice_params.username_fragment.len() <= 256
&& self.ice_params.password.len() >= 22
&& self.ice_params.password.len() <= 256
}
}
#[derive(Debug)]
pub(crate) struct Candidate {
session_id: SessionId,
endpoint_id: EndpointId,
remote_conn_cred: ConnectionCredentials,
local_conn_cred: ConnectionCredentials,
remote_description: RTCSessionDescription,
local_description: RTCSessionDescription,
expired_time: Instant,
}
impl Candidate {
pub(crate) fn new(
session_id: SessionId,
endpoint_id: EndpointId,
remote_conn_cred: ConnectionCredentials,
local_conn_cred: ConnectionCredentials,
remote_description: RTCSessionDescription,
local_description: RTCSessionDescription,
expired_time: Instant,
) -> Self {
Self {
session_id,
endpoint_id,
local_conn_cred,
remote_conn_cred,
remote_description,
local_description,
expired_time,
}
}
pub(crate) fn remote_connection_credentials(&self) -> &ConnectionCredentials {
&self.remote_conn_cred
}
pub(crate) fn local_connection_credentials(&self) -> &ConnectionCredentials {
&self.local_conn_cred
}
pub(crate) fn get_remote_parameters(&self) -> &RTCIceParameters {
&self.remote_conn_cred.ice_params
}
pub(crate) fn get_local_parameters(&self) -> &RTCIceParameters {
&self.local_conn_cred.ice_params
}
pub(crate) fn session_id(&self) -> SessionId {
self.session_id
}
pub(crate) fn endpoint_id(&self) -> EndpointId {
self.endpoint_id
}
pub(crate) fn username(&self) -> UserName {
format!(
"{}:{}",
self.local_conn_cred.ice_params.username_fragment,
self.remote_conn_cred.ice_params.username_fragment
)
}
pub(crate) fn remote_description(&self) -> &RTCSessionDescription {
&self.remote_description
}
pub(crate) fn local_description(&self) -> &RTCSessionDescription {
&self.local_description
}
pub(crate) fn expired_time(&self) -> Instant {
self.expired_time
}
}