Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions librice/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,22 @@ impl Stream {
self.state.proto_stream.remote_credentials()
}

/// Restart this stream with fresh random local ICE credentials.
pub fn restart(&self) -> Result<Credentials, AgentError> {
self.state
.proto_stream
.restart(Instant::from_std(self.state.base_instant))?;

if let Some(agent) = self.state.weak_agent_inner.upgrade() {
let mut agent = agent.lock().unwrap();
if let Some(waker) = agent.waker.take() {
waker.wake();
}
}

Ok(self.state.proto_stream.local_credentials().unwrap())
}

/// Add a remote candidate for connection checks for use with this stream
///
/// # Examples
Expand Down Expand Up @@ -1085,4 +1101,23 @@ mod tests {
assert_eq!(remote_cands.len(), 1);
assert_eq!(remote_cands[0], candidate);
}

#[test]
fn restart_resets_remote_credentials() {
#[cfg(feature = "runtime-tokio")]
let _runtime = crate::tests::tokio_runtime().enter();
init();
let agent = Agent::default();
let stream = agent.add_stream();
let local = Credentials::new("luser", "lpass");
let remote = Credentials::new("ruser", "rpass");

stream.set_local_credentials(&local);
stream.set_remote_credentials(&remote);
let restarted = stream.restart().unwrap();

assert_ne!(restarted, local);
assert_eq!(stream.local_credentials(), Some(restarted));
assert!(stream.remote_credentials().is_none());
}
}
28 changes: 28 additions & 0 deletions rice-c/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ impl Agent {
unsafe { crate::ffi::rice_agent_close(self.ffi, now.as_nanos()) }
}

/// Restart every stream in this agent with fresh per-stream local ICE credentials.
pub fn restart(&self, now: Instant) {
unsafe { crate::ffi::rice_agent_restart(self.ffi, now.as_nanos()) }
}

