Complete first release candidate audit (#107)
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
Some checks failed
API and SemVer surface / api-surface (push) Failing after 1m34s
Native code generation / deterministic (push) Has been cancelled
Concurrency and resource soak audit / soak (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
performance evidence / audit (push) Has been cancelled
First release candidate / non-fuzz-release-gate (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
Native release artifact audit / audit (push) Has been cancelled
Imaging and meshing gate / native (push) Failing after 53s
JPEG 2000 feature / linux (push) Successful in 2m49s
Native Rust workspace compile / compile (push) Failing after 59s
Skia feature / linux (push) Has been cancelled
This commit is contained in:
3751
crates/libremetaverse-voice-webrtc/src/compatibility.rs
Normal file
3751
crates/libremetaverse-voice-webrtc/src/compatibility.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,34 +7,92 @@
|
||||
|
||||
extern crate self as libremetaverse_voice_webrtc;
|
||||
|
||||
#[allow(clippy::all, clippy::pedantic)] // Public shapes mirror the pinned C# API.
|
||||
mod compatibility;
|
||||
mod generated;
|
||||
mod native;
|
||||
|
||||
pub use compatibility::VoiceLogRecord;
|
||||
pub use generated::*;
|
||||
pub use libremetaverse as core;
|
||||
pub use libremetaverse_structured_data as structured_data;
|
||||
pub use libremetaverse_types::Error;
|
||||
pub use native::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct JsonValueInner(serde_json::Value);
|
||||
|
||||
/// Project-owned boundary types for external JSON signatures.
|
||||
pub mod signaling {
|
||||
pub struct JsonValue;
|
||||
/// Owned JSON value without exposing the implementation's JSON crate in
|
||||
/// the public compatibility boundary.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct JsonValue(crate::JsonValueInner);
|
||||
|
||||
impl JsonValue {
|
||||
/// Parses one complete JSON value.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`crate::Error::Argument`] when `value` is not valid JSON.
|
||||
pub fn parse(value: &str) -> Result<Self, crate::Error> {
|
||||
serde_json::from_str(value)
|
||||
.map(crate::JsonValueInner)
|
||||
.map(Self)
|
||||
.map_err(|_| crate::Error::Argument)
|
||||
}
|
||||
|
||||
/// Serializes the value as compact JSON.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`crate::Error::Argument`] if serialization fails.
|
||||
pub fn to_json(&self) -> Result<String, crate::Error> {
|
||||
serde_json::to_string(&self.0.0).map_err(|_| crate::Error::Argument)
|
||||
}
|
||||
|
||||
pub(crate) fn into_inner(self) -> serde_json::Value {
|
||||
self.0.0
|
||||
}
|
||||
|
||||
pub(crate) fn from_inner(value: serde_json::Value) -> Self {
|
||||
Self(crate::JsonValueInner(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Project-owned boundary types for external WebRTC transport signatures.
|
||||
pub mod transport {
|
||||
pub struct RTCDataChannel;
|
||||
pub struct RTCPeerConnection;
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct RTCDataChannel {
|
||||
pub label: String,
|
||||
pub ready: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct RTCPeerConnection {
|
||||
pub connected: bool,
|
||||
}
|
||||
}
|
||||
|
||||
/// Project-owned boundary types for external media signatures.
|
||||
pub mod media {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct AudioSamplingRatesEnum(pub i32);
|
||||
}
|
||||
|
||||
/// Project-owned boundary types for external audio signatures.
|
||||
pub mod audio {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct NoiseSuppressionLevel(pub i32);
|
||||
pub struct SoundFlowAudioEndPoint;
|
||||
pub struct SoundFlowAudioSource;
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SoundFlowAudioEndPoint {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub struct SoundFlowAudioSource {
|
||||
pub id: String,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +453,7 @@ struct Playback {
|
||||
/// Owned native WebRTC voice session.
|
||||
pub struct WebRtcVoiceSession {
|
||||
command: mpsc::Sender<Command>,
|
||||
events: mpsc::Receiver<VoiceEvent>,
|
||||
events: Option<mpsc::Receiver<VoiceEvent>>,
|
||||
snapshot: Arc<Mutex<SessionSnapshot>>,
|
||||
signaling: Arc<dyn VoiceSignaling>,
|
||||
viewer_session: UUID,
|
||||
@@ -461,6 +461,8 @@ pub struct WebRtcVoiceSession {
|
||||
credentials: Option<VoiceSecret>,
|
||||
task: Option<JoinHandle<()>>,
|
||||
timeout: Duration,
|
||||
local_sdp: String,
|
||||
remote_sdp: String,
|
||||
#[cfg(feature = "real-audio")]
|
||||
hardware_audio: Option<RealAudioBridge>,
|
||||
}
|
||||
@@ -493,10 +495,11 @@ impl WebRtcVoiceSession {
|
||||
let (offer, pending) = change.apply().ok_or(WebRtcError::Protocol(
|
||||
"SDP changes did not produce an offer",
|
||||
))?;
|
||||
let local_sdp = offer.to_sdp_string();
|
||||
let request = ProvisionRequest {
|
||||
jsep: Jsep {
|
||||
kind: "offer".into(),
|
||||
sdp: offer.to_sdp_string(),
|
||||
sdp: local_sdp.clone(),
|
||||
},
|
||||
channel_type: "local".into(),
|
||||
voice_server_type: "webrtc".into(),
|
||||
@@ -509,7 +512,8 @@ impl WebRtcVoiceSession {
|
||||
abandon_provisioned(&signaling, &mut response, config.timeout).await;
|
||||
return Err(WebRtcError::Protocol("SDP answer exceeds 1 MiB"));
|
||||
}
|
||||
let answer = match SdpAnswer::from_sdp_string(&sanitize_remote_sdp(&response.answer_sdp)) {
|
||||
let remote_sdp = sanitize_remote_sdp(&response.answer_sdp);
|
||||
let answer = match SdpAnswer::from_sdp_string(&remote_sdp) {
|
||||
Ok(answer) => answer,
|
||||
Err(error) => {
|
||||
abandon_provisioned(&signaling, &mut response, config.timeout).await;
|
||||
@@ -578,7 +582,7 @@ impl WebRtcVoiceSession {
|
||||
});
|
||||
Ok(Self {
|
||||
command: command_tx,
|
||||
events: event_rx,
|
||||
events: Some(event_rx),
|
||||
snapshot,
|
||||
signaling,
|
||||
viewer_session: response.viewer_session,
|
||||
@@ -586,13 +590,21 @@ impl WebRtcVoiceSession {
|
||||
credentials: response.credentials,
|
||||
task: Some(task),
|
||||
timeout: config.timeout,
|
||||
local_sdp,
|
||||
remote_sdp,
|
||||
#[cfg(feature = "real-audio")]
|
||||
hardware_audio: Some(hardware_audio),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn next_event(&mut self) -> Option<VoiceEvent> {
|
||||
self.events.recv().await
|
||||
self.events.as_mut()?.recv().await
|
||||
}
|
||||
|
||||
/// Transfers the event stream to a facade task while retaining the session's
|
||||
/// command and teardown handles.
|
||||
pub fn take_events(&mut self) -> Option<mpsc::Receiver<VoiceEvent>> {
|
||||
self.events.take()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -615,6 +627,49 @@ impl WebRtcVoiceSession {
|
||||
reply_rx.await.map_err(|_| WebRtcError::Closed)?
|
||||
}
|
||||
|
||||
/// Queues a bounded data-channel message without blocking the caller.
|
||||
pub fn try_send_data(&self, message: impl Into<String>) -> Result<bool, WebRtcError> {
|
||||
let message = message.into();
|
||||
if message.len() > MAX_DATA_BYTES {
|
||||
return Err(WebRtcError::InvalidInput(
|
||||
"data-channel message exceeds 64 KiB",
|
||||
));
|
||||
}
|
||||
let (reply_tx, _reply_rx) = oneshot::channel();
|
||||
match self.command.try_send(Command::Data(message, reply_tx)) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(mpsc::error::TrySendError::Full(_)) => Ok(false),
|
||||
Err(mpsc::error::TrySendError::Closed(_)) => Err(WebRtcError::Closed),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn viewer_session(&self) -> UUID {
|
||||
self.viewer_session
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn channel(&self) -> Option<String> {
|
||||
self.channel.clone()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn credentials(&self) -> Option<String> {
|
||||
self.credentials
|
||||
.as_ref()
|
||||
.map(|credentials| credentials.expose().to_owned())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn local_sdp(&self) -> String {
|
||||
self.local_sdp.clone()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn remote_sdp(&self) -> String {
|
||||
self.remote_sdp.clone()
|
||||
}
|
||||
|
||||
pub async fn set_peer_mute(&self, peer: UUID, mute: bool) -> Result<bool, WebRtcError> {
|
||||
self.send_data(json!({"m": {peer.to_string(): mute}}).to_string())
|
||||
.await
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
//! Behavioral checks for the mutable WebRTC voice data models.
|
||||
|
||||
use libremetaverse_types::UUID;
|
||||
use libremetaverse_voice_webrtc::{
|
||||
VoiceSessionAvatarPosition, VoiceSessionInt3, VoiceSessionInt4, VoiceSessionPeerAudioState,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn peer_audio_state_uses_clr_defaults_and_round_trips_updates() {
|
||||
let mut state = VoiceSessionPeerAudioState::new().expect("PeerAudioState");
|
||||
assert_eq!(state.power(), None);
|
||||
assert_eq!(state.voice_active(), None);
|
||||
assert_eq!(state.joined_primary(), None);
|
||||
assert_eq!(state.moderator_muted(), None);
|
||||
assert!(!state.left());
|
||||
|
||||
state.set_power(Some(Some(37)));
|
||||
state.set_voice_active(Some(Some(true)));
|
||||
state.set_joined_primary(Some(Some(false)));
|
||||
state.set_moderator_muted(Some(Some(true)));
|
||||
state.set_left(true);
|
||||
|
||||
assert_eq!(state.power(), Some(Some(37)));
|
||||
assert_eq!(state.voice_active(), Some(Some(true)));
|
||||
assert_eq!(state.joined_primary(), Some(Some(false)));
|
||||
assert_eq!(state.moderator_muted(), Some(Some(true)));
|
||||
assert!(state.left());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn avatar_position_round_trips_typed_coordinates() {
|
||||
let mut position = VoiceSessionAvatarPosition::new().expect("AvatarPosition");
|
||||
let agent = UUID::new_with_u_int64(44).expect("UUID");
|
||||
let xyz = VoiceSessionInt3 { x: 1, y: 2, z: 3 };
|
||||
let xyzw = VoiceSessionInt4 {
|
||||
x: 4,
|
||||
y: 5,
|
||||
z: 6,
|
||||
w: 7,
|
||||
};
|
||||
|
||||
assert_eq!(position.agent_id(), UUID::zero());
|
||||
position.set_agent_id(agent);
|
||||
position.set_sender_position(Some(Some(xyz)));
|
||||
position.set_listener_heading(Some(Some(xyzw)));
|
||||
|
||||
assert_eq!(position.agent_id(), agent);
|
||||
assert_eq!(position.sender_position(), Some(Some(xyz)));
|
||||
assert_eq!(position.listener_heading(), Some(Some(xyzw)));
|
||||
}
|
||||
Reference in New Issue
Block a user