use std::ffi::{CStr, CString};
pub trait CandidateApi: sealed::CandidateAsC {
fn component_id(&self) -> usize {
unsafe { (*self.as_c()).component_id }
}
fn candidate_type(&self) -> CandidateType {
unsafe { (*self.as_c()).candidate_type.into() }
}
fn transport(&self) -> TransportType {
unsafe { (*self.as_c()).transport_type.into() }
}
fn foundation(&self) -> String {
unsafe {
CStr::from_ptr((*self.as_c()).foundation)
.to_str()
.unwrap()
.to_owned()
}
}
fn priority(&self) -> u32 {
unsafe { (*self.as_c()).priority }
}
fn address(&self) -> crate::Address {
unsafe { crate::Address::from_c_none((*self.as_c()).address) }
}
fn base_address(&self) -> crate::Address {
unsafe { crate::Address::from_c_none((*self.as_c()).base_address) }
}
fn related_address(&self) -> Option<crate::Address> {
unsafe {
let related = (*self.as_c()).related_address;
if related.is_null() {
None
} else {
Some(crate::Address::from_c_none(related))
}
}
}
fn tcp_type(&self) -> TcpType {
unsafe { (*self.as_c()).tcp_type.into() }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(i32)]
pub enum ParseCandidateError {
NotCandidate = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_NOT_CANDIDATE,
BadFoundation = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_FOUNDATION,
BadComponentId = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_COMPONENT_ID,
BadTransportType = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_TRANSPORT_TYPE,
BadPriority = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_PRIORITY,
BadAddress = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_ADDRESS,
BadCandidateType = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_CANDIDATE_TYPE,
BadExtension = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION,
Malformed = crate::ffi::RICE_PARSE_CANDIDATE_ERROR_MALFORMED,
}
impl ParseCandidateError {
pub(crate) fn from_c(
value: crate::ffi::RiceParseCandidateError,
) -> Result<(), ParseCandidateError> {
match value {
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_SUCCESS => Ok(()),
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_NOT_CANDIDATE => {
Err(ParseCandidateError::NotCandidate)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_FOUNDATION => {
Err(ParseCandidateError::BadFoundation)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_COMPONENT_ID => {
Err(ParseCandidateError::BadComponentId)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_TRANSPORT_TYPE => {
Err(ParseCandidateError::BadTransportType)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_PRIORITY => {
Err(ParseCandidateError::BadPriority)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_ADDRESS => {
Err(ParseCandidateError::BadAddress)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_CANDIDATE_TYPE => {
Err(ParseCandidateError::BadCandidateType)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION => {
Err(ParseCandidateError::BadExtension)
}
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_MALFORMED => Err(ParseCandidateError::Malformed),
val => panic!("Unknown RiceParseCandidateError value {val:x?}"),
}
}
}
mod sealed {
pub trait CandidateAsC {
fn as_c(&self) -> *const crate::ffi::RiceCandidate;
}
}
#[derive(Clone)]
pub struct Candidate {
ffi: crate::ffi::RiceCandidate,
}
unsafe impl Send for Candidate {}
unsafe impl Sync for Candidate {}
impl core::fmt::Debug for Candidate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_struct("Candidate");
dbg.field("component_id", &self.component_id());
dbg.field("candidate_type", &self.candidate_type());
dbg.field("transport_type", &self.transport());
dbg.field("foundation", &self.foundation());
dbg.field("priority", &self.priority());
dbg.field("address", &self.address());
dbg.field("base_address", &self.base_address());
dbg.field("related_address", &self.related_address());
dbg.field("tcp_type", &self.tcp_type());
dbg.finish()
}
}
impl PartialEq<Candidate> for Candidate {
fn eq(&self, other: &Candidate) -> bool {
unsafe { crate::ffi::rice_candidate_eq(&self.ffi, &other.ffi) }
}
}
impl PartialEq<CandidateOwned> for Candidate {
fn eq(&self, other: &CandidateOwned) -> bool {
unsafe { crate::ffi::rice_candidate_eq(&self.ffi, other.ffi) }
}
}
impl Eq for Candidate {}
impl CandidateApi for Candidate {}
impl sealed::CandidateAsC for Candidate {
fn as_c(&self) -> *const crate::ffi::RiceCandidate {
&self.ffi
}
}
impl Drop for Candidate {
fn drop(&mut self) {
unsafe { crate::ffi::rice_candidate_clear(&mut self.ffi) }
}
}
impl Candidate {
pub fn builder(
component_id: usize,
ctype: CandidateType,
ttype: TransportType,
foundation: &str,
address: crate::Address,
) -> CandidateBuilder {
unsafe {
let foundation = CString::new(foundation).unwrap();
let mut ret = CandidateBuilder {
ffi: crate::ffi::RiceCandidate::zeroed(),
};
let address = address.into_c_full();
let res = crate::ffi::rice_candidate_init(
&mut ret.ffi,
component_id,
ctype.into(),
ttype.into(),
foundation.as_ptr(),
address,
);
if res != crate::ffi::RICE_ERROR_SUCCESS {
let _address = crate::Address::from_c_full(address);
panic!("Failed to crate ICE candidate!");
}
ret
}
}
pub(crate) fn as_c(&self) -> *const crate::ffi::RiceCandidate {
&self.ffi
}
pub(crate) unsafe fn from_c_none(candidate: *const crate::ffi::RiceCandidate) -> Self {
unsafe {
let mut ret = Self {
ffi: crate::ffi::RiceCandidate::zeroed(),
};
crate::ffi::rice_candidate_copy_into(candidate, &mut ret.ffi);
ret
}
}
pub(crate) fn from_c_full(candidate: crate::ffi::RiceCandidate) -> Self {
Self { ffi: candidate }
}
pub fn to_owned(&self) -> CandidateOwned {
unsafe {
CandidateOwned {
ffi: crate::ffi::rice_candidate_copy(&self.ffi),
}
}
}
pub fn to_sdp_string(&self) -> String {
unsafe {
let res = crate::ffi::rice_candidate_to_sdp_string(&self.ffi);
let s = CStr::from_ptr(res);
let ret = s.to_str().unwrap().to_owned();
crate::ffi::rice_string_free(res);
ret
}
}
pub fn from_sdp_string(s: &str) -> Result<Candidate, ParseCandidateError> {
let cand_str = std::ffi::CString::new(s).unwrap();
unsafe {
let mut ret = Candidate {
ffi: crate::ffi::RiceCandidate::zeroed(),
};
let res =
crate::ffi::rice_candidate_init_from_sdp_string(&mut ret.ffi, cand_str.as_ptr());
ParseCandidateError::from_c(res)?;
Ok(ret)
}
}
}
#[derive(Eq)]
pub struct CandidateOwned {
ffi: *mut crate::ffi::RiceCandidate,
}
unsafe impl Send for CandidateOwned {}
unsafe impl Sync for CandidateOwned {}
impl core::fmt::Debug for CandidateOwned {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dbg = f.debug_struct("CandidateOwned");
dbg.field("component_id", &self.component_id());
dbg.field("candidate_type", &self.candidate_type());
dbg.field("transport_type", &self.transport());
dbg.field("foundation", &self.foundation());
dbg.field("priority", &self.priority());
dbg.field("address", &self.address());
dbg.field("base_address", &self.base_address());
dbg.field("related_address", &self.related_address());
dbg.field("tcp_type", &self.tcp_type());
dbg.finish()
}
}
impl PartialEq<CandidateOwned> for CandidateOwned {
fn eq(&self, other: &CandidateOwned) -> bool {
unsafe { crate::ffi::rice_candidate_eq(self.ffi, other.ffi) }
}
}
impl PartialEq<Candidate> for CandidateOwned {
fn eq(&self, other: &Candidate) -> bool {
unsafe { crate::ffi::rice_candidate_eq(self.ffi, &other.ffi) }
}
}
impl Clone for CandidateOwned {
fn clone(&self) -> Self {
unsafe {
Self {
ffi: crate::ffi::rice_candidate_copy(self.ffi),
}
}
}
}
impl Drop for CandidateOwned {
fn drop(&mut self) {
unsafe { crate::ffi::rice_candidate_free(self.ffi) }
}
}
impl CandidateApi for CandidateOwned {}
impl sealed::CandidateAsC for CandidateOwned {
fn as_c(&self) -> *const crate::ffi::RiceCandidate {
self.ffi
}
}
impl CandidateOwned {
pub(crate) fn as_c(&self) -> *const crate::ffi::RiceCandidate {
self.ffi
}
pub fn to_sdp_string(&self) -> String {
unsafe {
let res = crate::ffi::rice_candidate_to_sdp_string(self.ffi);
let s = CStr::from_ptr(res);
let ret = s.to_str().unwrap().to_owned();
crate::ffi::rice_string_free(res);
ret
}
}
}
#[derive(Debug)]
pub struct CandidateBuilder {
ffi: crate::ffi::RiceCandidate,
}
impl CandidateBuilder {
pub fn build(self) -> Candidate {
Candidate { ffi: self.ffi }
}
pub fn priority(mut self, priority: u32) -> Self {
unsafe {
crate::ffi::rice_candidate_set_priority(&mut self.ffi, priority);
self
}
}
pub fn base_address(mut self, base: crate::Address) -> Self {
unsafe {
crate::ffi::rice_candidate_set_base_address(&mut self.ffi, base.into_c_full());
self
}
}
pub fn related_address(mut self, related: crate::Address) -> Self {
unsafe {
crate::ffi::rice_candidate_set_related_address(&mut self.ffi, related.into_c_full());
self
}
}
pub fn tcp_type(mut self, typ: TcpType) -> Self {
unsafe {
if self.ffi.transport_type != TransportType::Tcp.into() && typ != TcpType::None {
panic!("Attempt made to set the TcpType of a non-TCP candidate");
}
crate::ffi::rice_candidate_set_tcp_type(&mut self.ffi, typ.into());
self
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum CandidateType {
Host = crate::ffi::RICE_CANDIDATE_TYPE_HOST,
PeerReflexive = crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
ServerReflexive = crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
Relayed = crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
}
impl From<crate::ffi::RiceCandidateType> for CandidateType {
fn from(value: crate::ffi::RiceCandidateType) -> Self {
match value {
crate::ffi::RICE_CANDIDATE_TYPE_HOST => Self::Host,
crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE => Self::PeerReflexive,
crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE => Self::ServerReflexive,
crate::ffi::RICE_CANDIDATE_TYPE_RELAYED => Self::Relayed,
val => panic!("Unknown candidate type {val:x?}"),
}
}
}
impl From<CandidateType> for crate::ffi::RiceCandidateType {
fn from(value: CandidateType) -> Self {
match value {
CandidateType::Host => crate::ffi::RICE_CANDIDATE_TYPE_HOST,
CandidateType::PeerReflexive => crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
CandidateType::ServerReflexive => crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
CandidateType::Relayed => crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum TcpType {
None = crate::ffi::RICE_TCP_TYPE_NONE,
Active = crate::ffi::RICE_TCP_TYPE_ACTIVE,
Passive = crate::ffi::RICE_TCP_TYPE_PASSIVE,
So = crate::ffi::RICE_TCP_TYPE_SO,
}
impl From<crate::ffi::RiceTcpType> for TcpType {
fn from(value: crate::ffi::RiceTcpType) -> Self {
match value {
crate::ffi::RICE_TCP_TYPE_NONE => Self::None,
crate::ffi::RICE_TCP_TYPE_ACTIVE => Self::Active,
crate::ffi::RICE_TCP_TYPE_PASSIVE => Self::Passive,
crate::ffi::RICE_TCP_TYPE_SO => Self::So,
val => panic!("Unknown tcp type value {val:x?}"),
}
}
}
impl From<TcpType> for crate::ffi::RiceTcpType {
fn from(value: TcpType) -> Self {
match value {
TcpType::None => crate::ffi::RICE_TCP_TYPE_NONE,
TcpType::Active => crate::ffi::RICE_TCP_TYPE_ACTIVE,
TcpType::Passive => crate::ffi::RICE_TCP_TYPE_PASSIVE,
TcpType::So => crate::ffi::RICE_TCP_TYPE_SO,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum TransportType {
Udp,
Tcp,
}
impl core::fmt::Display for TransportType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl From<crate::ffi::RiceTransportType> for TransportType {
fn from(value: crate::ffi::RiceTransportType) -> Self {
match value {
crate::ffi::RICE_TRANSPORT_TYPE_UDP => Self::Udp,
crate::ffi::RICE_TRANSPORT_TYPE_TCP => Self::Tcp,
_ => panic!("Unknown transport type value"),
}
}
}
impl From<TransportType> for crate::ffi::RiceTransportType {
fn from(value: TransportType) -> Self {
match value {
TransportType::Udp => crate::ffi::RICE_TRANSPORT_TYPE_UDP,
TransportType::Tcp => crate::ffi::RICE_TRANSPORT_TYPE_TCP,
}
}
}
impl core::str::FromStr for TransportType {
type Err = ParseTransportTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("udp") {
Ok(Self::Udp)
} else if s.eq_ignore_ascii_case("tcp") {
Ok(Self::Tcp)
} else {
Err(ParseTransportTypeError::UnknownTransport)
}
}
}
#[derive(Copy, Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum ParseTransportTypeError {
#[error("Unknown transport value was provided")]
UnknownTransport,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidatePair {
pub local: CandidateOwned,
pub remote: CandidateOwned,
}
impl CandidatePair {
pub fn new(local: CandidateOwned, remote: CandidateOwned) -> Self {
Self { local, remote }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn candidate_type() {
let _log = crate::tests::test_init_log();
for (c, r) in [
(crate::ffi::RICE_CANDIDATE_TYPE_HOST, CandidateType::Host),
(
crate::ffi::RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE,
CandidateType::ServerReflexive,
),
(
crate::ffi::RICE_CANDIDATE_TYPE_PEER_REFLEXIVE,
CandidateType::PeerReflexive,
),
(
crate::ffi::RICE_CANDIDATE_TYPE_RELAYED,
CandidateType::Relayed,
),
] {
assert_eq!(CandidateType::from(c), r);
assert_eq!(crate::ffi::RiceCandidateType::from(r), c);
}
}
#[test]
#[should_panic = "Unknown candidate type"]
fn candidate_type_out_of_range() {
let _log = crate::tests::test_init_log();
let _ = CandidateType::from(u32::MAX);
}
#[test]
fn tcp_type() {
let _log = crate::tests::test_init_log();
for (c, r) in [
(crate::ffi::RICE_TCP_TYPE_NONE, TcpType::None),
(crate::ffi::RICE_TCP_TYPE_ACTIVE, TcpType::Active),
(crate::ffi::RICE_TCP_TYPE_PASSIVE, TcpType::Passive),
(crate::ffi::RICE_TCP_TYPE_SO, TcpType::So),
] {
assert_eq!(TcpType::from(c), r);
assert_eq!(crate::ffi::RiceTcpType::from(r), c);
}
}
#[test]
#[should_panic = "Unknown tcp type value"]
fn tcp_type_out_of_range() {
let _log = crate::tests::test_init_log();
let _ = TcpType::from(u32::MAX);
}
#[test]
fn transport_type() {
let _log = crate::tests::test_init_log();
for (c, r) in [
(crate::ffi::RICE_TRANSPORT_TYPE_UDP, TransportType::Udp),
(crate::ffi::RICE_TRANSPORT_TYPE_TCP, TransportType::Tcp),
] {
assert_eq!(TransportType::from(c), r);
assert_eq!(crate::ffi::RiceTransportType::from(r), c);
}
}
#[test]
#[should_panic = "Unknown transport type value"]
fn transport_type_out_of_range() {
let _log = crate::tests::test_init_log();
let _ = TransportType::from(u32::MAX);
}
#[test]
fn parse_candidate_error() {
let _log = crate::tests::test_init_log();
for (c, r) in [
(crate::ffi::RICE_PARSE_CANDIDATE_ERROR_SUCCESS, Ok(())),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_NOT_CANDIDATE,
Err(ParseCandidateError::NotCandidate),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_FOUNDATION,
Err(ParseCandidateError::BadFoundation),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_COMPONENT_ID,
Err(ParseCandidateError::BadComponentId),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_TRANSPORT_TYPE,
Err(ParseCandidateError::BadTransportType),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_PRIORITY,
Err(ParseCandidateError::BadPriority),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_ADDRESS,
Err(ParseCandidateError::BadAddress),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_CANDIDATE_TYPE,
Err(ParseCandidateError::BadCandidateType),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION,
Err(ParseCandidateError::BadExtension),
),
(
crate::ffi::RICE_PARSE_CANDIDATE_ERROR_MALFORMED,
Err(ParseCandidateError::Malformed),
),
] {
assert_eq!(ParseCandidateError::from_c(c), r);
}
}
#[test]
#[should_panic = "Unknown RiceParseCandidateError value"]
fn parse_candidate_error_out_of_range() {
let _log = crate::tests::test_init_log();
let _ = ParseCandidateError::from_c(i32::MAX);
}
#[test]
fn transport_type_from_str() {
use core::str::FromStr;
let _log = crate::tests::test_init_log();
for (s, r) in [
("udp", Ok(TransportType::Udp)),
("UDP", Ok(TransportType::Udp)),
("tcp", Ok(TransportType::Tcp)),
("TCP", Ok(TransportType::Tcp)),
("random", Err(ParseTransportTypeError::UnknownTransport)),
] {
assert_eq!(TransportType::from_str(s), r);
}
}
fn base_address() -> crate::Address {
"127.0.0.1:1000".parse().unwrap()
}
fn address() -> crate::Address {
"127.0.0.2:2000".parse().unwrap()
}
fn related_address() -> crate::Address {
"127.0.0.3:3000".parse().unwrap()
}
#[test]
fn candidate_build() {
let _log = crate::tests::test_init_log();
let base = base_address();
let addr = address();
let related = related_address();
let cand = Candidate::builder(
1,
CandidateType::PeerReflexive,
TransportType::Tcp,
"foundation",
addr.clone(),
)
.base_address(base.clone())
.related_address(related.clone())
.tcp_type(TcpType::Active)
.priority(1234)
.build();
assert_eq!(cand.component_id(), 1);
assert_eq!(cand.candidate_type(), CandidateType::PeerReflexive);
assert_eq!(cand.transport(), TransportType::Tcp);
assert_eq!(cand.foundation(), "foundation");
assert_eq!(cand.address(), addr);
assert_eq!(cand.base_address(), base);
assert_eq!(cand.related_address(), Some(related));
assert_eq!(cand.tcp_type(), TcpType::Active);
assert_eq!(cand.priority(), 1234);
let cand_clone = cand.clone();
assert_eq!(cand, cand_clone);
}
#[test]
fn candidate_to_owned() {
let _log = crate::tests::test_init_log();
let base = base_address();
let addr = address();
let related = related_address();
let cand = Candidate::builder(
1,
CandidateType::PeerReflexive,
TransportType::Tcp,
"foundation",
addr.clone(),
)
.base_address(base.clone())
.related_address(related.clone())
.tcp_type(TcpType::Active)
.priority(1234)
.build();
let owned = cand.to_owned();
assert_eq!(cand, owned);
assert_eq!(owned, cand);
}
#[test]
fn candidate_string() {
let _log = crate::tests::test_init_log();
let addr = address();
let related = related_address();
let cand = Candidate::builder(
1,
CandidateType::PeerReflexive,
TransportType::Tcp,
"foundation",
addr.clone(),
)
.related_address(related.clone())
.tcp_type(TcpType::Active)
.priority(1234)
.build();
let s = cand.to_sdp_string();
println!("{s}");
let parsed = Candidate::from_sdp_string(&s).unwrap();
assert_eq!(parsed, cand);
let owned = cand.to_owned();
let s = cand.to_sdp_string();
println!("{s}");
let parsed = Candidate::from_sdp_string(&s).unwrap();
assert_eq!(parsed, owned);
}
}