/// The controlling state of this ICE agent. This value may change throughout the ICE
/// negotiation process.
pub fn controlling(&self) -> bool {
Expand Down Expand Up @@ -779,4 +784,27 @@ mod tests {
.build();
let _ = agent;
}

#[test]
fn restart_all_streams() {
let _log = crate::tests::test_init_log();
let agent = Agent::builder().controlling(true).build();
let stream1 = agent.add_stream();
let stream2 = agent.add_stream();
let local1 = crate::stream::Credentials::new("luser1", "lpass1");
let local2 = crate::stream::Credentials::new("luser2", "lpass2");
let remote = crate::stream::Credentials::new("ruser", "rpass");

stream1.set_local_credentials(&local1);
stream1.set_remote_credentials(&remote);
stream2.set_local_credentials(&local2);
stream2.set_remote_credentials(&remote);

agent.restart(Instant::ZERO);

assert_ne!(stream1.local_credentials().unwrap(), local1);
assert!(stream1.remote_credentials().is_none());
assert_ne!(stream2.local_credentials().unwrap(), local2);
assert!(stream2.remote_credentials().is_none());
}
}
112 changes: 70 additions & 42 deletions rice-c/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,29 @@
pub const RICE_PROTO_MAJOR: u32 = 0;
pub const RICE_PROTO_MINOR: u32 = 4;
pub const RICE_PROTO_PATCH: u32 = 3;
#[doc = " The UDP transport"]
pub const RICE_TRANSPORT_TYPE_UDP: RiceTransportType = 0;
#[doc = " The TCP transport"]
pub const RICE_TRANSPORT_TYPE_TCP: RiceTransportType = 1;
pub type RiceTransportType = u32;
#[doc = " The candidate is a local network interface"]
pub const RICE_CANDIDATE_TYPE_HOST: RiceCandidateType = 0;
#[doc = " The candidate was discovered from incoming data"]
pub const RICE_CANDIDATE_TYPE_PEER_REFLEXIVE: RiceCandidateType = 1;
#[doc = " The candidate was discovered by asking an external server (STUN/TURN)"]
pub const RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: RiceCandidateType = 2;
#[doc = " The candidate will relay all data through an external server (TURN)."]
pub const RICE_CANDIDATE_TYPE_RELAYED: RiceCandidateType = 3;
pub type RiceCandidateType = u32;
#[doc = " Not a TCP candidate."]
pub const RICE_TCP_TYPE_NONE: RiceTcpType = 0;
#[doc = " The candidate address will connect to a remote address."]
pub const RICE_TCP_TYPE_ACTIVE: RiceTcpType = 1;
#[doc = " The candidate will listen for incominng TCP connections."]
pub const RICE_TCP_TYPE_PASSIVE: RiceTcpType = 2;
#[doc = " Simultaneous open. The candidate will both listen for incoming connections, and connect to\n remote addresses."]
pub const RICE_TCP_TYPE_SO: RiceTcpType = 3;
pub type RiceTcpType = u32;
#[doc = " Component is in initial state and no connectivity checks are in progress."]
pub const RICE_COMPONENT_CONNECTION_STATE_NEW: RiceComponentConnectionState = 0;
#[doc = " Connectivity checks are in progress for this candidate"]
Expand All @@ -17,15 +40,25 @@ pub const RICE_ADDRESS_FAMILY_IPV4: RiceAddressFamily = 1;
#[doc = " IP version 6."]
pub const RICE_ADDRESS_FAMILY_IPV6: RiceAddressFamily = 2;
pub type RiceAddressFamily = u32;
#[doc = " The candidate is a local network interface"]
pub const RICE_CANDIDATE_TYPE_HOST: RiceCandidateType = 0;
#[doc = " The candidate was discovered from incoming data"]
pub const RICE_CANDIDATE_TYPE_PEER_REFLEXIVE: RiceCandidateType = 1;
#[doc = " The candidate was discovered by asking an external server (STUN/TURN)"]
pub const RICE_CANDIDATE_TYPE_SERVER_REFLEXIVE: RiceCandidateType = 2;
#[doc = " The candidate will relay all data through an external server (TURN)."]
pub const RICE_CANDIDATE_TYPE_RELAYED: RiceCandidateType = 3;
pub type RiceCandidateType = u32;
#[doc = " The SHA-1 HMAC."]
pub const RICE_INTEGRITY_ALGORITHM_SHA1: RiceIntegrityAlgorithm = 0;
#[doc = " The SHA-256 HMAC."]
pub const RICE_INTEGRITY_ALGORITHM_SHA256: RiceIntegrityAlgorithm = 1;
pub type RiceIntegrityAlgorithm = u32;
#[doc = " The configuration will automatically be used when supported."]
pub const RICE_FEATURE_DISABLED: RiceFeature = -1;
#[doc = " The configuration will automatically be used when supported."]
pub const RICE_FEATURE_AUTO: RiceFeature = 0;
#[doc = " The configuration is enabled and required."]
pub const RICE_FEATURE_REQUIRED: RiceFeature = 1;
pub type RiceFeature = i32;
#[doc = " Openssl."]
pub const RICE_TLS_VARIANT_OPENSSL: RiceTlsVariant = 1;
#[doc = " Rustls."]
pub const RICE_TLS_VARIANT_RUSTLS: RiceTlsVariant = 2;
#[doc = " Dimpl."]
pub const RICE_TLS_VARIANT_DIMPL: RiceTlsVariant = 3;
pub type RiceTlsVariant = u32;
#[doc = " Not an error. The operation was completed successfully."]
pub const RICE_ERROR_SUCCESS: RiceError = 0;
#[doc = " The operation failed for an unspecified reason."]
Expand All @@ -35,18 +68,6 @@ pub const RICE_ERROR_RESOURCE_NOT_FOUND: RiceError = -2;
#[doc = " The operation is already in progress."]
pub const RICE_ERROR_ALREADY_IN_PROGRESS: RiceError = -3;
pub type RiceError = i32;
#[doc = " The configuration will automatically be used when supported."]
pub const RICE_FEATURE_DISABLED: RiceFeature = -1;
#[doc = " The configuration will automatically be used when supported."]
pub const RICE_FEATURE_AUTO: RiceFeature = 0;
#[doc = " The configuration is enabled and required."]
pub const RICE_FEATURE_REQUIRED: RiceFeature = 1;
pub type RiceFeature = i32;
#[doc = " The SHA-1 HMAC."]
pub const RICE_INTEGRITY_ALGORITHM_SHA1: RiceIntegrityAlgorithm = 0;
#[doc = " The SHA-256 HMAC."]
pub const RICE_INTEGRITY_ALGORITHM_SHA256: RiceIntegrityAlgorithm = 1;
pub type RiceIntegrityAlgorithm = u32;
#[doc = " No error."]
pub const RICE_PARSE_CANDIDATE_ERROR_SUCCESS: RiceParseCandidateError = 0;
#[doc = " Not a candidate message."]
Expand All @@ -68,27 +89,6 @@ pub const RICE_PARSE_CANDIDATE_ERROR_BAD_EXTENSION: RiceParseCandidateError = -8
#[doc = " Data is not well formed."]
pub const RICE_PARSE_CANDIDATE_ERROR_MALFORMED: RiceParseCandidateError = -9;
pub type RiceParseCandidateError = i32;
#[doc = " Not a TCP candidate."]
pub const RICE_TCP_TYPE_NONE: RiceTcpType = 0;
#[doc = " The candidate address will connect to a remote address."]
pub const RICE_TCP_TYPE_ACTIVE: RiceTcpType = 1;
#[doc = " The candidate will listen for incominng TCP connections."]
pub const RICE_TCP_TYPE_PASSIVE: RiceTcpType = 2;
#[doc = " Simultaneous open. The candidate will both listen for incoming connections, and connect to\n remote addresses."]
pub const RICE_TCP_TYPE_SO: RiceTcpType = 3;
pub type RiceTcpType = u32;
#[doc = " Openssl."]
pub const RICE_TLS_VARIANT_OPENSSL: RiceTlsVariant = 1;
#[doc = " Rustls."]
pub const RICE_TLS_VARIANT_RUSTLS: RiceTlsVariant = 2;
#[doc = " Dimpl."]
pub const RICE_TLS_VARIANT_DIMPL: RiceTlsVariant = 3;
pub type RiceTlsVariant = u32;
#[doc = " The UDP transport"]
pub const RICE_TRANSPORT_TYPE_UDP: RiceTransportType = 0;
#[doc = " The TCP transport"]
pub const RICE_TRANSPORT_TYPE_TCP: RiceTransportType = 1;
pub type RiceTransportType = u32;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct Credentials {
Expand Down Expand Up @@ -620,6 +620,10 @@ unsafe extern "C" {
#[doc = " Close the `RiceAgent`.\n\n Closure does involve closing network resources (signalled through calls to\n `rice_agent_poll()`) and will only succesfully complete once `rice_agent_poll`() returns\n `Closed`."]
pub fn rice_agent_close(agent: *const RiceAgent, now_nanos: i64);
}
unsafe extern "C" {
#[doc = " Restart every stream in the `RiceAgent` with fresh local ICE credentials."]
pub fn rice_agent_restart(agent: *const RiceAgent, now_nanos: i64);
}
unsafe extern "C" {
#[doc = " Return the process-local unique id for this agent."]
pub fn rice_agent_id(agent: *const RiceAgent) -> u64;
Expand Down Expand Up @@ -961,6 +965,18 @@ unsafe extern "C" {
credentials: *const RiceCredentials,
);
}
unsafe extern "C" {
#[doc = " Restart this `RiceStream` with explicit local ICE credentials."]
pub fn rice_stream_restart_with_credentials(
stream: *mut RiceStream,
credentials: *const RiceCredentials,
now_nanos: i64,
) -> RiceError;
}
unsafe extern "C" {
#[doc = " Restart this `RiceStream` with fresh random local ICE credentials."]
pub fn rice_stream_restart(stream: *mut RiceStream, now_nanos: i64) -> RiceError;
}
unsafe extern "C" {
#[doc = " Construct a `RiceCandidate` from a string as formatted in an SDP and specified in RFC5245\n Section 15.1.\n\n Takes the form 'a=candidate:foundation 1 UDP 12345 127.0.0.1 23456 typ host'."]
pub fn rice_candidate_new_from_sdp_string(
Expand Down Expand Up @@ -1156,6 +1172,18 @@ unsafe extern "C" {
component_id: usize,
) -> *mut RiceComponent;
}
unsafe extern "C" {
#[doc = " Start gathering candidates for every component in a stream with the provided local socket\n addresses.\n\n Components are started in stream order. If a later component fails after an earlier component\n has already begun gathering, the earlier component continues gathering."]
pub fn rice_stream_gather_candidates(
stream: *mut RiceStream,
sockets_len: usize,
sockets_addr: *const *const RiceAddress,
sockets_transports: *const RiceTransportType,
turn_len: usize,
turn_sockets: *const *const RiceAddress,
turn_config: *const *mut RiceTurnConfig,
) -> RiceError;
}
unsafe extern "C" {
#[doc = " Start gathering candidates for a component with the provided local socket addresses.\n\n - `component`: The component to start gathering.\n - `sockets_len`: The number of entries in both `sockets_addr` and `sockets_transports`.\n - `sockets_addr`: An array of addresses for producing host and STUN server-reflexive\n candidates.\n - `sockets_transports`: An array of transport types for producing host and STUN\n server-reflexive candidates.\n - `turn_len`: the number of entries in both `turn_sockets` and `turn_config`.\n - `turn_sockets`: An array of local addresses for producing TURN candidates.\n - `turn_config`: An array of TURN server configurations.\n\n Candidates will be generated as follows (if they succeed):\n\n 1. A host candidate for each `(sockets_transports[i], socket_addr[i])`. If TCP, then both an\n active and passive host candidate will be generated.\n 2. For each configured STUN server, a reflexive candidate for each\n `(sockets_transports[i], socket_addr[i])` if different from any other candidate\n produced. The local address for each STUN server connection will be one of the entries\n provided in `sockets_addr`.\n 3. For each `(turn_sockets[i], turn_config[i])` a TURN allocation will be attempted and a\n relayed candidate produced on success. If you would like multiple options for relayed\n candidates, e.g. UDP, TCP, TCP/TLS, then provide each options as different entries in the\n provided array. The `turn_sockets[i]` value is the local address to communicate with the\n TURN server in `turn_config[i]` and should be different than any value provided through\n `sockets_addr`."]
pub fn rice_component_gather_candidates(
Expand Down
113 changes: 113 additions & 0 deletions rice-c/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,31 @@ impl Stream {
}
}

/// Restart this stream with explicit local ICE credentials.
pub fn restart_with_credentials(
&self,
credentials: &Credentials,
now: Instant,
) -> Result<(), crate::agent::AgentError> {
unsafe {
crate::agent::AgentError::from_c(crate::ffi::rice_stream_restart_with_credentials(
self.ffi,
credentials.into_c_none(),
now.as_nanos(),
))
}
}

/// Restart this stream with fresh random local ICE credentials.
pub fn restart(&self, now: Instant) -> Result<(), crate::agent::AgentError> {
unsafe {
crate::agent::AgentError::from_c(crate::ffi::rice_stream_restart(
self.ffi,
now.as_nanos(),
))
}
}

/// Signal the end of local candidates. Calling this function may allow ICE processing to
/// complete.
pub fn end_of_local_candidates(&self) {
Expand Down Expand Up @@ -228,6 +253,39 @@ impl Stream {
}
}

/// Start gathering candidates for every component in this stream.
pub fn gather_candidates<'a, 'b>(
&self,
sockets: impl IntoIterator<Item = (TransportType, &'a crate::Address)>,
turn_servers: impl IntoIterator<Item = (&'b crate::Address, crate::turn::TurnConfig)>,
) -> Result<(), crate::agent::AgentError> {
unsafe {
let mut transports = vec![];
let mut socket_addr = vec![];
let mut socket_addresses = vec![];
for (ttype, addr) in sockets.into_iter() {
transports.push(ttype.into());
socket_addresses.push(crate::const_override(addr.ffi));
socket_addr.push(addr);
}
let mut turn_sockets = vec![];
let mut turn_configs = vec![];
for (turn_addr, config) in turn_servers.into_iter() {
turn_sockets.push(crate::const_override(turn_addr.ffi));
turn_configs.push(config.into_c_full());
}
crate::agent::AgentError::from_c(crate::ffi::rice_stream_gather_candidates(
self.ffi,
transports.len(),
socket_addresses.as_ptr(),
transports.as_ptr(),
turn_sockets.len(),
turn_sockets.as_ptr(),
turn_configs.as_ptr(),
))
}
}

/// Provide the stream with data that has been received on an external socket. The returned
/// value indicates what has been done with the data and any application data that has been
/// received.
Expand Down Expand Up @@ -430,10 +488,13 @@ impl GatheredCandidate {

#[cfg(test)]
mod tests {
use std::collections::BTreeSet;

use sans_io_time::Instant;

use super::*;
use crate::agent::{Agent, AgentPoll};
use crate::candidate::CandidateApi;

#[test]
fn getters() {
Expand Down Expand Up @@ -503,4 +564,56 @@ mod tests {
let _ = agent.poll(Instant::ZERO);
let _ = agent.poll(Instant::ZERO);
}

#[test]
fn restart_clears_remote_credentials() {
let _log = crate::tests::test_init_log();
let agent = Agent::builder().build();
let stream = agent.add_stream();
let local = Credentials::new("luser", "lpass");
let remote = Credentials::new("ruser", "rpass");
let restart = Credentials::new("nextuser", "nextpass");

stream.set_local_credentials(&local);
stream.set_remote_credentials(&remote);
stream
.restart_with_credentials(&restart, Instant::ZERO)
.unwrap();

assert_eq!(stream.local_credentials(), Some(restart));
assert!(stream.remote_credentials().is_none());
}

#[test]
fn stream_gather_candidates_all_components() {
let _log = crate::tests::test_init_log();
let addr: crate::Address = "192.168.0.1:1000".parse().unwrap();
let agent = Agent::builder().build();
let stream = agent.add_stream();
stream.set_local_credentials(&Credentials::new("luser", "lpass"));
stream.set_remote_credentials(&Credentials::new("ruser", "rpass"));
let _component1 = stream.add_component();
let _component2 = stream.add_component();

stream
.gather_candidates([(TransportType::Udp, &addr)], [])
.unwrap();

let mut gathered_components = BTreeSet::new();
for _ in 0..8 {
let mut poll = agent.poll(Instant::ZERO);
match &mut poll {
AgentPoll::GatheredCandidate(candidate) => {
gathered_components.insert(candidate.gathered.candidate().component_id());
stream.add_local_gathered_candidate(candidate.gathered.take());
}
AgentPoll::GatheringComplete(_) | AgentPoll::WaitUntilNanos(_) => (),
other => panic!("unexpected poll result: {other:?}"),
}
if gathered_components.len() == 2 {
break;
}
}
assert_eq!(gathered_components, BTreeSet::from([1, 2]));
}
}
Loading