use super::IceError;
use crate::io::Protocol;
use crate::sdp::parse_candidate;
use serde::ser::SerializeStruct;
use serde::{Deserialize, Serialize, Serializer};
use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::net::{IpAddr, SocketAddr};
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Candidate {
foundation: Option<String>,
component_id: u16,
proto: Protocol,
prio: Option<u32>,
addr: SocketAddr,
base: Option<SocketAddr>,
kind: CandidateKind,
raddr: Option<SocketAddr>,
ufrag: Option<String>,
local_preference: Option<u32>,
discarded: bool,
}
impl fmt::Debug for Candidate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Candidate({}={}/{}", self.kind, self.addr, self.proto)?;
if let Some(base) = self.base {
if base != self.addr {
write!(f, " base={base}")?;
}
}
if let Some(raddr) = self.raddr {
write!(f, " raddr={raddr}")?;
}
write!(f, " prio={}", self.prio())?;
if self.discarded {
write!(f, " discarded")?;
}
write!(f, ")")
}
}
impl Candidate {
#[allow(clippy::too_many_arguments)]
fn new(
foundation: Option<String>,
component_id: u16,
proto: Protocol,
prio: Option<u32>,
addr: SocketAddr,
base: Option<SocketAddr>,
kind: CandidateKind,
raddr: Option<SocketAddr>,
ufrag: Option<String>,
) -> Self {
Candidate {
foundation,
component_id,
proto,
prio,
addr,
base,
kind,
raddr,
ufrag,
local_preference: None,
discarded: false,
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn parsed(
foundation: String,
component_id: u16,
proto: Protocol,
prio: u32,
addr: SocketAddr,
kind: CandidateKind,
raddr: Option<SocketAddr>,
ufrag: Option<String>,
) -> Self {
Candidate::new(
Some(foundation),
component_id,
proto,
Some(prio),
addr,
None,
kind,
raddr,
ufrag,
)
}
pub fn host(addr: SocketAddr, proto: impl TryInto<Protocol>) -> Result<Self, IceError> {
if !is_valid_ip(addr.ip()) {
return Err(IceError::BadCandidate(format!("invalid ip {}", addr.ip())));
}
Ok(Candidate::new(
None,
1, parse_proto(proto)?,
None,
addr,
Some(addr),
CandidateKind::Host,
None,
None,
))
}
pub fn server_reflexive(
addr: SocketAddr,
base: SocketAddr,
proto: impl TryInto<Protocol>,
) -> Result<Self, IceError> {
if !is_valid_ip(addr.ip()) {
return Err(IceError::BadCandidate(format!("invalid ip {}", addr.ip())));
}
Ok(Candidate::new(
None,
1, parse_proto(proto)?,
None,
addr,
Some(base),
CandidateKind::ServerReflexive,
None,
None,
))
}
pub fn relayed(addr: SocketAddr, proto: impl TryInto<Protocol>) -> Result<Self, IceError> {
if !is_valid_ip(addr.ip()) {
return Err(IceError::BadCandidate(format!("invalid ip {}", addr.ip())));
}
Ok(Candidate::new(
None,
1, parse_proto(proto)?,
None,
addr,
Some(addr),
CandidateKind::Relayed,
None,
None,
))
}
pub fn from_sdp_string(s: &str) -> Result<Self, IceError> {
parse_candidate(s).map_err(|e| IceError::BadCandidate(format!("{}: {}", s, e)))
}
pub(crate) fn peer_reflexive(
proto: impl TryInto<Protocol>,
addr: SocketAddr,
base: SocketAddr,
prio: u32,
found: Option<String>,
ufrag: String,
) -> Self {
Candidate::new(
found,
1, parse_proto(proto).expect("internal call to have correct protocol"),
Some(prio),
addr,
Some(base),
CandidateKind::PeerReflexive,
None,
Some(ufrag),
)
}
#[cfg(test)]
pub(crate) fn test_peer_rflx(
addr: SocketAddr,
base: SocketAddr,
proto: impl TryInto<Protocol>,
) -> Self {
Candidate::new(
None,
1, parse_proto(proto).expect("internal test to have correct protocol"),
None,
addr,
Some(base),
CandidateKind::PeerReflexive,
None,
None,
)
}
pub(crate) fn foundation(&self) -> String {
if let Some(v) = &self.foundation {
return v.clone();
}
let mut hasher = DefaultHasher::new();
self.kind.hash(&mut hasher);
self.base().ip().hash(&mut hasher);
if let Some(raddr) = self.raddr {
raddr.ip().hash(&mut hasher);
}
self.proto.hash(&mut hasher);
let hash = hasher.finish();
format!("{:08x}{hash:x}", self.prio().to_be())
}
pub fn prio(&self) -> u32 {
self.do_prio(false)
}
pub(crate) fn prio_prflx(&self) -> u32 {
self.do_prio(true)
}
fn do_prio(&self, as_prflx: bool) -> u32 {
if let Some(prio) = &self.prio {
return *prio;
}
let kind = if as_prflx {
CandidateKind::PeerReflexive
} else {
self.kind
};
let type_preference = match (kind, self.proto) {
(CandidateKind::Host, Protocol::Udp) => 126,
(CandidateKind::PeerReflexive, Protocol::Udp) => 110,
(CandidateKind::ServerReflexive, _) => 100,
(CandidateKind::Host, _) => 90,
(CandidateKind::PeerReflexive, _) => 80,
(CandidateKind::Relayed, Protocol::Udp) => 2,
(CandidateKind::Relayed, Protocol::Tcp) => 1,
(CandidateKind::Relayed, _) => 0,
};
let prio =
type_preference << 24 | self.local_preference() << 8 | (256 - self.component_id as u32);
assert!(prio >= 1 && prio < 2_u32.pow(31));
prio
}
pub(crate) fn local_preference(&self) -> u32 {
self.local_preference
.unwrap_or_else(|| if self.addr.is_ipv6() { 65_535 } else { 65_534 })
}
pub(crate) fn component_id(&self) -> u16 {
self.component_id
}
pub fn addr(&self) -> SocketAddr {
self.addr
}
pub fn proto(&self) -> Protocol {
self.proto
}
pub(crate) fn base(&self) -> SocketAddr {
self.base.unwrap_or(self.addr)
}
pub(crate) fn raddr(&self) -> Option<SocketAddr> {
self.raddr
}
pub fn kind(&self) -> CandidateKind {
self.kind
}
pub(crate) fn set_local_preference(&mut self, v: u32) {
self.local_preference = Some(v);
}
pub(crate) fn set_discarded(&mut self, discarded: bool) {
self.discarded = discarded;
}
pub(crate) fn discarded(&self) -> bool {
self.discarded
}
pub(crate) fn set_ufrag(&mut self, ufrag: &str) {
self.ufrag = Some(ufrag.into());
}
pub(crate) fn ufrag(&self) -> Option<&str> {
self.ufrag.as_deref()
}
pub(crate) fn clear_ufrag(&mut self) {
self.ufrag = None;
}
pub fn to_sdp_string(&self) -> String {
let mut s = format!(
"candidate:{} {} {} {} {} {} typ {}",
self.foundation(),
self.component_id,
self.proto,
self.prio(),
self.addr.ip(),
self.addr.port(),
self.kind
);
if let Some(raddr) = &self.raddr {
s.push_str(&format!(" raddr {} rport {}", raddr.ip(), raddr.port()))
}
if let Some(ufrag) = &self.ufrag {
s.push_str(&format!(" ufrag {}", ufrag));
}
s
}
}
impl fmt::Display for Candidate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_sdp_string())
}
}
fn parse_proto(proto: impl TryInto<Protocol>) -> Result<Protocol, IceError> {
proto
.try_into()
.map_err(|_| IceError::BadCandidate("invalid protocol".into()))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CandidateKind {
Host,
PeerReflexive,
ServerReflexive,
Relayed,
}
impl fmt::Display for CandidateKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let x = match self {
CandidateKind::Host => "host",
CandidateKind::PeerReflexive => "prflx",
CandidateKind::ServerReflexive => "srflx",
CandidateKind::Relayed => "relay",
};
write!(f, "{x}")
}
}
fn is_valid_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(v) => {
!v.is_link_local() && !v.is_broadcast() && !v.is_multicast() && !v.is_unspecified()
}
IpAddr::V6(v) => !v.is_multicast() && !v.is_unspecified(),
}
}
impl Serialize for Candidate {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut o = serializer.serialize_struct("CandidateInfo", 4)?;
o.serialize_field("candidate", &self.to_sdp_string())?;
o.serialize_field("sdpMid", &None::<()>)?;
o.serialize_field("sdpMLineIndex", &0)?;
o.serialize_field("usernameFragment", &self.ufrag())?;
o.end()
}
}
impl<'de> Deserialize<'de> for Candidate {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CandidateInfo {
candidate: String,
username_fragment: Option<String>,
}
let CandidateInfo {
candidate,
username_fragment,
} = CandidateInfo::deserialize(deserializer)?;
let mut candidate =
Candidate::from_sdp_string(&candidate).map_err(serde::de::Error::custom)?;
if let Some(ufrag) = username_fragment {
candidate.set_ufrag(&ufrag);
}
Ok(candidate)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_serialize_deserialize() {
let socket_addr = "1.2.3.4:9876".parse().unwrap();
let c1 = Candidate::host(socket_addr, Protocol::Udp).unwrap();
let json = serde_json::to_string(&c1).unwrap();
let c2: Candidate = serde_json::from_str(&json).unwrap();
assert_eq!(c1.to_string(), c2.to_string());
}
#[test]
fn serialize() {
let socket_addr = "1.2.3.4:9876".parse().unwrap();
let mut candidate = Candidate::host(socket_addr, Protocol::Udp).unwrap();
assert_eq!(
no_hash(serde_json::to_string(&candidate).unwrap()),
r#"{"candidate":"candidate:--- 1 udp 2130706175 1.2.3.4 9876 typ host","sdpMid":null,"sdpMLineIndex":0,"usernameFragment":null}"#
);
candidate.ufrag = Some("ufrag".to_string());
assert_eq!(
no_hash(serde_json::to_string(&candidate).unwrap()),
r#"{"candidate":"candidate:--- 1 udp 2130706175 1.2.3.4 9876 typ host ufrag ufrag","sdpMid":null,"sdpMLineIndex":0,"usernameFragment":"ufrag"}"#
);
}
fn no_hash(mut s: String) -> String {
let f = s.find("candidate:").unwrap();
let t = s.find(" 1 ").unwrap();
s.replace_range((f + 10)..t, "---");
s
}
#[test]
fn deserialize() {
let json = r#"{"candidate":"candidate:12044049749558888150 1 udp 2130706175 1.2.3.4 9876 typ host ufrag ufrag","sdpMid":"ignored","sdpMLineIndex":123,"usernameFragment":"ufrag"}"#;
let candidate: Candidate = serde_json::from_str(json).unwrap();
assert_eq!(candidate.ufrag(), Some("ufrag"));
assert_eq!(candidate.addr().to_string(), "1.2.3.4:9876");
assert_eq!(candidate.base().to_string(), "1.2.3.4:9876");
assert_eq!(candidate.kind(), CandidateKind::Host);
assert_eq!(candidate.proto(), Protocol::Udp);
assert_eq!(candidate.prio(), 2130706175);
assert_eq!(candidate.component_id(), 1);
assert_eq!(candidate.raddr(), None);
assert!(!candidate.discarded());
}
#[test]
fn to_string() {
let socket_addr = "1.2.3.4:9876".parse().unwrap();
let mut candidate = Candidate::host(socket_addr, Protocol::Udp).unwrap();
assert_eq!(
no_hash(candidate.to_string()),
"candidate:--- 1 udp 2130706175 1.2.3.4 9876 typ host"
);
candidate.ufrag = Some("ufrag".into());
assert_eq!(
no_hash(candidate.to_string()),
"candidate:--- 1 udp 2130706175 1.2.3.4 9876 typ host ufrag ufrag"
);
candidate.raddr = Some("5.5.5.5:5555".parse().unwrap());
assert_eq!(
no_hash(candidate.to_string()),
"candidate:--- 1 udp 2130706175 1.2.3.4 9876 typ host raddr 5.5.5.5 rport 5555 ufrag ufrag");
let candidate = Candidate::relayed(socket_addr, Protocol::SslTcp).unwrap();
assert_eq!(
no_hash(candidate.to_string()),
"candidate:--- 1 ssltcp 16776959 1.2.3.4 9876 typ relay"
);
}
#[test]
fn new_from_sdp_string() {
let candidate = Candidate::from_sdp_string(
"candidate:fffeff7e5e895846293d220a 1 udp 2130706175 1.2.3.4 9876 typ host ufrag myuserfrag",
)
.unwrap();
assert_eq!(candidate.ufrag(), Some("myuserfrag"));
assert_eq!(candidate.addr().to_string(), "1.2.3.4:9876");
}
#[test]
fn bad_candidate() {
let s = "candidate:12344 bad value";
assert!(Candidate::from_sdp_string(s).is_err());
}
#[test]
fn lexical_ordering_of_sdp_is_follows_priority() {
let mut candidates = Vec::from([
host("1.1.1.1:0"),
host("2.2.2.2:0"),
srflx("3.3.3.3:0", "4.4.4.4:0"),
srflx("5.5.5.5:0", "6.6.6.6:0"),
relay("8.8.8.8:0"),
relay("7.7.7.7:0"),
]);
candidates.sort();
assert!(candidates[0].contains("relay"));
assert!(candidates[1].contains("relay"));
assert!(candidates[2].contains("srflx"));
assert!(candidates[3].contains("srflx"));
assert!(candidates[4].contains("host"));
assert!(candidates[5].contains("host"));
}
fn host(socket: &str) -> String {
Candidate::host(socket.parse().unwrap(), "udp")
.unwrap()
.to_sdp_string()
}
fn srflx(addr: &str, base: &str) -> String {
Candidate::server_reflexive(addr.parse().unwrap(), base.parse().unwrap(), "udp")
.unwrap()
.to_sdp_string()
}
fn relay(addr: &str) -> String {
Candidate::relayed(addr.parse().unwrap(), "udp")
.unwrap()
.to_sdp_string()
}
}