Skip to content
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions rs/libmoq/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ pub enum Error {
#[error("offline")]
Offline,

/// Connection was rejected as unauthorized by the server.
#[error("unauthorized")]
Unauthorized,

/// Connection was forbidden by the server.
#[error("forbidden")]
Forbidden,

/// Error from the hang media layer.
#[error("hang error: {0}")]
Hang(#[from] hang::Error),
Expand Down Expand Up @@ -195,6 +203,8 @@ impl ffi::ReturnCode for Error {
Error::Audio(_) => -30,
Error::GroupNotFound => -31,
Error::Native(_) => -32,
Error::Unauthorized => -33,
Error::Forbidden => -34,
}
}
}
50 changes: 43 additions & 7 deletions rs/libmoq/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,25 +74,24 @@ impl Session {
.with_consume(consume)
.reconnect(url);

// report() runs until the reconnect loop gives up; map its terminal error to Connect.
Self::report(callback, reconnect)
.await
.map_err(|err| Error::Connect(Arc::new(err)))
Self::report(callback, reconnect).await
}

/// Forward connection epochs to the status callback until the reconnect loop stops.
///
/// Returns the terminal error via `?`. Disconnects aren't reported: status 0 is reserved for a
/// clean close (delivered as the terminal callback once the task ends).
async fn report(callback: ffi::OnStatus, mut reconnect: moq_native::Reconnect) -> anyhow::Result<()> {
async fn report(callback: ffi::OnStatus, mut reconnect: moq_native::Reconnect) -> Result<(), Error> {
let mut connects: u64 = 0;
loop {
if let moq_native::Status::Connected = reconnect.status().await? {
if let moq_native::Status::Connected = reconnect.status().await.map_err(map_connect_error)? {
connects += 1;
// Positive status carries the connection epoch, so callers can tell a
// reconnect (>1) from the first connect (1). No lock is held, so the C
// callback is free to re-enter libmoq.
let code = i32::try_from(connects).context("connection epoch exceeded i32::MAX")?;
let code = i32::try_from(connects)
.context("connection epoch exceeded i32::MAX")
.map_err(|err| Error::Connect(Arc::new(err)))?;
callback.call(code);
}
}
Expand All @@ -110,3 +109,40 @@ impl Session {
Ok(())
}
}

fn map_connect_error(err: moq_native::Error) -> Error {
match err.connect_error() {
Some(moq_native::ConnectError::Unauthorized) => Error::Unauthorized,
Some(moq_native::ConnectError::Forbidden) => Error::Forbidden,
_ => Error::Connect(Arc::new(err.into())),
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

#[cfg(test)]
mod tests {
use super::*;
use crate::ffi::ReturnCode;

#[test]
fn maps_native_auth_connect_errors() {
assert!(matches!(
map_connect_error(moq_native::ConnectError::Unauthorized.into()),
Error::Unauthorized
));
assert!(matches!(
map_connect_error(moq_native::ConnectError::Forbidden.into()),
Error::Forbidden
));
assert!(matches!(
map_connect_error(moq_net::Error::Unauthorized.into()),
Error::Unauthorized
));
assert!(matches!(
map_connect_error(moq_native::Error::ConnectFailed),
Error::Connect(_)
));
assert_eq!(Error::Unauthorized.code(), -33);
assert_eq!(Error::Forbidden.code(), -34);
assert_eq!(map_connect_error(moq_native::Error::ConnectFailed).code(), -5);
}
}
3 changes: 3 additions & 0 deletions rs/moq-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub enum MoqError {
#[error("unauthorized")]
Unauthorized,

#[error("forbidden")]
Forbidden,

#[error("log: {0}")]
Log(String),
}
33 changes: 27 additions & 6 deletions rs/moq-ffi/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ struct Client {

impl Client {
async fn connect(&self, url: Url) -> Result<Arc<MoqSession>, MoqError> {
let client = self
.config
.clone()
.init()
.map_err(|err| MoqError::Connect(format!("{err}")))?;
let client = self.config.clone().init().map_err(map_connect_error)?;

let publish = self.publish.as_ref().map(|o| o.inner().consume());
let consume = self.consume.as_ref().map(|o| o.inner().clone());
Expand All @@ -29,12 +25,37 @@ impl Client {
.with_consume(consume)
.connect(url)
.await
.map_err(|err| MoqError::Connect(format!("{err}")))?;
.map_err(map_connect_error)?;

Ok(Arc::new(MoqSession::new(session)))
}
}

fn map_connect_error(err: moq_native::Error) -> MoqError {
match err.connect_error() {
Some(moq_native::ConnectError::Unauthorized) => MoqError::Unauthorized,
Some(moq_native::ConnectError::Forbidden) => MoqError::Forbidden,
_ => MoqError::Connect(format!("{err}")),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn maps_native_auth_connect_errors() {
assert!(matches!(
map_connect_error(moq_native::ConnectError::Unauthorized.into()),
MoqError::Unauthorized
));
assert!(matches!(
map_connect_error(moq_native::ConnectError::Forbidden.into()),
MoqError::Forbidden
));
}
}

#[derive(uniffi::Object)]
pub struct MoqClient {
task: Task<Client>,
Expand Down
1 change: 1 addition & 0 deletions rs/moq-native/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ web-transport-noq = { workspace = true, optional = true }
web-transport-proto = { workspace = true, optional = true }
web-transport-quiche = { workspace = true, optional = true }
web-transport-quinn = { workspace = true, optional = true }
web-transport-trait = { workspace = true }

[target.'cfg(target_os = "android")'.dependencies]
tracing-android = { version = "0.2", optional = true }
Expand Down
Loading