From f3861d3459fc754c40e14cd84834c4ab32521917 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Tue, 29 Oct 2024 17:58:51 -0400 Subject: [PATCH 01/29] Log ADM backend name after logging is initialized. --- src/rust/src/electron.rs | 6 ++ src/rust/src/webrtc/audio_device_module.rs | 6 ++ .../src/webrtc/peer_connection_factory.rs | 70 +++++++++++++------ 3 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/rust/src/electron.rs b/src/rust/src/electron.rs index dd4d7940..db027c05 100644 --- a/src/rust/src/electron.rs +++ b/src/rust/src/electron.rs @@ -439,6 +439,12 @@ impl CallEndpoint { *event_reporter_for_logging = Some(event_reporter.clone()); } + if use_ringrtc_adm { + // After initializing logs, log the backend in use. + let backend = peer_connection_factory.audio_backend(); + info!("audio_device_module using cubeb backend {:?}", backend); + } + // Only relevant for 1:1 calls let signaling_sender = Box::new(event_reporter.clone()); let should_assume_messages_sent = false; // Use async notification from app to send next message. diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs index e4ee9306..67acd20a 100644 --- a/src/rust/src/webrtc/audio_device_module.rs +++ b/src/rust/src/webrtc/audio_device_module.rs @@ -192,6 +192,12 @@ impl AudioDeviceModule { } } + pub fn backend_name(&self) -> Option { + self.cubeb_ctx + .as_ref() + .map(|ctx| ctx.backend_id().to_string()) + } + pub fn terminate(&mut self) -> i32 { if self.recording { self.stop_recording(); diff --git a/src/rust/src/webrtc/peer_connection_factory.rs b/src/rust/src/webrtc/peer_connection_factory.rs index 8671ddda..923220b2 100644 --- a/src/rust/src/webrtc/peer_connection_factory.rs +++ b/src/rust/src/webrtc/peer_connection_factory.rs @@ -195,7 +195,9 @@ impl Default for AudioConfig { } impl AudioConfig { - fn rffi(&self) -> Result { + // Return both the RffiAudioConfig as well as the name of the cubeb backend + // in use, if any. + fn rffi(&self) -> Result<(RffiAudioConfig, Option)> { let (input_file, output_file) = if self.audio_device_module_type == RffiAudioDeviceModuleType::File { if let Some(file_based_adm_config) = &self.file_based_adm_config { @@ -210,11 +212,10 @@ impl AudioConfig { (std::ptr::null(), std::ptr::null()) }; - #[cfg(not(all(target_os = "linux", target_arch = "aarch64")))] - let audio_device_module_type = self.audio_device_module_type; - #[cfg(all(target_os = "linux", target_arch = "aarch64"))] let audio_device_module_type = - if self.audio_device_module_type == RffiAudioDeviceModuleType::RingRtc { + if cfg!(not(all(target_os = "linux", target_arch = "aarch64"))) { + self.audio_device_module_type + } else if self.audio_device_module_type == RffiAudioDeviceModuleType::RingRtc { RffiAudioDeviceModuleType::Default } else { self.audio_device_module_type @@ -225,11 +226,22 @@ impl AudioConfig { feature = "native", not(all(target_os = "linux", target_arch = "aarch64")) ))] - let adm_borrowed = - webrtc::ptr::Borrowed::from_ptr(Box::into_raw(Box::new(AudioDeviceModule::new()))) - .to_void(); + let (adm_borrowed, backend_name) = { + let mut adm = AudioDeviceModule::new(); + // Initialize the ADM here. This isn't strictly necessary, but allows + // us to log the backend name (e.g. audiounit vs audiounit-rust). + adm.init(); + let backend_name = adm.backend_name(); + ( + webrtc::ptr::Borrowed::from_ptr(Box::into_raw(Box::new(AudioDeviceModule::new()))) + .to_void(), + backend_name, + ) + }; #[cfg(all(target_os = "linux", target_arch = "aarch64"))] - let adm_borrowed = webrtc::ptr::Borrowed::null(); + let (adm_borrowed, backend_name) = (webrtc::ptr::Borrowed::null(), None); + #[cfg(any(feature = "sim", not(feature = "native")))] + let backend_name = None; #[cfg(all( not(feature = "sim"), @@ -241,19 +253,22 @@ impl AudioConfig { #[cfg(all(target_os = "linux", target_arch = "aarch64"))] let rust_audio_device_callbacks = webrtc::ptr::Borrowed::null(); - Ok(RffiAudioConfig { - audio_device_module_type, - input_file: webrtc::ptr::Borrowed::from_ptr(input_file), - output_file: webrtc::ptr::Borrowed::from_ptr(output_file), - high_pass_filter_enabled: self.high_pass_filter_enabled, - aec_enabled: self.aec_enabled, - ns_enabled: self.ns_enabled, - agc_enabled: self.agc_enabled, - #[cfg(all(not(feature = "sim"), feature = "native"))] - adm_borrowed, - #[cfg(all(not(feature = "sim"), feature = "native"))] - rust_audio_device_callbacks, - }) + Ok(( + RffiAudioConfig { + audio_device_module_type, + input_file: webrtc::ptr::Borrowed::from_ptr(input_file), + output_file: webrtc::ptr::Borrowed::from_ptr(output_file), + high_pass_filter_enabled: self.high_pass_filter_enabled, + aec_enabled: self.aec_enabled, + ns_enabled: self.ns_enabled, + agc_enabled: self.agc_enabled, + #[cfg(all(not(feature = "sim"), feature = "native"))] + adm_borrowed, + #[cfg(all(not(feature = "sim"), feature = "native"))] + rust_audio_device_callbacks, + }, + backend_name, + )) } } @@ -309,6 +324,7 @@ pub struct PeerConnectionFactory { rffi: webrtc::Arc, #[cfg(feature = "native")] device_counts: DeviceCounts, + backend_name: Option, } impl PeerConnectionFactory { @@ -317,9 +333,11 @@ impl PeerConnectionFactory { pub fn new(audio_config: &AudioConfig, use_injectable_network: bool) -> Result { debug!("PeerConnectionFactory::new()"); + let (audio_config_rffi, backend_name) = audio_config.rffi()?; + let rffi = unsafe { webrtc::Arc::from_owned(pcf::Rust_createPeerConnectionFactory( - webrtc::ptr::Borrowed::from_ptr(&audio_config.rffi()?), + webrtc::ptr::Borrowed::from_ptr(&audio_config_rffi), use_injectable_network, )) }; @@ -330,6 +348,7 @@ impl PeerConnectionFactory { rffi, #[cfg(feature = "native")] device_counts: Default::default(), + backend_name, }) } @@ -352,6 +371,7 @@ impl PeerConnectionFactory { rffi, #[cfg(feature = "native")] device_counts: Default::default(), + backend_name: None, } } @@ -694,4 +714,8 @@ impl PeerConnectionFactory { Err(RingRtcError::SetAudioDevice.into()) } } + + pub fn audio_backend(&self) -> Option { + self.backend_name.clone() + } } From 1bac8312fb99f27fedd11f9886f7e7069e2b17a3 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Thu, 31 Oct 2024 17:05:19 -0400 Subject: [PATCH 02/29] Fix prebuilt_webrtc on windows --- .github/workflows/ringrtc.yml | 4 +++- src/rust/build.rs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ringrtc.yml b/.github/workflows/ringrtc.yml index ac7ead80..c08db87a 100644 --- a/.github/workflows/ringrtc.yml +++ b/.github/workflows/ringrtc.yml @@ -150,12 +150,14 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-22.04, macos-13] + os: [ubuntu-22.04, macos-13, windows-latest] include: - os: ubuntu-22.04 install-deps: sudo apt-get update && sudo apt-get install -y protobuf-compiler libpulse-dev - os: macos-13 install-deps: brew install protobuf coreutils + - os: windows-latest + install-deps: choco install protoc cmake runs-on: ${{ matrix.os }} defaults: run: diff --git a/src/rust/build.rs b/src/rust/build.rs index ab1f64c7..cc3cccbc 100644 --- a/src/rust/build.rs +++ b/src/rust/build.rs @@ -142,9 +142,10 @@ fn fetch_webrtc_artifact( platform, artifact_out_dir ); - let output = Command::new(fetch_script) + let output = Command::new("bash") .current_dir(project_dir()) .env("OUTPUT_DIR", artifact_out_dir) + .arg(fetch_script) .arg("--platform") .arg(platform) .output() From eda6e002b8e8fa093c96742b1112660467e250d7 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Thu, 31 Oct 2024 17:36:06 -0400 Subject: [PATCH 03/29] Update to latest cubeb and re-enable new ADM on linux aarch64. --- Cargo.lock | 18 ++++---- acknowledgments/acknowledgments.html | 39 +++++++++++++--- acknowledgments/acknowledgments.md | 31 ++++++++++++- acknowledgments/acknowledgments.plist | 35 ++++++++++++++- src/rust/Cargo.toml | 5 +-- src/rust/src/lib.rs | 17 ++----- src/rust/src/webrtc/audio_device_module.rs | 25 +++++++++++ .../src/webrtc/peer_connection_factory.rs | 44 +++---------------- 8 files changed, 142 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6ce99b0..0913f5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -534,12 +534,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.5" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -757,28 +758,29 @@ dependencies = [ [[package]] name = "cubeb" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46141374032595c0fa92563b6ff21c2fef695c4e932ab1a35d8726d78b35255d" +checksum = "72e0a08fb5746d9c1cf8257994151b802bcd9c64f786aa92e009ad8a0d92e43f" dependencies = [ "cubeb-core", ] [[package]] name = "cubeb-core" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9d9d5a70e005de5a7ca350d973822702244af08c0e0720112641a799d8be4c" +checksum = "541f3f1620b78511ecb29821f64de9d4303ed90eb0ae9e83f3fecdd91d94f67b" dependencies = [ "bitflags 1.3.2", + "cc", "cubeb-sys", ] [[package]] name = "cubeb-sys" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "192cd49cc8485ceb2b9d82ba7ca583af01b9b0a9251f4128550d154b06702c6e" +checksum = "052b32518d7d175c58d07a57990a5c961219860f71c8ae1b3dbba539ce47defc" dependencies = [ "cmake", "pkg-config", diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index c3ffe53e..0e3b1f1b 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -45,7 +45,7 @@

Third Party Licenses

Overview of licenses:

    -
  • MIT License (157)
  • +
  • MIT License (158)
  • GNU Affero General Public License v3.0 (11)
  • Apache License 2.0 (5)
  • BSD 3-Clause "New" or "Revised" License (4)
  • @@ -1540,9 +1540,9 @@

    Used by:

    ISC License

    Used by:

    Copyright © 2017 Mozilla Foundation
     
    @@ -1732,7 +1732,7 @@ 

    Used by:

    MIT License

    Used by:

    + +
  • +

    MIT License

    +

    Used by:

    + +
    The MIT License (MIT)
    +
    +Copyright (c) 2015 Nicholas Allegra (comex).
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.
     
  • diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md index 54091f64..8ef18159 100644 --- a/acknowledgments/acknowledgments.md +++ b/acknowledgments/acknowledgments.md @@ -1453,7 +1453,7 @@ THIS SOFTWARE. ``` -## cubeb-core 0.14.0, cubeb-sys 0.14.0, cubeb 0.14.0 +## cubeb-core 0.16.0, cubeb-sys 0.16.0, cubeb 0.16.0 ``` Copyright © 2017 Mozilla Foundation @@ -1620,7 +1620,7 @@ SOFTWARE. ``` -## cc 1.1.5, cfg-if 1.0.0, cmake 0.1.50, pkg-config 0.3.30 +## cc 1.1.31, cfg-if 1.0.0, cmake 0.1.50, pkg-config 0.3.30 ``` Copyright (c) 2014 Alex Crichton @@ -3409,6 +3409,33 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +## shlex 1.3.0 + +``` +The MIT License (MIT) + +Copyright (c) 2015 Nicholas Allegra (comex). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ``` ## jni 0.21.1 diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist index 6c667a2e..b7a23c3d 100644 --- a/acknowledgments/acknowledgments.plist +++ b/acknowledgments/acknowledgments.plist @@ -1518,7 +1518,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. License ISC License Title - cubeb-core 0.14.0, cubeb-sys 0.14.0, cubeb 0.14.0 + cubeb-core 0.16.0, cubeb-sys 0.16.0, cubeb 0.16.0 Type PSGroupSpecifier @@ -1721,7 +1721,7 @@ DEALINGS IN THE SOFTWARE. License MIT License Title - cc 1.1.5, cfg-if 1.0.0, cmake 0.1.50, pkg-config 0.3.30 + cc 1.1.31, cfg-if 1.0.0, cmake 0.1.50, pkg-config 0.3.30 Type PSGroupSpecifier @@ -3729,6 +3729,37 @@ THE SOFTWARE. FooterText The MIT License (MIT) +Copyright (c) 2015 Nicholas Allegra (comex). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + License + MIT License + Title + shlex 1.3.0 + Type + PSGroupSpecifier + + + FooterText + The MIT License (MIT) + Copyright (c) 2016 Prevoty, Inc. and jni-rs contributors Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index a0ffd2c0..b63c2bfc 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -94,10 +94,9 @@ chrono = {version = "0.4.38", optional = true } call_protobuf = { path = "../../protobuf", package = "protobuf"} mrp = { path = "../../mrp" } -[target.'cfg(not(all(target_os="linux", target_arch="aarch64")))'.dependencies] # Optional, needed by "native" feature -cubeb = { version = "0.14.0", optional = true } -cubeb-core = { version = "0.14.0", optional = true } +cubeb = { version = "0.16.0", optional = true } +cubeb-core = { version = "0.16.0", optional = true } [target.'cfg(windows)'.dependencies] # Only needed by native feature on windows diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 1bf1e5da..09b3a0af 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -89,17 +89,9 @@ pub mod native; pub mod webrtc { pub mod arc; pub use arc::Arc; - #[cfg(all( - not(feature = "sim"), - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) - ))] + #[cfg(all(not(feature = "sim"), feature = "native"))] pub mod audio_device_module; - #[cfg(all( - not(feature = "sim"), - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) - ))] + #[cfg(all(not(feature = "sim"), feature = "native"))] pub mod audio_device_module_utils; pub mod field_trial; pub mod ice_gatherer; @@ -119,10 +111,7 @@ pub mod webrtc { pub mod stats_observer; #[cfg(not(feature = "sim"))] mod ffi { - #[cfg(all( - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) - ))] + #[cfg(feature = "native")] pub mod audio_device_module; pub mod field_trial; pub mod ice_gatherer; diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs index 67acd20a..acc4a00b 100644 --- a/src/rust/src/webrtc/audio_device_module.rs +++ b/src/rust/src/webrtc/audio_device_module.rs @@ -1108,3 +1108,28 @@ impl AudioDeviceModule { } } } + +#[cfg(test)] +mod audio_device_module_tests { + use crate::webrtc::audio_device_module::AudioDeviceModule; + + #[test] + fn init_backend_id() { + #[cfg(target_os = "windows")] + let expected_backend = "wasapi"; + #[cfg(target_os = "macos")] + let expected_backend = "audiounit-rust"; + #[cfg(target_os = "linux")] + let expected_backend = "pulse-rust"; + + cubeb_core::set_logging( + cubeb_core::LogLevel::Normal, + Some(|cstr| println!("{:?}", cstr)), + ) + .expect("failed to set logging"); + let mut adm = AudioDeviceModule::new(); + assert_eq!(adm.init(), 0); + + assert_eq!(adm.backend_name(), Some(expected_backend.to_string())); + } +} diff --git a/src/rust/src/webrtc/peer_connection_factory.rs b/src/rust/src/webrtc/peer_connection_factory.rs index 923220b2..56b7763a 100644 --- a/src/rust/src/webrtc/peer_connection_factory.rs +++ b/src/rust/src/webrtc/peer_connection_factory.rs @@ -16,17 +16,9 @@ use std::os::raw::c_char; use crate::common::Result; use crate::error::RingRtcError; use crate::webrtc; -#[cfg(all( - not(feature = "sim"), - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) -))] +#[cfg(all(not(feature = "sim"), feature = "native"))] use crate::webrtc::audio_device_module::AudioDeviceModule; -#[cfg(all( - not(feature = "sim"), - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) -))] +#[cfg(all(not(feature = "sim"), feature = "native"))] use crate::webrtc::ffi::audio_device_module::AUDIO_DEVICE_CBS_PTR; #[cfg(feature = "injectable_network")] use crate::webrtc::injectable_network::InjectableNetwork; @@ -212,20 +204,7 @@ impl AudioConfig { (std::ptr::null(), std::ptr::null()) }; - let audio_device_module_type = - if cfg!(not(all(target_os = "linux", target_arch = "aarch64"))) { - self.audio_device_module_type - } else if self.audio_device_module_type == RffiAudioDeviceModuleType::RingRtc { - RffiAudioDeviceModuleType::Default - } else { - self.audio_device_module_type - }; - - #[cfg(all( - not(feature = "sim"), - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) - ))] + #[cfg(all(not(feature = "sim"), feature = "native"))] let (adm_borrowed, backend_name) = { let mut adm = AudioDeviceModule::new(); // Initialize the ADM here. This isn't strictly necessary, but allows @@ -238,24 +217,12 @@ impl AudioConfig { backend_name, ) }; - #[cfg(all(target_os = "linux", target_arch = "aarch64"))] - let (adm_borrowed, backend_name) = (webrtc::ptr::Borrowed::null(), None); #[cfg(any(feature = "sim", not(feature = "native")))] let backend_name = None; - #[cfg(all( - not(feature = "sim"), - feature = "native", - not(all(target_os = "linux", target_arch = "aarch64")) - ))] - let rust_audio_device_callbacks = - webrtc::ptr::Borrowed::from_ptr(AUDIO_DEVICE_CBS_PTR).to_void(); - #[cfg(all(target_os = "linux", target_arch = "aarch64"))] - let rust_audio_device_callbacks = webrtc::ptr::Borrowed::null(); - Ok(( RffiAudioConfig { - audio_device_module_type, + audio_device_module_type: self.audio_device_module_type, input_file: webrtc::ptr::Borrowed::from_ptr(input_file), output_file: webrtc::ptr::Borrowed::from_ptr(output_file), high_pass_filter_enabled: self.high_pass_filter_enabled, @@ -265,7 +232,8 @@ impl AudioConfig { #[cfg(all(not(feature = "sim"), feature = "native"))] adm_borrowed, #[cfg(all(not(feature = "sim"), feature = "native"))] - rust_audio_device_callbacks, + rust_audio_device_callbacks: webrtc::ptr::Borrowed::from_ptr(AUDIO_DEVICE_CBS_PTR) + .to_void(), }, backend_name, )) From 28ee7ec53e7734555f6f4d6e5b10fd52c159bbb5 Mon Sep 17 00:00:00 2001 From: Jim Gustafson Date: Thu, 31 Oct 2024 15:03:44 -0700 Subject: [PATCH 04/29] Log remote removed ports --- src/rust/src/core/connection.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rust/src/core/connection.rs b/src/rust/src/core/connection.rs index 6b24d865..3e627f38 100644 --- a/src/rust/src/core/connection.rs +++ b/src/rust/src/core/connection.rs @@ -1292,8 +1292,10 @@ where ) -> Result<()> { let mut added_sdps = vec![]; let mut removed_addresses = vec![]; + let mut removed_ports = vec![]; for candidate in remote_ice_candidates { if let Some(removed_address) = candidate.removed_address() { + removed_ports.push(removed_address.port()); removed_addresses.push(removed_address); // We don't add a candidate if it's both added and removed because of // the backwards-compatibility mechanism we have that contains a dummy @@ -1313,6 +1315,8 @@ where ) ); + info!("Remote ICE candidates removed; ports: {:?}", removed_ports); + for added_sdp in added_sdps { if let Err(e) = pc.add_ice_candidate_from_sdp(&added_sdp) { warn!("Failed to add ICE candidate: {:?}", e); @@ -1743,7 +1747,7 @@ where .iter() .map(|address| address.port()) .collect(); - info!("Local ICE candidates removed; ports: {:?}", removed_ports,); + info!("Local ICE candidates removed; ports: {:?}", removed_ports); let candidates = removed_addresses .into_iter() From d049429ddc0edbe90616f12cd1845dff56bb44d7 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Fri, 1 Nov 2024 13:25:48 -0400 Subject: [PATCH 05/29] Bump version to v2.48.5. --- CHANGELOG.md | 11 +++++++++++ Cargo.lock | 6 +++--- Cargo.toml | 2 +- SignalRingRTC.podspec | 2 +- acknowledgments/acknowledgments.html | 6 +++--- acknowledgments/acknowledgments.md | 2 +- acknowledgments/acknowledgments.plist | 2 +- call_sim/docker/signaling_server/Cargo.lock | 2 +- config/version.properties | 2 +- src/node/package-lock.json | 4 ++-- src/node/package.json | 2 +- 11 files changed, 26 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 606f8e89..6a87c1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v2.48.5 + +- Desktop: Improve new Audio Device Module support on mac and linux + - Update cubeb + - Reenable build on aarch64 linux + - Debug logging + +- Additional debug logging + +- Build improvements + ## v2.48.4 - Update to webrtc 6723a (m130) diff --git a/Cargo.lock b/Cargo.lock index 0913f5d6..991b71bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1703,7 +1703,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "mrp" -version = "2.48.4" +version = "2.48.5" dependencies = [ "anyhow", "log", @@ -2117,7 +2117,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.48.4" +version = "2.48.5" dependencies = [ "prost-build", "tonic-build", @@ -2261,7 +2261,7 @@ dependencies = [ [[package]] name = "ringrtc" -version = "2.48.4" +version = "2.48.5" dependencies = [ "aes", "aes-gcm-siv", diff --git a/Cargo.toml b/Cargo.toml index 7e5bd521..0eaa48a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ ] [workspace.package] -version = "2.48.4" +version = "2.48.5" authors = ["Calling Team "] [patch.crates-io] diff --git a/SignalRingRTC.podspec b/SignalRingRTC.podspec index b12a7762..07f01d30 100644 --- a/SignalRingRTC.podspec +++ b/SignalRingRTC.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "SignalRingRTC" - s.version = "2.48.4" + s.version = "2.48.5" s.summary = "A Swift & Objective-C library used by the Signal iOS app for WebRTC interactions." s.description = <<-DESC diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index 0e3b1f1b..87b946cf 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -733,9 +733,9 @@

    GNU Affero General Public License v3.0 (synthesized)

    Used by:

    diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md index 8ef18159..53a43124 100644 --- a/acknowledgments/acknowledgments.md +++ b/acknowledgments/acknowledgments.md @@ -669,7 +669,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ``` -## libsignal-core 0.1.0, mrp 2.48.4, protobuf 2.48.4, ringrtc 2.48.4, regex-aot 0.1.0, partial-default-derive 0.1.0 +## libsignal-core 0.1.0, mrp 2.48.5, protobuf 2.48.5, ringrtc 2.48.5, regex-aot 0.1.0, partial-default-derive 0.1.0 ``` GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist index b7a23c3d..24dd625a 100644 --- a/acknowledgments/acknowledgments.plist +++ b/acknowledgments/acknowledgments.plist @@ -924,7 +924,7 @@ You should also get your employer (if you work as a programmer) or school, if an License GNU Affero General Public License v3.0 Title - libsignal-core 0.1.0, mrp 2.48.4, protobuf 2.48.4, ringrtc 2.48.4, regex-aot 0.1.0, partial-default-derive 0.1.0 + libsignal-core 0.1.0, mrp 2.48.5, protobuf 2.48.5, ringrtc 2.48.5, regex-aot 0.1.0, partial-default-derive 0.1.0 Type PSGroupSpecifier
    diff --git a/call_sim/docker/signaling_server/Cargo.lock b/call_sim/docker/signaling_server/Cargo.lock index 379fcbad..fd4598bc 100644 --- a/call_sim/docker/signaling_server/Cargo.lock +++ b/call_sim/docker/signaling_server/Cargo.lock @@ -761,7 +761,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.48.4" +version = "2.48.5" dependencies = [ "prost-build", "tonic-build", diff --git a/config/version.properties b/config/version.properties index 40680598..960cdaad 100644 --- a/config/version.properties +++ b/config/version.properties @@ -2,4 +2,4 @@ webrtc.version=6723a ringrtc.version.major=2 ringrtc.version.minor=48 -ringrtc.version.revision=4 +ringrtc.version.revision=5 diff --git a/src/node/package-lock.json b/src/node/package-lock.json index 7c18145d..46b2ff52 100644 --- a/src/node/package-lock.json +++ b/src/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.4", + "version": "2.48.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@signalapp/ringrtc", - "version": "2.48.4", + "version": "2.48.5", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/node/package.json b/src/node/package.json index 1f166dd1..74d661ea 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -1,6 +1,6 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.4", + "version": "2.48.5", "description": "Signal Messenger voice and video calling library.", "main": "dist/index.js", "types": "dist/index.d.ts", From 6f46e724851ad8c62ed0cb0642d482e121524f3a Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Mon, 4 Nov 2024 12:30:23 -0500 Subject: [PATCH 06/29] Ignore monitor inputs on Linux. Such devices are likely not the intended use case for a call, and could lead to loud audio feedback. One exception: allow them if the user has selected one as the OS default. --- .../src/webrtc/audio_device_module_utils.rs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/rust/src/webrtc/audio_device_module_utils.rs b/src/rust/src/webrtc/audio_device_module_utils.rs index 1bb755fc..90d60970 100644 --- a/src/rust/src/webrtc/audio_device_module_utils.rs +++ b/src/rust/src/webrtc/audio_device_module_utils.rs @@ -17,6 +17,15 @@ pub struct DeviceCollectionWrapper<'a> { device_collection: DeviceCollection<'a>, } +#[cfg(target_os = "linux")] +fn device_is_monitor(device: &DeviceInfo) -> bool { + device.device_type() == cubeb::DeviceType::INPUT + && device + .device_id() + .as_ref() + .map_or(false, |s| s.ends_with(".monitor")) +} + impl DeviceCollectionWrapper<'_> { pub fn new(device_collection: DeviceCollection<'_>) -> DeviceCollectionWrapper<'_> { DeviceCollectionWrapper { device_collection } @@ -31,6 +40,16 @@ impl DeviceCollectionWrapper<'_> { .filter(|d| d.state() == DeviceState::Enabled) } + // For linux only, a method that will ignore "monitor" devices. + #[cfg(target_os = "linux")] + pub fn iter_non_monitor( + &self, + ) -> std::iter::Filter, fn(&&DeviceInfo) -> bool> { + self.device_collection + .iter() + .filter(|&d| d.state() == DeviceState::Enabled && !device_is_monitor(d)) + } + #[cfg(target_os = "windows")] /// Get a specified device index, accounting for the two default devices. pub fn get(&self, idx: usize) -> Option<&DeviceInfo> { @@ -59,9 +78,19 @@ impl DeviceCollectionWrapper<'_> { if self.count() == 0 { None } else if idx > 0 { - self.iter().nth(idx - 1) + #[cfg(target_os = "macos")] + { + self.iter().nth(idx - 1) + } + #[cfg(target_os = "linux")] + { + // filter out "monitor" devices. + self.iter_non_monitor().nth(idx - 1) + } } else { // Find a device that's preferred for VOICE -- device 0 is the "default" + // Even on linux, we do *NOT* filter monitor devices -- if the user specified that as + // default, we respect it. self.iter() .find(|&device| device.preferred().contains(DevicePref::VOICE)) } @@ -75,10 +104,16 @@ impl DeviceCollectionWrapper<'_> { pub fn count(&self) -> usize { self.iter().count() } + #[cfg(not(target_os = "windows"))] /// Returns the number of devices, counting the default device. pub fn count(&self) -> usize { + #[cfg(target_os = "macos")] let count = self.iter().count(); + // Whether a monitor device is default or not, there will be an additional default, + // so no need to do anything different. + #[cfg(target_os = "linux")] + let count = self.iter_non_monitor().count(); if count == 0 { 0 } else { From 2c4f2b3980b7d3c1704f7a6d482f7e7af8de3424 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Mon, 4 Nov 2024 20:07:02 -0500 Subject: [PATCH 07/29] Update to cubeb 0.17.0 This fixes cross-compilation for the cubeb rust libs. Also, pass a necessary environment variable to request that the rust backends are cross-compiled. --- .github/workflows/desktop_artifacts.yml | 2 ++ Cargo.lock | 12 ++++++------ acknowledgments/acknowledgments.html | 6 +++--- acknowledgments/acknowledgments.md | 2 +- acknowledgments/acknowledgments.plist | 2 +- bin/build-electron | 3 +++ src/rust/Cargo.toml | 4 ++-- 7 files changed, 18 insertions(+), 13 deletions(-) diff --git a/.github/workflows/desktop_artifacts.yml b/.github/workflows/desktop_artifacts.yml index 9f1a78c4..c2f41631 100644 --- a/.github/workflows/desktop_artifacts.yml +++ b/.github/workflows/desktop_artifacts.yml @@ -360,6 +360,8 @@ jobs: - run: npm run build working-directory: src/node/ + - run: sudo apt-get update && sudo apt-get install -y libpulse0 + - run: xvfb-run --auto-servernum npm test working-directory: src/node/ diff --git a/Cargo.lock b/Cargo.lock index 991b71bb..2cb730d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -758,18 +758,18 @@ dependencies = [ [[package]] name = "cubeb" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e0a08fb5746d9c1cf8257994151b802bcd9c64f786aa92e009ad8a0d92e43f" +checksum = "4a342fa08004b09f8301c328cd714004c33fe718d311aaf215c372ff52f5ad37" dependencies = [ "cubeb-core", ] [[package]] name = "cubeb-core" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "541f3f1620b78511ecb29821f64de9d4303ed90eb0ae9e83f3fecdd91d94f67b" +checksum = "23b5dfbff806ffe83a09663e65dc9d00edae4da7977ced9dc6b44c2b036e33d3" dependencies = [ "bitflags 1.3.2", "cc", @@ -778,9 +778,9 @@ dependencies = [ [[package]] name = "cubeb-sys" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052b32518d7d175c58d07a57990a5c961219860f71c8ae1b3dbba539ce47defc" +checksum = "d3a1b58889aa85e1df0292cf8eb579b3f669893f4d18967e8f74be3ec4e597da" dependencies = [ "cmake", "pkg-config", diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index 87b946cf..27ae7415 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -1540,9 +1540,9 @@

    Used by:

    ISC License

    Used by:

    Copyright © 2017 Mozilla Foundation
     
    diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md
    index 53a43124..27407d5c 100644
    --- a/acknowledgments/acknowledgments.md
    +++ b/acknowledgments/acknowledgments.md
    @@ -1453,7 +1453,7 @@ THIS SOFTWARE.
     
     ```
     
    -## cubeb-core 0.16.0, cubeb-sys 0.16.0, cubeb 0.16.0
    +## cubeb-core 0.17.0, cubeb-sys 0.17.0, cubeb 0.17.0
     
     ```
     Copyright © 2017 Mozilla Foundation
    diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist
    index 24dd625a..02778200 100644
    --- a/acknowledgments/acknowledgments.plist
    +++ b/acknowledgments/acknowledgments.plist
    @@ -1518,7 +1518,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     			License
     			ISC License
     			Title
    -			cubeb-core 0.16.0, cubeb-sys 0.16.0, cubeb 0.16.0
    +			cubeb-core 0.17.0, cubeb-sys 0.17.0, cubeb 0.17.0
     			Type
     			PSGroupSpecifier
     		
    diff --git a/bin/build-electron b/bin/build-electron
    index b890644d..20543979 100755
    --- a/bin/build-electron
    +++ b/bin/build-electron
    @@ -221,6 +221,9 @@ then
                 exit 1 # unreachable
             fi
     
    +        # Propagate this cross-compilation request to child processes;
    +        # e.g. cubeb rust libs.
    +        export CARGO_BUILD_TARGET="${CARGO_TARGET}"
             RUSTFLAGS="${RUSTFLAGS}" OUTPUT_DIR="${OUTPUT_DIR}" cargo rustc --package ringrtc --target ${CARGO_TARGET} --features electron ${INCLUDE_RELEASE_FLAG:+"--release"} --crate-type cdylib
     
             mkdir -p src/node/build/${DEFAULT_PLATFORM}
    diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
    index b63c2bfc..c5121749 100644
    --- a/src/rust/Cargo.toml
    +++ b/src/rust/Cargo.toml
    @@ -95,8 +95,8 @@ call_protobuf = { path = "../../protobuf", package = "protobuf"}
     mrp = { path = "../../mrp" }
     
     # Optional, needed by "native" feature
    -cubeb = {  version = "0.16.0", optional = true }
    -cubeb-core = {  version = "0.16.0", optional = true }
    +cubeb = {  version = "0.17.0", optional = true }
    +cubeb-core = {  version = "0.17.0", optional = true }
     
     [target.'cfg(windows)'.dependencies]
     # Only needed by native feature on windows
    
    From e51e145ff3fa532e904c8297e761df4116371677 Mon Sep 17 00:00:00 2001
    From: Miriam Zimmerman 
    Date: Tue, 5 Nov 2024 12:12:35 -0500
    Subject: [PATCH 08/29] Bump version to v2.48.6.
    
    ---
     CHANGELOG.md                                | 6 ++++++
     Cargo.lock                                  | 6 +++---
     Cargo.toml                                  | 2 +-
     SignalRingRTC.podspec                       | 2 +-
     acknowledgments/acknowledgments.html        | 6 +++---
     acknowledgments/acknowledgments.md          | 2 +-
     acknowledgments/acknowledgments.plist       | 2 +-
     call_sim/docker/signaling_server/Cargo.lock | 2 +-
     config/version.properties                   | 2 +-
     src/node/package-lock.json                  | 4 ++--
     src/node/package.json                       | 2 +-
     11 files changed, 21 insertions(+), 15 deletions(-)
    
    diff --git a/CHANGELOG.md b/CHANGELOG.md
    index 6a87c1d5..c958f201 100644
    --- a/CHANGELOG.md
    +++ b/CHANGELOG.md
    @@ -1,5 +1,11 @@
     # Changelog
     
    +## v2.48.6
    +
    +- Desktop:
    +  - Don't show `Monitor of` devices as inputs to match existing ADM behavior
    +  - Uprev cubeb to 0.17.0 to fix cross-compilation
    +
     ## v2.48.5
     
     - Desktop: Improve new Audio Device Module support on mac and linux
    diff --git a/Cargo.lock b/Cargo.lock
    index 2cb730d3..4a0484de 100644
    --- a/Cargo.lock
    +++ b/Cargo.lock
    @@ -1703,7 +1703,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
     
     [[package]]
     name = "mrp"
    -version = "2.48.5"
    +version = "2.48.6"
     dependencies = [
      "anyhow",
      "log",
    @@ -2117,7 +2117,7 @@ dependencies = [
     
     [[package]]
     name = "protobuf"
    -version = "2.48.5"
    +version = "2.48.6"
     dependencies = [
      "prost-build",
      "tonic-build",
    @@ -2261,7 +2261,7 @@ dependencies = [
     
     [[package]]
     name = "ringrtc"
    -version = "2.48.5"
    +version = "2.48.6"
     dependencies = [
      "aes",
      "aes-gcm-siv",
    diff --git a/Cargo.toml b/Cargo.toml
    index 0eaa48a9..01cd7250 100644
    --- a/Cargo.toml
    +++ b/Cargo.toml
    @@ -13,7 +13,7 @@ members = [
     ]
     
     [workspace.package]
    -version = "2.48.5"
    +version = "2.48.6"
     authors = ["Calling Team "]
     
     [patch.crates-io]
    diff --git a/SignalRingRTC.podspec b/SignalRingRTC.podspec
    index 07f01d30..edf8febf 100644
    --- a/SignalRingRTC.podspec
    +++ b/SignalRingRTC.podspec
    @@ -8,7 +8,7 @@
     
     Pod::Spec.new do |s|
       s.name             = "SignalRingRTC"
    -  s.version          = "2.48.5"
    +  s.version          = "2.48.6"
       s.summary          = "A Swift & Objective-C library used by the Signal iOS app for WebRTC interactions."
     
       s.description      = <<-DESC
    diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html
    index 27ae7415..0bd8c73b 100644
    --- a/acknowledgments/acknowledgments.html
    +++ b/acknowledgments/acknowledgments.html
    @@ -733,9 +733,9 @@ 

    GNU Affero General Public License v3.0 (synthesized)

    Used by:

    diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md index 27407d5c..570b7973 100644 --- a/acknowledgments/acknowledgments.md +++ b/acknowledgments/acknowledgments.md @@ -669,7 +669,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ``` -## libsignal-core 0.1.0, mrp 2.48.5, protobuf 2.48.5, ringrtc 2.48.5, regex-aot 0.1.0, partial-default-derive 0.1.0 +## libsignal-core 0.1.0, mrp 2.48.6, protobuf 2.48.6, ringrtc 2.48.6, regex-aot 0.1.0, partial-default-derive 0.1.0 ``` GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist index 02778200..a3ddbafd 100644 --- a/acknowledgments/acknowledgments.plist +++ b/acknowledgments/acknowledgments.plist @@ -924,7 +924,7 @@ You should also get your employer (if you work as a programmer) or school, if an License GNU Affero General Public License v3.0 Title - libsignal-core 0.1.0, mrp 2.48.5, protobuf 2.48.5, ringrtc 2.48.5, regex-aot 0.1.0, partial-default-derive 0.1.0 + libsignal-core 0.1.0, mrp 2.48.6, protobuf 2.48.6, ringrtc 2.48.6, regex-aot 0.1.0, partial-default-derive 0.1.0 Type PSGroupSpecifier diff --git a/call_sim/docker/signaling_server/Cargo.lock b/call_sim/docker/signaling_server/Cargo.lock index fd4598bc..d657f58b 100644 --- a/call_sim/docker/signaling_server/Cargo.lock +++ b/call_sim/docker/signaling_server/Cargo.lock @@ -761,7 +761,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.48.5" +version = "2.48.6" dependencies = [ "prost-build", "tonic-build", diff --git a/config/version.properties b/config/version.properties index 960cdaad..35080463 100644 --- a/config/version.properties +++ b/config/version.properties @@ -2,4 +2,4 @@ webrtc.version=6723a ringrtc.version.major=2 ringrtc.version.minor=48 -ringrtc.version.revision=5 +ringrtc.version.revision=6 diff --git a/src/node/package-lock.json b/src/node/package-lock.json index 46b2ff52..c9ebd8a4 100644 --- a/src/node/package-lock.json +++ b/src/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.5", + "version": "2.48.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@signalapp/ringrtc", - "version": "2.48.5", + "version": "2.48.6", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/node/package.json b/src/node/package.json index 74d661ea..a27da46f 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -1,6 +1,6 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.5", + "version": "2.48.6", "description": "Signal Messenger voice and video calling library.", "main": "dist/index.js", "types": "dist/index.d.ts", From a73b5925dee1616267f880284bc0d2a4a968446d Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Thu, 7 Nov 2024 12:44:29 -0500 Subject: [PATCH 09/29] Remove some spammy logs. --- src/rust/src/webrtc/audio_device_module.rs | 5 +---- src/rust/src/webrtc/ffi/audio_device_module.rs | 10 ---------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs index acc4a00b..1ae9e9b5 100644 --- a/src/rust/src/webrtc/audio_device_module.rs +++ b/src/rust/src/webrtc/audio_device_module.rs @@ -992,10 +992,7 @@ impl AudioDeviceModule { } } } - None => { - error!("Cannot get playout delay with no stream"); - -1 - } + None => -1, } } diff --git a/src/rust/src/webrtc/ffi/audio_device_module.rs b/src/rust/src/webrtc/ffi/audio_device_module.rs index 2cb4d8cb..3040cc1a 100644 --- a/src/rust/src/webrtc/ffi/audio_device_module.rs +++ b/src/rust/src/webrtc/ffi/audio_device_module.rs @@ -138,16 +138,6 @@ macro_rules! adm_wrapper { () => {}; ($f:ident($($param:ident: $arg_ty:ty),*) -> $ret:ty ; $($t:tt)*) => { extern "C" fn $f(ptr: webrtc::ptr::Borrowed, $($param: $arg_ty),*) -> $ret { - // Safety: Safe as long as this function is only called from a single thread, which will - // be the case. - static mut LOG_COUNT: i32 = 0; - unsafe { - if LOG_COUNT % 100 == 0 { - info!("{} wrapper", stringify!($f)); - LOG_COUNT = 0; - } - LOG_COUNT += 1; - } if let Some(adm) = unsafe { ptr.as_mut() } { adm.$f($($param),*) } else { From 6bd379fe64376fd975e3772f5b151704ffd1f030 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Mon, 11 Nov 2024 18:44:51 -0500 Subject: [PATCH 10/29] Grab cubeb logs. --- Cargo.lock | 13 ++-- acknowledgments/acknowledgments.html | 6 +- acknowledgments/acknowledgments.md | 2 +- acknowledgments/acknowledgments.plist | 2 +- src/rust/Cargo.toml | 3 +- src/rust/src/webrtc/audio_device_module.rs | 74 ++++++++++++++++++++-- 6 files changed, 83 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a0484de..7c515b63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2201,9 +2201,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.5" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -2223,9 +2223,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -2234,9 +2234,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" @@ -2293,6 +2293,7 @@ dependencies = [ "protobuf", "rand", "rand_chacha", + "regex", "regex-aot", "regex-automata", "rustls", diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index 0bd8c73b..72b69532 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -1804,9 +1804,9 @@

    Used by:

  • bitflags 1.3.2
  • bitflags 2.6.0
  • log 0.4.22
  • -
  • regex-automata 0.4.7
  • -
  • regex-syntax 0.8.4
  • -
  • regex 1.10.5
  • +
  • regex-automata 0.4.8
  • +
  • regex-syntax 0.8.5
  • +
  • regex 1.11.1
Copyright (c) 2014 The Rust Project Developers
 
diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md
index 570b7973..95f5ecc1 100644
--- a/acknowledgments/acknowledgments.md
+++ b/acknowledgments/acknowledgments.md
@@ -1682,7 +1682,7 @@ DEALINGS IN THE SOFTWARE.
 
 ```
 
-## bitflags 1.3.2, bitflags 2.6.0, log 0.4.22, regex-automata 0.4.7, regex-syntax 0.8.4, regex 1.10.5
+## bitflags 1.3.2, bitflags 2.6.0, log 0.4.22, regex-automata 0.4.8, regex-syntax 0.8.5, regex 1.11.1
 
 ```
 Copyright (c) 2014 The Rust Project Developers
diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist
index a3ddbafd..b69155b8 100644
--- a/acknowledgments/acknowledgments.plist
+++ b/acknowledgments/acknowledgments.plist
@@ -1791,7 +1791,7 @@ DEALINGS IN THE SOFTWARE.
 			License
 			MIT License
 			Title
-			bitflags 1.3.2, bitflags 2.6.0, log 0.4.22, regex-automata 0.4.7, regex-syntax 0.8.4, regex 1.10.5
+			bitflags 1.3.2, bitflags 2.6.0, log 0.4.22, regex-automata 0.4.8, regex-syntax 0.8.5, regex 1.11.1
 			Type
 			PSGroupSpecifier
 		
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index c5121749..13b5d3ac 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -97,6 +97,7 @@ mrp = { path = "../../mrp" }
 # Optional, needed by "native" feature
 cubeb = {  version = "0.17.0", optional = true }
 cubeb-core = {  version = "0.17.0", optional = true }
+regex = {  version = "1.11.1", optional = true }
 
 [target.'cfg(windows)'.dependencies]
 # Only needed by native feature on windows
@@ -110,7 +111,7 @@ sysinfo = { version = "0.31.2", default-features = false, features = ["system"]
 default = []
 sim = []
 electron = ["neon", "native"]
-native = ["cubeb", "cubeb-core", "windows"]
+native = ["cubeb", "cubeb-core", "windows", "regex"]
 prebuilt_webrtc = ["native"]
 simnet = ["injectable_network"]
 injectable_network = []
diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs
index 1ae9e9b5..df6e7180 100644
--- a/src/rust/src/webrtc/audio_device_module.rs
+++ b/src/rust/src/webrtc/audio_device_module.rs
@@ -8,11 +8,13 @@ use crate::webrtc::audio_device_module_utils::{copy_and_truncate_string, DeviceC
 use crate::webrtc::ffi::audio_device_module::RffiAudioTransport;
 use anyhow::anyhow;
 use cubeb::{Context, DeviceId, DeviceType, MonoFrame, Stream, StreamPrefs};
-use cubeb_core::InputProcessingParams;
-use std::collections::VecDeque;
-use std::ffi::{c_uchar, c_void, CString};
-use std::sync::{Arc, Mutex};
-use std::time::Duration;
+use cubeb_core::{log_enabled, set_logging, InputProcessingParams, LogLevel};
+use lazy_static::lazy_static;
+use regex::Regex;
+use std::collections::{HashMap, VecDeque};
+use std::ffi::{c_uchar, c_void, CStr, CString};
+use std::sync::{Arc, Mutex, RwLock};
+use std::time::{Duration, Instant};
 #[cfg(target_os = "windows")]
 use windows::Win32::System::Com;
 
@@ -132,6 +134,63 @@ fn write_to_null_or_valid_pointer(ptr: webrtc::ptr::Borrowed, v: T) -> any
     }
 }
 
+fn log_c_str(s: &CStr) {
+    lazy_static! {
+        static ref RE: Regex = Regex::new(r"(?[^\s]+:\d+)(?.*)").unwrap();
+    }
+    lazy_static! {
+        static ref RATE_LIMITER: RwLock> = RwLock::new(HashMap::new());
+    }
+
+    match s.to_str() {
+        Ok(msg) => {
+            if msg.contains("DeviceID") && msg.contains("Name") {
+                // Shortcut:
+                // Suppress spammy lines that log all devices (we already do this)
+                // See `log_device` in cubeb.c
+                return;
+            }
+
+            // Assume valid lines are formatted "file:lineno" and ignore anything
+            // not matching
+            let Some(caps) = RE.captures(msg) else { return };
+            let ident = &caps["ident"];
+            let contents = &caps["message"];
+
+            // Rate limit each line to 1/5 sec.
+            let too_recent = if let Ok(guard) = RATE_LIMITER.read() {
+                guard.get(ident).as_ref().map_or(false, |&i| {
+                    Instant::now().duration_since(*i) < Duration::from_secs(5)
+                })
+            } else {
+                return;
+            };
+
+            if too_recent {
+                return;
+            }
+
+            if let Ok(mut guard) = RATE_LIMITER.write() {
+                guard.insert(ident.to_string(), Instant::now());
+            } else {
+                return;
+            }
+            // To minimize sensitive data in logs, only take first few characters.
+            // This impairs debuggability but at least will give a good pointer
+            // to *which* line in cubeb has a problem, if any.
+            let to_log = if cfg!(debug_assertions) {
+                contents.to_string()
+            } else {
+                contents.chars().take(20).collect::()
+            };
+            info!("cubeb: {}{}...", ident, to_log);
+        }
+        Err(e) => {
+            warn!("cubeb log message not UTF-8: {:?}", e);
+        }
+    }
+}
+
 impl AudioDeviceModule {
     pub fn new() -> Self {
         Self::default()
@@ -160,6 +219,11 @@ impl AudioDeviceModule {
         if self.initialized {
             return 0;
         }
+        if !log_enabled() {
+            if let Err(e) = set_logging(LogLevel::Normal, Some(log_c_str)) {
+                warn!("failed to set cubeb logging: {:?}", e);
+            }
+        }
         #[cfg(target_os = "windows")]
         {
             // Safety: calling with valid parameters.

From 3a3b21efa558024cf4cbeb158c0ade00f90b8c8a Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Tue, 12 Nov 2024 19:24:04 -0500
Subject: [PATCH 11/29] Notify clients for important speech events.

---
 .../api/org/signal/ringrtc/CallManager.java   |  14 +++
 .../api/org/signal/ringrtc/GroupCall.java     |  31 +++++
 .../SignalRingRTC/CallManager.swift           |  14 +++
 .../SignalRingRTC/CallManagerInterface.swift  |  31 ++++-
 .../SignalRingRTC/GroupCall.swift             |  18 +++
 .../TestGroupCallDelegate.swift               |   7 ++
 src/node/index.ts                             |   1 +
 src/node/ringrtc/Service.ts                   |  23 ++++
 src/node/test/RingRTC-test.ts                 |   2 +
 src/rust/src/android/android_platform.rs      |  32 +++++
 src/rust/src/bin/group_call.rs                |   9 +-
 src/rust/src/core/call_manager.rs             |   9 ++
 src/rust/src/core/group_call.rs               | 109 ++++++++++++++++++
 src/rust/src/core/platform.rs                 |   6 +
 src/rust/src/electron.rs                      |   9 ++
 .../src/ios/api/call_manager_interface.rs     |   2 +
 src/rust/src/ios/ios_platform.rs              |  18 ++-
 src/rust/src/native.rs                        |  19 +++
 src/rust/src/sim/sim_platform.rs              |   8 ++
 19 files changed, 355 insertions(+), 7 deletions(-)

diff --git a/src/android/api/org/signal/ringrtc/CallManager.java b/src/android/api/org/signal/ringrtc/CallManager.java
index 2e40d9ff..410b0f80 100644
--- a/src/android/api/org/signal/ringrtc/CallManager.java
+++ b/src/android/api/org/signal/ringrtc/CallManager.java
@@ -1787,6 +1787,20 @@ private void handleEnded(long clientId, GroupCall.GroupCallEndReason reason) {
     groupCall.handleEnded(reason);
   }
 
+  @CalledByNative
+  private void handleSpeakingNotification(long clientId, GroupCall.SpeechEvent event) {
+    Log.i(TAG, "handleSpeakingNotification():");
+
+    GroupCall groupCall = this.groupCallByClientId.get(clientId);
+    if (groupCall == null) {
+      Log.w(TAG, "groupCall not found by clientId: " + clientId);
+      return;
+    }
+
+    groupCall.handleSpeakingNotification(event);
+  }
+
+
   /**
    *
    * Contains parameters for creating Connection objects
diff --git a/src/android/api/org/signal/ringrtc/GroupCall.java b/src/android/api/org/signal/ringrtc/GroupCall.java
index ef4acb89..a86e2938 100644
--- a/src/android/api/org/signal/ringrtc/GroupCall.java
+++ b/src/android/api/org/signal/ringrtc/GroupCall.java
@@ -710,6 +710,18 @@ void handleNetworkRouteChanged(NetworkRoute networkRoute) {
         this.observer.onLocalDeviceStateChanged(this);
     }
 
+    /**
+     *
+     * Callback from RingRTC with details about a speech event.
+     * @param event The speech event.
+     *
+     */
+    void handleSpeakingNotification(SpeechEvent event) {
+        Log.i(TAG, "handleSpeakingNotification():");
+
+        this.observer.onSpeakingNotification(this, event);
+    }
+
     /**
      *
      * Callback from RingRTC with details about audio levels.
@@ -888,6 +900,20 @@ static JoinState fromNativeIndex(int nativeIndex) {
         }
     }
 
+    /**
+     * Enumeration of the type of speech durations we notify the client for.
+     */
+    public enum SpeechEvent {
+        /** The user was speaking, and is not anymore. */
+        STOPPED_SPEAKING,
+        /** The user has been speaking for long enough that they may want to lower their hand. */
+        LOWER_HAND_SUGGESTION;
+
+        @CalledByNative
+        static SpeechEvent fromNativeIndex(int nativeIndex) { return values()[nativeIndex]; }
+    }
+
+
     /**
      * A set of reasons why the group call has ended.
      */
@@ -1204,6 +1230,11 @@ public interface Observer {
          */
         void onLocalDeviceStateChanged(GroupCall groupCall);
 
+        /**
+         * Notification that speech state has changed.
+         */
+        void onSpeakingNotification(GroupCall groupCall, SpeechEvent event);
+
         /**
          * Notification of audio levels.
          */
diff --git a/src/ios/SignalRingRTC/SignalRingRTC/CallManager.swift b/src/ios/SignalRingRTC/SignalRingRTC/CallManager.swift
index 31405f3f..87e8be56 100644
--- a/src/ios/SignalRingRTC/SignalRingRTC/CallManager.swift
+++ b/src/ios/SignalRingRTC/SignalRingRTC/CallManager.swift
@@ -1230,6 +1230,20 @@ public class CallManager: CallManagerInterfac
             groupCall.handleEnded(reason: reason)
         }
     }
+
+    func handleSpeakingNotification(clientId: UInt32, event: SpeechEvent) {
+        Logger.debug("handleSpeakingNotification")
+
+        Task { @MainActor in
+            Logger.debug("handleSpeakingNotification - main.async")
+
+            guard let groupCall = self.groupCallByClientId[clientId] else {
+                return
+            }
+
+            groupCall.handleSpeakingNotification(event: event)
+        }
+    }
 }
 
 @available(iOSApplicationExtension, unavailable)
diff --git a/src/ios/SignalRingRTC/SignalRingRTC/CallManagerInterface.swift b/src/ios/SignalRingRTC/SignalRingRTC/CallManagerInterface.swift
index 7e98f757..8fce65b0 100644
--- a/src/ios/SignalRingRTC/SignalRingRTC/CallManagerInterface.swift
+++ b/src/ios/SignalRingRTC/SignalRingRTC/CallManagerInterface.swift
@@ -42,6 +42,7 @@ protocol CallManagerInterfaceDelegate: AnyObject {
     func handleIncomingVideoTrack(clientId: UInt32, remoteDemuxId: UInt32, nativeVideoTrackBorrowedRc: UnsafeMutableRawPointer?)
     func handlePeekChanged(clientId: UInt32, peekInfo: PeekInfo)
     func handleEnded(clientId: UInt32, reason: GroupCallEndReason)
+    func handleSpeakingNotification(clientId: UInt32, event: SpeechEvent)
 }
 
 @available(iOSApplicationExtension, unavailable)
@@ -99,7 +100,8 @@ class CallManagerInterface {
             handleRemoteDevicesChanged: callManagerInterfaceHandleRemoteDevicesChanged,
             handleIncomingVideoTrack: callManagerInterfaceHandleIncomingVideoTrack,
             handlePeekChanged: callManagerInterfaceHandlePeekChanged,
-            handleEnded: callManagerInterfaceHandleEnded
+            handleEnded: callManagerInterfaceHandleEnded,
+            handleSpeakingNotification: callManagerInterfaceHandleSpeakingNotification
         )
     }
 
@@ -356,6 +358,14 @@ class CallManagerInterface {
 
         delegate.handleEnded(clientId: clientId, reason: reason)
     }
+
+    func handleSpeakingNotification(clientId: UInt32, event: SpeechEvent) {
+        guard let delegate = self.callManagerObserverDelegate else {
+            return
+        }
+
+        delegate.handleSpeakingNotification(clientId: clientId, event: event)
+    }
 }
 
 @available(iOSApplicationExtension, unavailable)
@@ -1110,3 +1120,22 @@ func callManagerInterfaceHandleEnded(object: UnsafeMutableRawPointer?, clientId:
 
     obj.handleEnded(clientId: clientId, reason: _reason)
 }
+
+@available(iOSApplicationExtension, unavailable)
+func callManagerInterfaceHandleSpeakingNotification(object: UnsafeMutableRawPointer?, clientId: UInt32, event: Int32) {
+    guard let object = object else {
+        failDebug("object was unexpectedly nil")
+        return
+    }
+    let obj: CallManagerInterface = Unmanaged.fromOpaque(object).takeUnretainedValue()
+
+    let _event: SpeechEvent
+    if let validEvent = SpeechEvent(rawValue: event) {
+        _event = validEvent
+    } else {
+        failDebug("unexpected speech event")
+        return
+    }
+
+    obj.handleSpeakingNotification(clientId: clientId, event: _event)
+}
diff --git a/src/ios/SignalRingRTC/SignalRingRTC/GroupCall.swift b/src/ios/SignalRingRTC/SignalRingRTC/GroupCall.swift
index 6c0e79a3..53676782 100644
--- a/src/ios/SignalRingRTC/SignalRingRTC/GroupCall.swift
+++ b/src/ios/SignalRingRTC/SignalRingRTC/GroupCall.swift
@@ -48,6 +48,13 @@ public enum GroupCallEndReason: Int32 {
     case hasMaxDevices
 }
 
+/// The inferred state of user speech (e.g. to suggest lowering hand)
+@available(iOSApplicationExtension, unavailable)
+public enum SpeechEvent: Int32 {
+    case StoppedSpeaking = 0
+    case LowerHandSuggestion
+}
+
 /// The local device state for a group call.
 @available(iOSApplicationExtension, unavailable)
 public class LocalDeviceState {
@@ -244,6 +251,12 @@ public protocol GroupCallDelegate: AnyObject {
      */
     @MainActor
     func groupCall(onEnded groupCall: GroupCall, reason: GroupCallEndReason)
+
+    /**
+     * Indication that the user may have been speaking for a certain amount of time -- or stopped speaking.
+     */
+    @MainActor
+    func groupCall(onSpeakingNotification groupCall: GroupCall, event: SpeechEvent)
 }
 
 @available(iOSApplicationExtension, unavailable)
@@ -859,4 +872,9 @@ public class GroupCall {
 
         self.delegate?.groupCall(onEnded: self, reason: reason)
     }
+
+    @MainActor
+    func handleSpeakingNotification(event: SpeechEvent) {
+        self.delegate?.groupCall(onSpeakingNotification: self, event: event)
+    }
 }
diff --git a/src/ios/SignalRingRTC/SignalRingRTCTests/TestGroupCallDelegate.swift b/src/ios/SignalRingRTC/SignalRingRTCTests/TestGroupCallDelegate.swift
index f2cf2bb7..5d88af14 100644
--- a/src/ios/SignalRingRTC/SignalRingRTCTests/TestGroupCallDelegate.swift
+++ b/src/ios/SignalRingRTC/SignalRingRTCTests/TestGroupCallDelegate.swift
@@ -17,7 +17,9 @@ class TestGroupCallDelegate: GroupCallDelegate {
     var onRaisedHandsCount = 0
     var onPeekChangedCount = 0
     var onEndedCount = 0
+    var onSpeakingCount = 0
     var lastOnEndedReason: GroupCallEndReason? = nil
+    var lastOnSpeakingEvent: SpeechEvent? = nil
 
     func groupCall(requestMembershipProof groupCall: GroupCall) {
         requestMembershipProofCount += 1
@@ -59,4 +61,9 @@ class TestGroupCallDelegate: GroupCallDelegate {
         onEndedCount += 1
         lastOnEndedReason = reason
     }
+
+    func groupCall(onSpeakingNotification groupCall: GroupCall, event: SpeechEvent) {
+        onSpeakingCount += 1
+        lastOnSpeakingEvent = event
+    }
 }
diff --git a/src/node/index.ts b/src/node/index.ts
index a38ff65d..a60268a0 100644
--- a/src/node/index.ts
+++ b/src/node/index.ts
@@ -43,6 +43,7 @@ export {
   RingCancelReason,
   RingRTCType,
   RingUpdate,
+  SpeechEvent,
   UserId,
   VideoCapturer,
   VideoRenderer,
diff --git a/src/node/ringrtc/Service.ts b/src/node/ringrtc/Service.ts
index 22ee5beb..268f480d 100644
--- a/src/node/ringrtc/Service.ts
+++ b/src/node/ringrtc/Service.ts
@@ -1527,6 +1527,16 @@ export class RingRTCType {
     }
   }
 
+  // Called by Rust
+  handleSpeechEvent(clientId: GroupCallClientId, event: SpeechEvent): void {
+    sillyDeadlockProtection(() => {
+      const groupCall = this._groupCallByClientId.get(clientId);
+      if (groupCall) {
+        groupCall.handleSpeechEvent(event);
+      }
+    });
+  }
+
   // Called by Rust
   onLogMessage(
     level: number,
@@ -2246,6 +2256,14 @@ export enum GroupCallEndReason {
   HasMaxDevices,
 }
 
+// Matches SpeechEvent in rust.
+export enum SpeechEvent {
+  // User was speaking but stopped.
+  StoppedSpeaking = 0,
+  // User has been speaking for a while -- maybe lower hand?
+  LowerHandSuggestion,
+}
+
 export enum CallMessageUrgency {
   Droppable = 0,
   HandleImmediately,
@@ -2383,6 +2401,7 @@ export interface GroupCallObserver {
   onRaisedHands(groupCall: GroupCall, raisedHands: Array): void;
   onPeekChanged(groupCall: GroupCall): void;
   onEnded(groupCall: GroupCall, reason: GroupCallEndReason): void;
+  onSpeechEvent(groupCall: GroupCall, event: SpeechEvent): void;
 }
 
 export class GroupCall {
@@ -2698,6 +2717,10 @@ export class GroupCall {
   setRtcStatsInterval(intervalMillis: number): void {
     this._callManager.setRtcStatsInterval(this._clientId, intervalMillis);
   }
+
+  handleSpeechEvent(event: SpeechEvent): void {
+    this._observer.onSpeechEvent(this, event);
+  }
 }
 
 // Implements VideoSource for use in CanvasVideoRenderer
diff --git a/src/node/test/RingRTC-test.ts b/src/node/test/RingRTC-test.ts
index 2b5d31d9..b1e40027 100644
--- a/src/node/test/RingRTC-test.ts
+++ b/src/node/test/RingRTC-test.ts
@@ -22,6 +22,7 @@ import {
   OfferType,
   PeekStatusCodes,
   Reaction,
+  SpeechEvent,
   RingRTC,
   callIdFromEra,
   callIdFromRingId,
@@ -907,6 +908,7 @@ describe('RingRTC', () => {
       onRaisedHands(_call: GroupCall, _raisedHands: Array) {}
       onPeekChanged(_call: GroupCall) {}
       onEnded(_call: GroupCall, _reason: GroupCallEndReason) {}
+      onSpeechEvent(_call: GroupCall, _event: SpeechEvent) {}
       /* eslint-enable @typescript-eslint/no-empty-function */
     }
 
diff --git a/src/rust/src/android/android_platform.rs b/src/rust/src/android/android_platform.rs
index 314a7360..fa85627d 100644
--- a/src/rust/src/android/android_platform.rs
+++ b/src/rust/src/android/android_platform.rs
@@ -1016,6 +1016,38 @@ impl Platform for AndroidPlatform {
         }
     }
 
+    fn handle_speaking_notification(
+        &self,
+        client_id: group_call::ClientId,
+        event: group_call::SpeechEvent,
+    ) {
+        info!(
+            "handle_speaking_notification(): client_id {}, event: {:?}",
+            client_id, event
+        );
+
+        if let Ok(env) = &mut self.java_env() {
+            let jni_speech_event =
+                match self.java_enum(env, GROUP_CALL_CLASS, "SpeechEvent", event.ordinal()) {
+                    Ok(v) => AutoLocal::new(v, env),
+                    Err(error) => {
+                        error!("{:?}", error);
+                        return;
+                    }
+                };
+
+            let _ = jni_call_method(
+                env,
+                self.jni_call_manager.as_obj(),
+                "handleSpeakingNotification",
+                jni_args!((
+                    client_id as jlong => long,
+                    jni_speech_event => org.signal.ringrtc.GroupCall::SpeechEvent
+                ) -> void),
+            );
+        }
+    }
+
     fn handle_audio_levels(
         &self,
         client_id: group_call::ClientId,
diff --git a/src/rust/src/bin/group_call.rs b/src/rust/src/bin/group_call.rs
index 567ff246..561b0fde 100644
--- a/src/rust/src/bin/group_call.rs
+++ b/src/rust/src/bin/group_call.rs
@@ -5,12 +5,11 @@
 
 use ringrtc::lite::http::sim as sim_http;
 
+use log::info;
 use std::collections::{HashMap, HashSet};
 use std::sync::{Arc, Mutex};
 
-use log::info;
-
-use ringrtc::core::group_call::Reaction;
+use ringrtc::core::group_call::{Reaction, SpeechEvent};
 use ringrtc::{
     common::units::DataRate,
     core::{
@@ -122,6 +121,10 @@ impl group_call::Observer for Observer {
         // ignore
     }
 
+    fn handle_speaking_notification(&mut self, _client_id: ClientId, event: SpeechEvent) {
+        info!("Speaking {:?}", event);
+    }
+
     fn handle_audio_levels(
         &self,
         _client_id: group_call::ClientId,
diff --git a/src/rust/src/core/call_manager.rs b/src/rust/src/core/call_manager.rs
index e8f3b18a..f3f1b0f5 100644
--- a/src/rust/src/core/call_manager.rs
+++ b/src/rust/src/core/call_manager.rs
@@ -2607,6 +2607,15 @@ where
         );
     }
 
+    fn handle_speaking_notification(
+        &mut self,
+        client_id: group_call::ClientId,
+        event: group_call::SpeechEvent,
+    ) {
+        info!("handle_speaking_notification():");
+        platform_handler!(self, handle_speaking_notification, client_id, event);
+    }
+
     fn handle_audio_levels(
         &self,
         client_id: group_call::ClientId,
diff --git a/src/rust/src/core/group_call.rs b/src/rust/src/core/group_call.rs
index 9ad49a71..964d7264 100644
--- a/src/rust/src/core/group_call.rs
+++ b/src/rust/src/core/group_call.rs
@@ -186,6 +186,39 @@ impl SrtpKeys {
 
 pub const INVALID_CLIENT_ID: ClientId = 0;
 
+// The minimum level of sound to detect as "likely speaking" if we get consistently above this level
+// for a minimum amount of time.
+// AudioLevel can go up to ~32k, and even quiet sounds (e.g. a mouse click) can empirically cause
+// audio levels up to ~400.
+// In an unscientific test, even soft speaking with a distant microphone easily gets levels of 2000.
+// So, use 1000 as a cutoff for "silence".
+const MIN_NON_SILENT_LEVEL: AudioLevel = 1000;
+// How often to poll for speaking/silence.
+const SPEAKING_POLL_INTERVAL: Duration = Duration::from_millis(200);
+// The amount of time with audio at or below `MIN_NON_SILENT_LEVEL` before we consider the
+// user as having stopped speaking, rather than pausing.
+// This should be less than MIN_SPEAKING_HAND_LOWER, or it won't be effective.
+const STOPPED_SPEAKING_DURATION: Duration = Duration::from_secs(3);
+// Amount of "continuous" speech (i.e., with gaps no longer than `STOPPED_SPEAKING_DURATION`)
+// after which we suggest lowering a raised hand.
+const MIN_SPEAKING_HAND_LOWER: Duration = Duration::from_secs(10);
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum SpeechEvent {
+    StoppedSpeaking = 0,
+    LowerHandSuggestion,
+}
+
+impl SpeechEvent {
+    pub fn ordinal(&self) -> i32 {
+        // Must be kept in sync with the Java, Swift, and TypeScript enums.
+        match self {
+            SpeechEvent::StoppedSpeaking => 0,
+            SpeechEvent::LowerHandSuggestion => 1,
+        }
+    }
+}
+
 #[derive(Debug)]
 pub enum RemoteDevicesChangedReason {
     DemuxIdsChanged,
@@ -263,6 +296,8 @@ pub trait Observer {
         incoming_video_track: VideoTrack,
     );
 
+    fn handle_speaking_notification(&mut self, client_id: ClientId, speech_event: SpeechEvent);
+
     fn handle_audio_levels(
         &self,
         client_id: ClientId,
@@ -1001,6 +1036,16 @@ struct State {
     // Things for getting audio levels from the PeerConnection
     audio_levels_interval: Option,
     next_audio_levels_time: Option,
+    // Variables to track the start of the current utterance, and how frequently
+    // to poll for "is the user speaking?"
+    speaking_interval: Duration,
+    next_speaking_audio_levels_time: Option,
+    // Track the time the current speech began, if the user is not silent.
+    started_speaking: Option,
+    // Track the time the current silence started, if the user is not speaking.
+    silence_started: Option,
+    // Tracker for the last time speech-related notification sent to the client.
+    last_speaking_notification: Option,
 
     next_membership_proof_request_time: Option,
 
@@ -1257,6 +1302,12 @@ impl Client {
                     audio_levels_interval,
                     next_audio_levels_time: None,
 
+                    speaking_interval: SPEAKING_POLL_INTERVAL,
+                    next_speaking_audio_levels_time: None,
+                    started_speaking: None,
+                    silence_started: None,
+                    last_speaking_notification: None,
+
                     next_membership_proof_request_time: None,
 
                     next_raise_hand_time: None,
@@ -1371,6 +1422,48 @@ impl Client {
             }
         }
 
+        if let Some(next_speaking_audio_levels_time) = state.next_speaking_audio_levels_time {
+            if now >= next_speaking_audio_levels_time {
+                let (captured_level, _) = state.peer_connection.get_audio_levels();
+                state.started_speaking = if captured_level > MIN_NON_SILENT_LEVEL
+                    && !state.outgoing_heartbeat_state.audio_muted.unwrap_or(true)
+                {
+                    state.silence_started = None;
+                    state.started_speaking.or(Some(now))
+                } else {
+                    state.silence_started = state.silence_started.or(Some(now));
+                    let time_silent = state
+                        .silence_started
+                        .map_or(Duration::from_secs(0), |start| now.duration_since(start));
+                    if time_silent >= STOPPED_SPEAKING_DURATION {
+                        None
+                    } else {
+                        state.started_speaking
+                    }
+                };
+
+                let time_speaking = now.duration_since(state.silence_started.unwrap_or(now));
+
+                let event = if time_speaking > MIN_SPEAKING_HAND_LOWER {
+                    Some(SpeechEvent::LowerHandSuggestion)
+                } else if time_speaking.is_zero() && state.last_speaking_notification.is_some() {
+                    Some(SpeechEvent::StoppedSpeaking)
+                } else {
+                    None
+                };
+                if state.last_speaking_notification != event {
+                    if let Some(event) = event {
+                        state
+                            .observer
+                            .handle_speaking_notification(state.client_id, event);
+                        state.last_speaking_notification = Some(event);
+                    }
+                }
+
+                state.next_speaking_audio_levels_time = Some(now + state.speaking_interval);
+            }
+        }
+
         if let (Some(audio_levels_interval), Some(next_audio_levels_time)) =
             (state.audio_levels_interval, state.next_audio_levels_time)
         {
@@ -1594,6 +1687,7 @@ impl Client {
                     // Start heartbeats, audio levels, and raise hand right away.
                     state.next_heartbeat_time = Some(now);
                     state.next_audio_levels_time = Some(now);
+                    state.next_speaking_audio_levels_time = Some(now);
                     state.next_raise_hand_time = Some(now);
 
                     // Request group membership refresh as we start polling the participant list.
@@ -1787,6 +1881,7 @@ impl Client {
         state.next_heartbeat_time = None;
         state.next_stats_time = None;
         state.next_audio_levels_time = None;
+        state.next_speaking_audio_levels_time = None;
         state.next_membership_proof_request_time = None;
     }
 
@@ -4773,6 +4868,7 @@ mod tests {
         request_group_members_invocation_count: Arc,
         handle_remote_devices_changed_invocation_count: Arc,
         handle_audio_levels_invocation_count: Arc,
+        handle_speaking_notification_invocation_count: Arc,
         handle_reactions_invocation_count: Arc,
         reactions_count: Arc,
         send_signaling_message_invocation_count: Arc,
@@ -4814,6 +4910,7 @@ mod tests {
                 request_group_members_invocation_count: Default::default(),
                 handle_remote_devices_changed_invocation_count: Default::default(),
                 handle_audio_levels_invocation_count: Default::default(),
+                handle_speaking_notification_invocation_count: Default::default(),
                 handle_reactions_invocation_count: Default::default(),
                 reactions_count: Default::default(),
                 send_signaling_message_invocation_count: Default::default(),
@@ -4906,6 +5003,13 @@ mod tests {
                 .swap(0, Ordering::Relaxed)
         }
 
+        /// Gets the number of `speaking_notification` since last checked.
+        #[allow(unused)]
+        fn handle_speaking_notification_count(&self) -> u64 {
+            self.handle_speaking_notification_invocation_count
+                .swap(0, Ordering::Relaxed)
+        }
+
         fn handle_reactions_invocation_count(&self) -> u64 {
             self.handle_reactions_invocation_count
                 .swap(0, Ordering::Relaxed)
@@ -4981,6 +5085,11 @@ mod tests {
             self.remote_devices_changed.set();
         }
 
+        fn handle_speaking_notification(&mut self, _client_id: ClientId, _event: SpeechEvent) {
+            self.handle_speaking_notification_invocation_count
+                .fetch_add(1, Ordering::Relaxed);
+        }
+
         fn handle_audio_levels(
             &self,
             _client_id: ClientId,
diff --git a/src/rust/src/core/platform.rs b/src/rust/src/core/platform.rs
index d02e841a..d0235ac4 100644
--- a/src/rust/src/core/platform.rs
+++ b/src/rust/src/core/platform.rs
@@ -253,6 +253,12 @@ pub trait Platform: sfu::Delegate + fmt::Debug + fmt::Display + Send + Sized + '
         joined_members: &HashSet,
     );
 
+    fn handle_speaking_notification(
+        &self,
+        client_id: group_call::ClientId,
+        event: group_call::SpeechEvent,
+    );
+
     fn handle_audio_levels(
         &self,
         _client_id: group_call::ClientId,
diff --git a/src/rust/src/electron.rs b/src/rust/src/electron.rs
index db027c05..ee85aa74 100644
--- a/src/rust/src/electron.rs
+++ b/src/rust/src/electron.rs
@@ -2853,6 +2853,15 @@ fn processEvents(mut cx: FunctionContext) -> JsResult {
                 let method = observer.get::(&mut cx, method_name)?;
                 method.call(&mut cx, observer, args)?;
             }
+            Event::GroupUpdate(GroupUpdate::SpeechEvent(client_id, event)) => {
+                let method_name = "handleSpeechEvent";
+                let args = [
+                    cx.number(client_id).upcast(),
+                    cx.number(event as i32).upcast(),
+                ];
+                let method = observer.get::(&mut cx, method_name)?;
+                method.call(&mut cx, observer, args)?;
+            }
         }
     }
     Ok(cx.undefined().upcast())
diff --git a/src/rust/src/ios/api/call_manager_interface.rs b/src/rust/src/ios/api/call_manager_interface.rs
index 8e19c5b7..fcddd35d 100644
--- a/src/rust/src/ios/api/call_manager_interface.rs
+++ b/src/rust/src/ios/api/call_manager_interface.rs
@@ -501,6 +501,8 @@ pub struct AppInterface {
     ),
     pub handleEnded:
         extern "C" fn(object: *mut c_void, clientId: group_call::ClientId, reason: i32),
+    pub handleSpeakingNotification:
+        extern "C" fn(object: *mut c_void, clientId: group_call::ClientId, event: i32),
 }
 
 // Add an empty Send trait to allow transfer of ownership between threads.
diff --git a/src/rust/src/ios/ios_platform.rs b/src/rust/src/ios/ios_platform.rs
index 03b71b49..a55712f6 100644
--- a/src/rust/src/ios/ios_platform.rs
+++ b/src/rust/src/ios/ios_platform.rs
@@ -15,7 +15,7 @@ use crate::common::{
 };
 use crate::core::call::Call;
 use crate::core::connection::{Connection, ConnectionType};
-use crate::core::group_call::{ClientId, Reaction};
+use crate::core::group_call::Reaction;
 use crate::core::platform::{Platform, PlatformItem};
 use crate::core::{group_call, signaling};
 use crate::ios::api::call_manager_interface::{
@@ -617,7 +617,7 @@ impl Platform for IosPlatform {
         );
     }
 
-    fn handle_reactions(&self, client_id: ClientId, reactions: Vec) {
+    fn handle_reactions(&self, client_id: group_call::ClientId, reactions: Vec) {
         trace!("handle_reactions(): {:?}", reactions);
 
         let app_reactions: Vec = reactions
@@ -697,7 +697,7 @@ impl Platform for IosPlatform {
         );
     }
 
-    fn handle_raised_hands(&self, client_id: ClientId, raised_hands: Vec) {
+    fn handle_raised_hands(&self, client_id: group_call::ClientId, raised_hands: Vec) {
         info!("handle_raised_hands(): {:?}", raised_hands);
 
         let app_raised_hands_array = AppRaisedHandsArray {
@@ -778,6 +778,18 @@ impl Platform for IosPlatform {
     fn handle_ended(&self, client_id: group_call::ClientId, reason: group_call::EndReason) {
         (self.app_interface.handleEnded)(self.app_interface.object, client_id, reason as i32);
     }
+
+    fn handle_speaking_notification(
+        &self,
+        client_id: group_call::ClientId,
+        event: group_call::SpeechEvent,
+    ) {
+        (self.app_interface.handleSpeakingNotification)(
+            self.app_interface.object,
+            client_id,
+            event as i32,
+        );
+    }
 }
 
 impl sfu::Delegate for IosPlatform {
diff --git a/src/rust/src/native.rs b/src/rust/src/native.rs
index 68651901..853aa5f0 100644
--- a/src/rust/src/native.rs
+++ b/src/rust/src/native.rs
@@ -256,6 +256,7 @@ pub enum GroupUpdate {
     RtcStatsReportComplete {
         report_json: String,
     },
+    SpeechEvent(group_call::ClientId, group_call::SpeechEvent),
 }
 
 impl fmt::Display for GroupUpdate {
@@ -286,6 +287,9 @@ impl fmt::Display for GroupUpdate {
                 format!("RaisedHands({:?})", raised_hands)
             }
             GroupUpdate::RtcStatsReportComplete { .. } => "RtcStatsReportComplete".to_string(),
+            GroupUpdate::SpeechEvent(_, event) => {
+                format!("SpeechEvent({:?}", event)
+            }
         };
         write!(f, "({})", display)
     }
@@ -885,6 +889,21 @@ impl Platform for NativePlatform {
         }
     }
 
+    fn handle_speaking_notification(
+        &self,
+        client_id: group_call::ClientId,
+        event: group_call::SpeechEvent,
+    ) {
+        info!(
+            "NativePlatform::handle_speaking_notification(): {:?}",
+            event
+        );
+        let result = self.send_group_update(GroupUpdate::SpeechEvent(client_id, event));
+        if result.is_err() {
+            error!("{:?}", result.err());
+        }
+    }
+
     fn handle_audio_levels(
         &self,
         client_id: group_call::ClientId,
diff --git a/src/rust/src/sim/sim_platform.rs b/src/rust/src/sim/sim_platform.rs
index a2b4ba9f..2cb290be 100644
--- a/src/rust/src/sim/sim_platform.rs
+++ b/src/rust/src/sim/sim_platform.rs
@@ -529,6 +529,14 @@ impl Platform for SimPlatform {
         info!("handle_network_route_changed(): {:?}", network_route);
     }
 
+    fn handle_speaking_notification(
+        &self,
+        _client_id: group_call::ClientId,
+        event: group_call::SpeechEvent,
+    ) {
+        info!("handle_speaking_notification(): {:?}", event,);
+    }
+
     fn handle_audio_levels(
         &self,
         _client_id: group_call::ClientId,

From 5b3840b626334daa192950946b9dab5fa61527df Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Fri, 15 Nov 2024 17:46:03 -0500
Subject: [PATCH 12/29] Use a dedicated runner to build linux arm.

---
 .github/workflows/desktop_artifacts.yml | 69 ++++++++++++++++++++-----
 1 file changed, 57 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/desktop_artifacts.yml b/.github/workflows/desktop_artifacts.yml
index c2f41631..5ab9bf18 100644
--- a/.github/workflows/desktop_artifacts.yml
+++ b/.github/workflows/desktop_artifacts.yml
@@ -17,6 +17,13 @@ on:
         options:
           - 'ubuntu-20.04'
           - 'ubuntu-20.04-4-cores'
+      runner_linux_arm64:
+        description: "ARM64 Linux runner:"
+        default: 'ubuntu-24.04-arm64-2-cores'
+        required: true
+        type: choice
+        options:
+          - 'ubuntu-24.04-arm64-2-cores'
       runner_windows:
         description: "Windows runner:"
         default: 'windows-latest'
@@ -46,10 +53,8 @@ jobs:
     steps:
     - uses: actions/checkout@v4
 
-    - run: rustup toolchain install $(cat rust-toolchain) --profile minimal --target aarch64-unknown-linux-gnu
-
     - name: Install dependencies
-      run: sudo apt-get update && sudo apt-get install -y protobuf-compiler crossbuild-essential-arm64 libpulse-dev
+      run: sudo apt-get update && sudo apt-get install -y protobuf-compiler libpulse-dev
 
     - run: cargo install dump_syms
 
@@ -60,9 +65,6 @@ jobs:
     - run: ./bin/fetch-artifact --platform linux-x64 --release
     - run: ./bin/build-electron --ringrtc-only --release
 
-    - run: ./bin/fetch-artifact --platform linux-arm64 --release -o out-arm
-    - run: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc TARGET_ARCH=arm64 OUTPUT_DIR=out-arm ./bin/build-electron --ringrtc-only --release
-
     - name: Upload Desktop Artifacts
       uses: actions/upload-artifact@v4
       with:
@@ -77,20 +79,51 @@ jobs:
         path: out/release/libringrtc-*-linux-x64-debuginfo.sym
         retention-days: 2
 
+    - name: Upload WebRTC Acknowledgments
+      uses: actions/upload-artifact@v4
+      with:
+        name: webrtc-acknowledgments-linux
+        path: out/release/LICENSE.md
+
+  build_linux_arm:
+    name: Build Linux ARM64
+
+    runs-on: ${{ inputs.runner_linux_arm64 }}
+
+    steps:
+    - uses: actions/checkout@v4
+
+    - name: Install dependencies
+      run: sudo apt-get update && sudo apt-get install -y protobuf-compiler libpulse-dev
+
+    - run: cargo install dump_syms
+
+    - uses: actions/setup-node@v4
+      with:
+        node-version-file: 'src/node/.nvmrc'
+
+    - run: ./bin/fetch-artifact --platform linux-arm64 --release
+    - run: TARGET_ARCH=arm64 ./bin/build-electron --ringrtc-only --release
+
+    - name: Upload Desktop Artifacts
+      uses: actions/upload-artifact@v4
+      with:
+        name: ringrtc-desktop-linux-arm64
+        path: src/node/build/
+        retention-days: 2
+
     - name: Upload Desktop arm64 Debug Info
       uses: actions/upload-artifact@v4
       with:
         name: ringrtc-desktop-linux-debuginfo-arm64
-        path: out-arm/release/libringrtc-*-linux-arm64-debuginfo.sym
+        path: out/release/libringrtc-*-linux-arm64-debuginfo.sym
         retention-days: 2
 
     - name: Upload WebRTC Acknowledgments
       uses: actions/upload-artifact@v4
       with:
-        name: webrtc-acknowledgments-linux
-        path: |
-          out/release/LICENSE.md
-          out-arm/release/LICENSE.md
+        name: webrtc-acknowledgments-linux-arm64
+        path: out/release/LICENSE.md
 
   build_windows:
     name: Build Windows
@@ -214,7 +247,7 @@ jobs:
 
     runs-on: ubuntu-22.04
 
-    needs: [build_windows, build_mac, build_linux]
+    needs: [build_windows, build_mac, build_linux, build_linux_arm]
 
     steps:
       - uses: actions/checkout@v4
@@ -261,6 +294,12 @@ jobs:
           name: ringrtc-desktop-linux
           path: src/node/build/
 
+      - name: Download Desktop Linux Artifacts ARM64
+        uses: actions/download-artifact@v4
+        with:
+          name: ringrtc-desktop-linux-arm64
+          path: src/node/build/
+
       - name: Download Desktop Linux x64 Symbols
         uses: actions/download-artifact@v4
         with:
@@ -384,6 +423,12 @@ jobs:
           name: webrtc-acknowledgments-linux
           path: acknowledgments/webrtc-linux/
 
+      - name: Download Linux Acknowledgments ARM64
+        uses: actions/download-artifact@v4
+        with:
+          name: webrtc-acknowledgments-linux-arm64
+          path: acknowledgments/webrtc-linux/
+
       - run: cp acknowledgments/acknowledgments.md src/node/dist
       - run: bin/convert_webrtc_acknowledgments.py --format md acknowledgments/webrtc-*/*/*/LICENSE.md >> src/node/dist/acknowledgments.md
 

From 15bc44f0b9b06213f55f1b9d670f09ef29010f1c Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Fri, 15 Nov 2024 18:37:58 -0500
Subject: [PATCH 13/29] Don't count time silent towards time speaking

---
 src/rust/src/core/group_call.rs | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/rust/src/core/group_call.rs b/src/rust/src/core/group_call.rs
index 964d7264..50575133 100644
--- a/src/rust/src/core/group_call.rs
+++ b/src/rust/src/core/group_call.rs
@@ -1425,6 +1425,7 @@ impl Client {
         if let Some(next_speaking_audio_levels_time) = state.next_speaking_audio_levels_time {
             if now >= next_speaking_audio_levels_time {
                 let (captured_level, _) = state.peer_connection.get_audio_levels();
+                let mut time_silent = Duration::from_secs(0);
                 state.started_speaking = if captured_level > MIN_NON_SILENT_LEVEL
                     && !state.outgoing_heartbeat_state.audio_muted.unwrap_or(true)
                 {
@@ -1432,7 +1433,7 @@ impl Client {
                     state.started_speaking.or(Some(now))
                 } else {
                     state.silence_started = state.silence_started.or(Some(now));
-                    let time_silent = state
+                    time_silent = state
                         .silence_started
                         .map_or(Duration::from_secs(0), |start| now.duration_since(start));
                     if time_silent >= STOPPED_SPEAKING_DURATION {
@@ -1442,7 +1443,9 @@ impl Client {
                     }
                 };
 
-                let time_speaking = now.duration_since(state.silence_started.unwrap_or(now));
+                let time_speaking = now
+                    .duration_since(state.started_speaking.unwrap_or(now))
+                    .saturating_sub(time_silent);
 
                 let event = if time_speaking > MIN_SPEAKING_HAND_LOWER {
                     Some(SpeechEvent::LowerHandSuggestion)

From 3954fe2dbb3bb3365bb1e082c7eb4d8d2a47dfb0 Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Fri, 15 Nov 2024 18:42:36 -0500
Subject: [PATCH 14/29] Update version to 2.48.7

---
 CHANGELOG.md                                | 8 ++++++++
 Cargo.lock                                  | 6 +++---
 Cargo.toml                                  | 2 +-
 SignalRingRTC.podspec                       | 2 +-
 acknowledgments/acknowledgments.html        | 6 +++---
 acknowledgments/acknowledgments.md          | 2 +-
 acknowledgments/acknowledgments.plist       | 2 +-
 call_sim/docker/signaling_server/Cargo.lock | 2 +-
 config/version.properties                   | 2 +-
 src/node/package-lock.json                  | 4 ++--
 src/node/package.json                       | 2 +-
 11 files changed, 23 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c958f201..b1d65e20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Changelog
 
+## v2.48.7
+
+- Desktop:
+  - Logging improvements for ringrtc ADM
+  - Use a dedicated runner to build for linux ARM, fixing crash
+
+- Notify clients for important speech events
+
 ## v2.48.6
 
 - Desktop:
diff --git a/Cargo.lock b/Cargo.lock
index 7c515b63..b38f5f8f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1703,7 +1703,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
 
 [[package]]
 name = "mrp"
-version = "2.48.6"
+version = "2.48.7"
 dependencies = [
  "anyhow",
  "log",
@@ -2117,7 +2117,7 @@ dependencies = [
 
 [[package]]
 name = "protobuf"
-version = "2.48.6"
+version = "2.48.7"
 dependencies = [
  "prost-build",
  "tonic-build",
@@ -2261,7 +2261,7 @@ dependencies = [
 
 [[package]]
 name = "ringrtc"
-version = "2.48.6"
+version = "2.48.7"
 dependencies = [
  "aes",
  "aes-gcm-siv",
diff --git a/Cargo.toml b/Cargo.toml
index 01cd7250..e970b972 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ members = [
 ]
 
 [workspace.package]
-version = "2.48.6"
+version = "2.48.7"
 authors = ["Calling Team "]
 
 [patch.crates-io]
diff --git a/SignalRingRTC.podspec b/SignalRingRTC.podspec
index edf8febf..1211b270 100644
--- a/SignalRingRTC.podspec
+++ b/SignalRingRTC.podspec
@@ -8,7 +8,7 @@
 
 Pod::Spec.new do |s|
   s.name             = "SignalRingRTC"
-  s.version          = "2.48.6"
+  s.version          = "2.48.7"
   s.summary          = "A Swift & Objective-C library used by the Signal iOS app for WebRTC interactions."
 
   s.description      = <<-DESC
diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html
index 72b69532..4168ad6b 100644
--- a/acknowledgments/acknowledgments.html
+++ b/acknowledgments/acknowledgments.html
@@ -733,9 +733,9 @@ 

GNU Affero General Public License v3.0 (synthesized)

Used by:

diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md index 95f5ecc1..9d6b2e0c 100644 --- a/acknowledgments/acknowledgments.md +++ b/acknowledgments/acknowledgments.md @@ -669,7 +669,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ``` -## libsignal-core 0.1.0, mrp 2.48.6, protobuf 2.48.6, ringrtc 2.48.6, regex-aot 0.1.0, partial-default-derive 0.1.0 +## libsignal-core 0.1.0, mrp 2.48.7, protobuf 2.48.7, ringrtc 2.48.7, regex-aot 0.1.0, partial-default-derive 0.1.0 ``` GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist index b69155b8..d85fdeae 100644 --- a/acknowledgments/acknowledgments.plist +++ b/acknowledgments/acknowledgments.plist @@ -924,7 +924,7 @@ You should also get your employer (if you work as a programmer) or school, if an License GNU Affero General Public License v3.0 Title - libsignal-core 0.1.0, mrp 2.48.6, protobuf 2.48.6, ringrtc 2.48.6, regex-aot 0.1.0, partial-default-derive 0.1.0 + libsignal-core 0.1.0, mrp 2.48.7, protobuf 2.48.7, ringrtc 2.48.7, regex-aot 0.1.0, partial-default-derive 0.1.0 Type PSGroupSpecifier diff --git a/call_sim/docker/signaling_server/Cargo.lock b/call_sim/docker/signaling_server/Cargo.lock index d657f58b..be56866d 100644 --- a/call_sim/docker/signaling_server/Cargo.lock +++ b/call_sim/docker/signaling_server/Cargo.lock @@ -761,7 +761,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.48.6" +version = "2.48.7" dependencies = [ "prost-build", "tonic-build", diff --git a/config/version.properties b/config/version.properties index 35080463..25fded64 100644 --- a/config/version.properties +++ b/config/version.properties @@ -2,4 +2,4 @@ webrtc.version=6723a ringrtc.version.major=2 ringrtc.version.minor=48 -ringrtc.version.revision=6 +ringrtc.version.revision=7 diff --git a/src/node/package-lock.json b/src/node/package-lock.json index c9ebd8a4..a2c94ffd 100644 --- a/src/node/package-lock.json +++ b/src/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.6", + "version": "2.48.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@signalapp/ringrtc", - "version": "2.48.6", + "version": "2.48.7", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/node/package.json b/src/node/package.json index a27da46f..54777b5b 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -1,6 +1,6 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.6", + "version": "2.48.7", "description": "Signal Messenger voice and video calling library.", "main": "dist/index.js", "types": "dist/index.d.ts", From 89ccd855521c834b365976561c14a77e7d6cd66a Mon Sep 17 00:00:00 2001 From: Rashad Sookram Date: Wed, 20 Nov 2024 11:46:23 -0500 Subject: [PATCH 15/29] Remove support for unencrypted audio header --- src/rust/src/core/crypto.rs | 82 ++------ src/rust/src/core/group_call.rs | 196 ++++++------------ .../src/webrtc/peer_connection_observer.rs | 28 +-- 3 files changed, 77 insertions(+), 229 deletions(-) diff --git a/src/rust/src/core/crypto.rs b/src/rust/src/core/crypto.rs index 491cb63c..b76b13a1 100644 --- a/src/rust/src/core/crypto.rs +++ b/src/rust/src/core/crypto.rs @@ -206,21 +206,14 @@ fn convert_frame_counter_to_iv(frame_counter: FrameCounter) -> Iv { result } -fn check_mac( - state: &ReceiverState, - frame_counter: FrameCounter, - data: &[u8], - associated_data: &[u8], - mac: &Mac, -) -> bool { +fn check_mac(state: &ReceiverState, frame_counter: FrameCounter, data: &[u8], mac: &Mac) -> bool { let iv = convert_frame_counter_to_iv(frame_counter); let mut hmac = HmacSha256::new_from_slice(&state.sender_state.current_hmac_key[..]) .expect("HMAC can take key of any size"); hmac.update(&iv[..]); hmac.update(&len_as_u32_be_bytes(data)[..]); hmac.update(data); - hmac.update(&len_as_u32_be_bytes(associated_data)[..]); - hmac.update(associated_data); + hmac.update(&0_u32.to_be_bytes()); let hmac_result = hmac.finalize().into_bytes(); const_assert!(MAC_SIZE_BYTES <= HMAC_SHA256_SIZE_BYTES); let result = hmac_result[..MAC_SIZE_BYTES].ct_eq(mac); @@ -264,7 +257,6 @@ impl Context { pub fn encrypt( &mut self, data: &mut [u8], - associated_data: &[u8], mac: &mut Mac, ) -> Result<(RatchetCounter, FrameCounter), Error> { let frame_counter = self.next_frame_counter; @@ -278,8 +270,7 @@ impl Context { hmac.update(&iv[..]); hmac.update(&len_as_u32_be_bytes(data)[..]); hmac.update(data); - hmac.update(&len_as_u32_be_bytes(associated_data)[..]); - hmac.update(associated_data); + hmac.update(&0_u32.to_be_bytes()); let hmac_result = hmac.finalize().into_bytes(); const_assert!(MAC_SIZE_BYTES <= HMAC_SHA256_SIZE_BYTES); mac.copy_from_slice(&hmac_result[..MAC_SIZE_BYTES]); @@ -295,7 +286,6 @@ impl Context { ratchet_counter: RatchetCounter, frame_counter: FrameCounter, data: &mut [u8], - associated_data: &[u8], mac: &Mac, ) -> Result<(), Error> { let states = self.get_mut_ref_state_vec_by_id(sender_id); @@ -303,7 +293,7 @@ impl Context { // try all states with matching ratchet counters first for state in states.iter() { if state.sender_state.ratchet_counter == ratchet_counter - && check_mac(state, frame_counter, data, associated_data, mac) + && check_mac(state, frame_counter, data, mac) { decrypt_internal(state, frame_counter, data); return Ok(()); @@ -313,7 +303,7 @@ impl Context { // before giving up, try more expensive repeated ratcheting of each state to match given ratchet counter for state in states.iter_mut() { let mut try_state = state.try_advance_ratchet(ratchet_counter, frame_counter); - if check_mac(&try_state, frame_counter, data, associated_data, mac) { + if check_mac(&try_state, frame_counter, data, mac) { try_state.limit_ooo(); *state = try_state; decrypt_internal(state, frame_counter, data); @@ -407,10 +397,8 @@ mod tests { ctx.add_receive_secret(sender_id, 0, send_secret); let mut data = plaintext.to_vec(); - let associated_data = Vec::from("Can't touch this"); let mut mac = Mac::default(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(0, ratchet_counter); assert_ne!(&plaintext[..], &data[..]); @@ -419,7 +407,6 @@ mod tests { ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); @@ -437,17 +424,14 @@ mod tests { ctx.add_receive_secret(sender_id, 0, send_secret); let mut data = plaintext.to_vec(); - let associated_data = Vec::from("Can't touch this"); let mut mac = Mac::default(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(0, ratchet_counter); ctx.decrypt( sender_id, ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); @@ -458,31 +442,26 @@ mod tests { ctx2.add_receive_secret(sender_id, ratchet_counter2, secret2); let mut data = plaintext.to_vec(); - let associated_data = Vec::from("Can't touch this"); let mut mac = [0u8; MAC_SIZE_BYTES]; - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(1, ratchet_counter); ctx.decrypt( sender_id, ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); let mut data = plaintext.to_vec(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(ratchet_counter2, ratchet_counter); ctx2.decrypt( sender_id, ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); @@ -500,10 +479,8 @@ mod tests { ctx.add_receive_secret(sender_id, 0, send_secret); let mut data = plaintext.to_vec(); - let associated_data = Vec::from("Can't touch this"); let mut mac = Mac::default(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(0, ratchet_counter); assert_eq!(1, frame_counter); ctx.decrypt( @@ -511,7 +488,6 @@ mod tests { ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); @@ -520,10 +496,8 @@ mod tests { ctx.add_receive_secret(sender_id, 0, new_secret); let mut data = plaintext.to_vec(); - let associated_data = Vec::from("Can't touch this"); let mut mac = Mac::default(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(0, ratchet_counter); assert_eq!(2, frame_counter); ctx.decrypt( @@ -531,7 +505,6 @@ mod tests { ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); @@ -540,8 +513,7 @@ mod tests { let mut data = plaintext.to_vec(); let mut mac = Mac::default(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; assert_eq!(0, ratchet_counter); assert_eq!(3, frame_counter); ctx.decrypt( @@ -549,7 +521,6 @@ mod tests { ratchet_counter, frame_counter, &mut data[..], - &associated_data, &mac, )?; assert_eq!(&plaintext[..], &data[..]); @@ -567,10 +538,8 @@ mod tests { ctx.add_receive_secret(sender_id, 0, send_secret); let mut data = plaintext.to_vec(); - let mut associated_data = Vec::from("Can't touch this"); let mut mac = Mac::default(); - let (ratchet_counter, frame_counter) = - ctx.encrypt(&mut data[..], &associated_data[..], &mut mac)?; + let (ratchet_counter, frame_counter) = ctx.encrypt(&mut data[..], &mut mac)?; mac[0] = mac[0].wrapping_add(1); let err = ctx @@ -579,7 +548,6 @@ mod tests { ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, ) .expect_err("decrypt should have returned an error"); @@ -591,24 +559,10 @@ mod tests { ratchet_counter, frame_counter, &mut data[..], - &associated_data[..], &mac, )?; assert_eq!(&plaintext[..], &data[..]); - associated_data[0] = associated_data[0].wrapping_add(1); - let err = ctx - .decrypt( - sender_id, - ratchet_counter, - frame_counter, - &mut data[..], - &associated_data[..], - &mac, - ) - .expect_err("decrypt should have returned an error"); - assert_eq!(err, Error::NoMatchingReceiverState); - Ok(()) } @@ -636,10 +590,8 @@ mod tests { ctx.add_receive_secret(sender_id, 0, send_secret); let mut data1 = plaintext.to_vec(); - let associated_data1 = Vec::from("Can't touch this"); let mut mac1 = Mac::default(); - let (ratchet_counter1, frame_counter1) = - ctx.encrypt(&mut data1[..], &associated_data1[..], &mut mac1)?; + let (ratchet_counter1, frame_counter1) = ctx.encrypt(&mut data1[..], &mut mac1)?; assert_eq!(0, ratchet_counter1); let (ratchet_counter2, secret2) = ctx.advance_send_ratchet(); @@ -648,17 +600,14 @@ mod tests { ctx2.add_receive_secret(sender_id, ratchet_counter2, secret2); let mut data2 = plaintext.to_vec(); - let associated_data2 = Vec::from("Can't touch this"); let mut mac2 = [0u8; MAC_SIZE_BYTES]; - let (ratchet_counter2, frame_counter2) = - ctx.encrypt(&mut data2[..], &associated_data2[..], &mut mac2)?; + let (ratchet_counter2, frame_counter2) = ctx.encrypt(&mut data2[..], &mut mac2)?; assert_eq!(1, ratchet_counter2); ctx.decrypt( sender_id, ratchet_counter2, frame_counter2, &mut data2[..], - &associated_data2[..], &mac2, )?; assert_eq!(&plaintext[..], &data2[..]); @@ -669,7 +618,6 @@ mod tests { ratchet_counter1, frame_counter1, &mut data1[..], - &associated_data1[..], &mac1, )?; assert_eq!(&plaintext[..], &data1[..]); diff --git a/src/rust/src/core/group_call.rs b/src/rust/src/core/group_call.rs index 50575133..0a0783b9 100644 --- a/src/rust/src/core/group_call.rs +++ b/src/rust/src/core/group_call.rs @@ -3449,7 +3449,6 @@ impl Client { } // The format for the ciphertext is: - // 1 (audio) or 10 (video) bytes of unencrypted media // N bytes of encrypted media (the rest of the given plaintext_size) // 1 byte RatchetCounter // 4 byte FrameCounter @@ -3467,22 +3466,6 @@ impl Client { + size_of::() + size_of::(); - // The portion of the frame we leave in the clear - // to allow the SFU to forward media properly. - fn unencrypted_media_header_len(is_audio: bool, has_encrypted_media_header: bool) -> usize { - if has_encrypted_media_header { - return 0; - } - - if is_audio { - // For Opus TOC - 1 - } else { - // For VP8 headers when dependency descriptor isn't used - 10 - } - } - // Called by WebRTC through PeerConnectionObserver // See comment on FRAME_ENCRYPTION_FOOTER_LEN for more details on the format fn get_ciphertext_buffer_size(plaintext_size: usize) -> usize { @@ -3493,24 +3476,13 @@ impl Client { // Called by WebRTC through PeerConnectionObserver // See comment on FRAME_ENCRYPTION_FOOTER_LEN for more details on the format - fn encrypt_media( - &self, - is_audio: bool, - plaintext: &[u8], - ciphertext_buffer: &mut [u8], - ) -> Result { + fn encrypt_media(&self, plaintext: &[u8], ciphertext_buffer: &mut [u8]) -> Result { let mut frame_crypto_context = self .frame_crypto_context .lock() .expect("Get e2ee context to encrypt media"); - let unencrypted_header_len = Self::unencrypted_media_header_len(is_audio, true); - Self::encrypt( - &mut frame_crypto_context, - unencrypted_header_len, - plaintext, - ciphertext_buffer, - ) + Self::encrypt(&mut frame_crypto_context, plaintext, ciphertext_buffer) } fn encrypt_data(state: &mut State, plaintext: &[u8]) -> Result> { @@ -3520,27 +3492,23 @@ impl Client { .expect("Get e2ee context to encrypt data"); let mut ciphertext = vec![0; Self::get_ciphertext_buffer_size(plaintext.len())]; - Self::encrypt(&mut frame_crypto_context, 0, plaintext, &mut ciphertext)?; + Self::encrypt(&mut frame_crypto_context, plaintext, &mut ciphertext)?; Ok(ciphertext) } fn encrypt( frame_crypto_context: &mut frame_crypto::Context, - unencrypted_header_len: usize, plaintext: &[u8], ciphertext_buffer: &mut [u8], ) -> Result { let ciphertext_size = Self::get_ciphertext_buffer_size(plaintext.len()); - let mut plaintext = Reader::new(plaintext); let mut ciphertext = Writer::new(ciphertext_buffer); - let unencrypted_header = plaintext.read_slice(unencrypted_header_len)?; - ciphertext.write_slice(unencrypted_header)?; - let encrypted_payload = ciphertext.write_slice(plaintext.remaining())?; + let encrypted_payload = ciphertext.write_slice(plaintext)?; let mut mac = frame_crypto::Mac::default(); let (ratchet_counter, frame_counter) = - frame_crypto_context.encrypt(encrypted_payload, unencrypted_header, &mut mac)?; + frame_crypto_context.encrypt(encrypted_payload, &mut mac)?; if frame_counter > u32::MAX as u64 { return Err(RingRtcError::FrameCounterTooBig.into()); } @@ -3564,22 +3532,17 @@ impl Client { fn decrypt_media( &self, remote_demux_id: DemuxId, - is_audio: bool, ciphertext: &[u8], plaintext_buffer: &mut [u8], - has_encrypted_media_header: bool, ) -> Result { let mut frame_crypto_context = self .frame_crypto_context .lock() .expect("Get e2ee context to decrypt media"); - let unencrypted_header_len = - Self::unencrypted_media_header_len(is_audio, has_encrypted_media_header); Self::decrypt( &mut frame_crypto_context, remote_demux_id, - unencrypted_header_len, ciphertext, plaintext_buffer, ) @@ -3595,7 +3558,6 @@ impl Client { Self::decrypt( &mut frame_crypto_context, remote_demux_id, - 0, ciphertext, &mut plaintext, )?; @@ -3605,14 +3567,12 @@ impl Client { fn decrypt( frame_crypto_context: &mut frame_crypto::Context, remote_demux_id: DemuxId, - unencrypted_header_len: usize, ciphertext: &[u8], plaintext_buffer: &mut [u8], ) -> Result { let mut ciphertext = Reader::new(ciphertext); let mut plaintext = Writer::new(plaintext_buffer); - let unencrypted_header = ciphertext.read_slice(unencrypted_header_len)?; let mac: frame_crypto::Mac = ciphertext .read_slice_from_end(size_of::())? .try_into()?; @@ -3621,7 +3581,6 @@ impl Client { // Allow for in-place decryption from ciphertext to plaintext_buffer by using // the write_slice that supports overlapping copies. - plaintext.write_slice_overlapping(unencrypted_header)?; let encrypted_payload = plaintext.write_slice_overlapping(ciphertext.remaining())?; frame_crypto_context.decrypt( @@ -3629,10 +3588,9 @@ impl Client { ratchet_counter, frame_counter as u64, encrypted_payload, - unencrypted_header, &mac, )?; - Ok(unencrypted_header.len() + encrypted_payload.len()) + Ok(encrypted_payload.len()) } fn send_heartbeat(state: &mut State) -> Result<()> { @@ -4547,14 +4505,9 @@ impl PeerConnectionObserverTrait for PeerConnectionObserverImpl { } // See comment on FRAME_ENCRYPTION_FOOTER_LEN for more details on the format - fn encrypt_media( - &mut self, - is_audio: bool, - plaintext: &[u8], - ciphertext_buffer: &mut [u8], - ) -> Result { + fn encrypt_media(&mut self, plaintext: &[u8], ciphertext_buffer: &mut [u8]) -> Result { if let Some(client) = &self.client { - client.encrypt_media(is_audio, plaintext, ciphertext_buffer) + client.encrypt_media(plaintext, ciphertext_buffer) } else { warn!("Call isn't setup yet! Can't encrypt."); Err(RingRtcError::FailedToEncrypt.into()) @@ -4574,20 +4527,12 @@ impl PeerConnectionObserverTrait for PeerConnectionObserverImpl { fn decrypt_media( &mut self, track_id: u32, - is_audio: bool, ciphertext: &[u8], plaintext_buffer: &mut [u8], - has_encrypted_media_header: bool, ) -> Result { if let Some(client) = &self.client { let remote_demux_id = track_id; - client.decrypt_media( - remote_demux_id, - is_audio, - ciphertext, - plaintext_buffer, - has_encrypted_media_header, - ) + client.decrypt_media(remote_demux_id, ciphertext, plaintext_buffer) } else { warn!("Call isn't setup yet! Can't decrypt"); Err(RingRtcError::FailedToDecrypt.into()) @@ -4688,15 +4633,6 @@ impl<'data> Reader<'data> { )) } - fn read_slice(&mut self, len: usize) -> Result<&'data [u8]> { - if len > self.data.len() { - return Err(RingRtcError::BufferTooSmall.into()); - } - let (read, rest) = self.data.split_at(len); - self.data = rest; - Ok(read) - } - fn read_slice_from_end(&mut self, len: usize) -> Result<&'data [u8]> { if len > self.data.len() { return Err(RingRtcError::BufferTooSmall.into()); @@ -5411,7 +5347,7 @@ mod tests { event.wait(Duration::from_secs(5)); } - fn encrypt_media(&mut self, is_audio: bool, plaintext: &[u8]) -> Result> { + fn encrypt_media(&mut self, plaintext: &[u8]) -> Result> { let mut ciphertext = vec![0; plaintext.len() + Client::FRAME_ENCRYPTION_FOOTER_LEN]; assert_eq!( ciphertext.len(), @@ -5419,8 +5355,7 @@ mod tests { ); assert_eq!( ciphertext.len(), - self.client - .encrypt_media(is_audio, plaintext, &mut ciphertext)? + self.client.encrypt_media(plaintext, &mut ciphertext)? ); Ok(ciphertext) } @@ -5428,9 +5363,7 @@ mod tests { fn decrypt_media( &mut self, remote_demux_id: DemuxId, - is_audio: bool, ciphertext: &[u8], - has_encrypted_media_header: bool, ) -> Result> { let mut plaintext = vec![ 0; @@ -5444,13 +5377,8 @@ mod tests { ); assert_eq!( plaintext.len(), - self.client.decrypt_media( - remote_demux_id, - is_audio, - ciphertext, - &mut plaintext, - has_encrypted_media_header - )? + self.client + .decrypt_media(remote_demux_id, ciphertext, &mut plaintext,)? ); Ok(plaintext) } @@ -5512,18 +5440,17 @@ mod tests { // And while client2 has shared the key with client1, client1 has not yet learned // about client2 so can't decrypt either. - let is_audio = true; let plaintext = &b"Fake Audio"[..]; - let ciphertext1 = client1.encrypt_media(is_audio, plaintext).unwrap(); - let ciphertext2 = client2.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext1 = client1.encrypt_media(plaintext).unwrap(); + let ciphertext2 = client2.encrypt_media(plaintext).unwrap(); assert_ne!(plaintext, &ciphertext1[..plaintext.len()]); assert!(client1 - .decrypt_media(client2.demux_id, is_audio, &ciphertext2, true) + .decrypt_media(client2.demux_id, &ciphertext2) .is_err()); assert!(client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext1, true) + .decrypt_media(client1.demux_id, &ciphertext1) .is_err()); client1.set_remotes_and_wait_until_applied(&[&client2]); @@ -5535,44 +5462,41 @@ mod tests { // Because client1 just learned about client2, it advanced its key // and so we need to re-encrypt with that key. - let mut ciphertext1 = client1.encrypt_media(is_audio, plaintext).unwrap(); + let mut ciphertext1 = client1.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext1, true) + .decrypt_media(client1.demux_id, &ciphertext1) .unwrap() ); assert_eq!( plaintext, client1 - .decrypt_media(client2.demux_id, is_audio, &ciphertext2, true) + .decrypt_media(client2.demux_id, &ciphertext2) .unwrap() ); // But if the footer is too small, decryption should fail - assert!(client1 - .decrypt_media(client2.demux_id, is_audio, b"small", true) - .is_err()); + assert!(client1.decrypt_media(client2.demux_id, b"small").is_err()); // And if the unencrypted media header has been modified, it should fail (bad mac) ciphertext1[0] = ciphertext1[0].wrapping_add(1); assert!(client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext1, true) + .decrypt_media(client1.demux_id, &ciphertext1) .is_err()); // Finally, let's make sure video works as well - let is_audio = false; let plaintext = &b"Fake Video Needs To Be Bigger"[..]; - let ciphertext1 = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext1 = client1.encrypt_media(plaintext).unwrap(); assert_ne!(plaintext, &ciphertext1[..plaintext.len()]); assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext1, true) + .decrypt_media(client1.demux_id, &ciphertext1) .unwrap() ); @@ -5602,23 +5526,22 @@ mod tests { // client2 and client3 can decrypt client1 // client4 can't yet - let is_audio = true; let plaintext = &b"Fake Audio"[..]; - let ciphertext = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext = client1.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client3 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert!(client4 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .is_err()); // Add client4 and remove client3 @@ -5626,23 +5549,23 @@ mod tests { // client2 and client4 can decrypt client1 // client3 can as well, at least for a little while - let ciphertext = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext = client1.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client3 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client4 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); @@ -5655,29 +5578,29 @@ mod tests { // one. set_group_and_wait_until_applied(&[&client1, &client4, &client5]); - let ciphertext = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext = client1.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client3 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client4 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client5 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); @@ -5685,26 +5608,26 @@ mod tests { // client4 and client5 can still decrypt from client1 // but client3 no longer can - let ciphertext = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext = client1.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert!(client3 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .is_err()); assert_eq!( plaintext, client4 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client5 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); @@ -5712,23 +5635,23 @@ mod tests { // After the next key rotation is applied, now client2 cannot decrypt, // but client4 and client5 can. - let ciphertext = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext = client1.encrypt_media(plaintext).unwrap(); assert!(client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .is_err()); assert!(client3 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .is_err()); assert_eq!( plaintext, client4 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); assert_eq!( plaintext, client5 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); @@ -5755,12 +5678,11 @@ mod tests { assert_eq!(1, remote_devices.len()); assert!(!remote_devices[0].media_keys_received); - let is_audio = false; let plaintext = &b"Fake Video is big"[..]; - let ciphertext = client1.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext = client1.encrypt_media(plaintext).unwrap(); // We can't decrypt because the keys got dropped assert!(client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .is_err()); client1.observer.set_outgoing_signaling_blocked(false); @@ -5775,7 +5697,7 @@ mod tests { assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext, true) + .decrypt_media(client1.demux_id, &ciphertext) .unwrap() ); } @@ -5790,24 +5712,23 @@ mod tests { client2a.connect_join_and_wait_until_joined(); set_group_and_wait_until_applied(&[&client1a, &client2a]); - let is_audio = true; let plaintext = &b"Fake Audio"[..]; - let ciphertext1a = client1a.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext1a = client1a.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2a - .decrypt_media(client1a.demux_id, is_audio, &ciphertext1a, true) + .decrypt_media(client1a.demux_id, &ciphertext1a) .unwrap() ); // Make sure the advanced key gets sent to client2b even though it's the same user as 2a. client2b.connect_join_and_wait_until_joined(); set_group_and_wait_until_applied(&[&client1a, &client2a, &client2b]); - let ciphertext1a = client1a.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext1a = client1a.encrypt_media(plaintext).unwrap(); assert_eq!( plaintext, client2b - .decrypt_media(client1a.demux_id, is_audio, &ciphertext1a, true) + .decrypt_media(client1a.demux_id, &ciphertext1a) .unwrap() ); } @@ -5827,20 +5748,19 @@ mod tests { set_group_and_wait_until_applied(&[&client1, &client2, &client3]); - let is_audio = true; let plaintext = &b"Fake Audio"[..]; - let ciphertext1 = client1.encrypt_media(is_audio, plaintext).unwrap(); - let ciphertext3 = client3.encrypt_media(is_audio, plaintext).unwrap(); + let ciphertext1 = client1.encrypt_media(plaintext).unwrap(); + let ciphertext3 = client3.encrypt_media(plaintext).unwrap(); // The forger doesn't mess anything up for the others assert_eq!( plaintext, client2 - .decrypt_media(client1.demux_id, is_audio, &ciphertext1, true) + .decrypt_media(client1.demux_id, &ciphertext1) .unwrap() ); // And you can't decrypt from the forger. assert!(client2 - .decrypt_media(client3.demux_id, is_audio, &ciphertext3, true) + .decrypt_media(client3.demux_id, &ciphertext3) .is_err()); client1.disconnect_and_wait_until_ended(); diff --git a/src/rust/src/webrtc/peer_connection_observer.rs b/src/rust/src/webrtc/peer_connection_observer.rs index 3f70167c..3b225fc0 100644 --- a/src/rust/src/webrtc/peer_connection_observer.rs +++ b/src/rust/src/webrtc/peer_connection_observer.rs @@ -159,12 +159,7 @@ pub trait PeerConnectionObserverTrait { ) -> usize { 0 } - fn encrypt_media( - &mut self, - _is_audio: bool, - _plaintext: &[u8], - _ciphertext_buffer: &mut [u8], - ) -> Result { + fn encrypt_media(&mut self, _plaintext: &[u8], _ciphertext_buffer: &mut [u8]) -> Result { Err(RingRtcError::FailedToEncrypt.into()) } fn get_media_plaintext_buffer_size( @@ -178,10 +173,8 @@ pub trait PeerConnectionObserverTrait { fn decrypt_media( &mut self, _track_id: u32, - _is_audio: bool, _ciphertext: &[u8], _plaintext_buffer: &mut [u8], - _has_encrypted_media_header: bool, ) -> Result { Err(RingRtcError::FailedToDecrypt.into()) } @@ -498,7 +491,6 @@ where #[allow(non_snake_case)] extern "C" fn pc_observer_EncryptMedia( observer: webrtc::ptr::Borrowed, - is_audio: bool, plaintext: webrtc::ptr::Borrowed, plaintext_size: size_t, ciphertext_out: *mut u8, @@ -514,8 +506,7 @@ where } trace!( - "pc_observer_EncryptMedia(): is_audio: {} plaintext_size: {}, ciphertext_out_size: {}", - is_audio, + "pc_observer_EncryptMedia(): plaintext_size: {}, ciphertext_out_size: {}", plaintext_size, ciphertext_out_size ); @@ -525,7 +516,7 @@ where let plaintext = unsafe { slice::from_raw_parts(plaintext.as_ptr(), plaintext_size) }; let ciphertext = unsafe { slice::from_raw_parts_mut(ciphertext_out, ciphertext_out_size) }; - match observer.encrypt_media(is_audio, plaintext, ciphertext) { + match observer.encrypt_media(plaintext, ciphertext) { Ok(size) => { unsafe { *ciphertext_size_out = size; @@ -570,13 +561,11 @@ where extern "C" fn pc_observer_DecryptMedia( observer: webrtc::ptr::Borrowed, track_id: u32, - is_audio: bool, ciphertext: webrtc::ptr::Borrowed, ciphertext_size: usize, plaintext_out: *mut u8, plaintext_out_size: size_t, plaintext_size_out: *mut size_t, - has_encrypted_media_header: bool, ) -> bool where T: PeerConnectionObserverTrait, @@ -590,13 +579,7 @@ where let ciphertext = unsafe { slice::from_raw_parts(ciphertext.as_ptr(), ciphertext_size) }; let plaintext = unsafe { slice::from_raw_parts_mut(plaintext_out, plaintext_out_size) }; - match observer.decrypt_media( - track_id, - is_audio, - ciphertext, - plaintext, - has_encrypted_media_header, - ) { + match observer.decrypt_media(track_id, ciphertext, plaintext) { Ok(size) => { unsafe { *plaintext_size_out = size; @@ -661,7 +644,6 @@ where getMediaCiphertextBufferSize: extern "C" fn(webrtc::ptr::Borrowed, bool, size_t) -> size_t, encryptMedia: extern "C" fn( webrtc::ptr::Borrowed, - bool, webrtc::ptr::Borrowed, size_t, *mut u8, @@ -673,13 +655,11 @@ where decryptMedia: extern "C" fn( webrtc::ptr::Borrowed, u32, - bool, webrtc::ptr::Borrowed, size_t, *mut u8, size_t, *mut size_t, - bool, ) -> bool, } From 339383f441a3b8838c5f95408f5d860f647fb54f Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Thu, 21 Nov 2024 12:49:48 -0500 Subject: [PATCH 16/29] Fix broken windows build. cubeb 0.17.0 is at https://github.com/mozilla/cubeb-rs/commit/512f66a905ad7c35d340db57141db7bb406654ee, which always clones the ToT libcubeb (https://github.com/mozilla/cubeb-rs/commit/b157769eb73324e193c3dd7e12195d982bc8b44d, the next commit, fixes that by pinning to a hash), but ToT cubeb just had https://github.com/mozilla/cubeb/commit/d504c22284d55a8cfd7323973f302dfa2bf93565, which requires a new option when compiling libcubeb, USE_STATIC_MSVC_RUNTIME. So, cubeb-sys 0.17.0 is now broken; we need 0.17.1. --- Cargo.lock | 4 ++-- acknowledgments/acknowledgments.html | 2 +- acknowledgments/acknowledgments.md | 2 +- acknowledgments/acknowledgments.plist | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b38f5f8f..bc19e3b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,9 +778,9 @@ dependencies = [ [[package]] name = "cubeb-sys" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a1b58889aa85e1df0292cf8eb579b3f669893f4d18967e8f74be3ec4e597da" +checksum = "aa5ea151dfe7491d9f7bc109fd60b660b3c3dd6d50dc9cbbd1a0327624f5fd14" dependencies = [ "cmake", "pkg-config", diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index 4168ad6b..c8ede87e 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -1541,7 +1541,7 @@

ISC License

Used by:

Copyright © 2017 Mozilla Foundation
diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md
index 9d6b2e0c..98c34f56 100644
--- a/acknowledgments/acknowledgments.md
+++ b/acknowledgments/acknowledgments.md
@@ -1453,7 +1453,7 @@ THIS SOFTWARE.
 
 ```
 
-## cubeb-core 0.17.0, cubeb-sys 0.17.0, cubeb 0.17.0
+## cubeb-core 0.17.0, cubeb-sys 0.17.1, cubeb 0.17.0
 
 ```
 Copyright © 2017 Mozilla Foundation
diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist
index d85fdeae..9647f69b 100644
--- a/acknowledgments/acknowledgments.plist
+++ b/acknowledgments/acknowledgments.plist
@@ -1518,7 +1518,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 			License
 			ISC License
 			Title
-			cubeb-core 0.17.0, cubeb-sys 0.17.0, cubeb 0.17.0
+			cubeb-core 0.17.0, cubeb-sys 0.17.1, cubeb 0.17.0
 			Type
 			PSGroupSpecifier
 		

From 137f93b3810d0893a63e783ecde482a8ea4a5f1b Mon Sep 17 00:00:00 2001
From: Jim Gustafson 
Date: Wed, 20 Nov 2024 08:50:05 -0800
Subject: [PATCH 17/29] Update to webrtc 6723b

- Enable video layers allocation header extension in group calls
- Remove checks for dependency descriptors
- Fix some tests and disable others
---
 bin/fetch-artifact.py     | 16 ++++++++--------
 config/version.properties |  2 +-
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/bin/fetch-artifact.py b/bin/fetch-artifact.py
index fc85ed61..874bc342 100755
--- a/bin/fetch-artifact.py
+++ b/bin/fetch-artifact.py
@@ -18,14 +18,14 @@
 UNVERIFIED_DOWNLOAD_NAME = "unverified.tmp"
 
 PREBUILD_CHECKSUMS = {
-    'android': '59ace51334064fa9aa1668782058605cc67439eef48e8d03fc145b2374f68915',
-    'ios': '9609a97abffb83bbf49386be7b6245298c17b8eb705fdfee671bf60e2170dfdb',
-    'linux-arm64': 'c875d7dc77acb5ad497989c228e9768969b392c10d6338c511fa8a40feab9c54',
-    'linux-x64': '40cd68cce608241005e59f0282dbcd3e57f46502d6cc64289bbbd1ce038cd960',
-    'mac-arm64': '6d9b407b257e5668664001604836e824321b984abfd9f8d65299152b0f543d71',
-    'mac-x64': '2b83846cb6ef55b1b7f0759b67439d6696e472f9007043a9f565ca0fc73b990c',
-    'windows-arm64': 'cd45c84a9fad7dba7fa31b63375b85e7c9f335691e96f7af8c88e5efcb604092',
-    'windows-x64': 'f2a6e76ecb5e83d9027f327f6eb4934e32390fa44b956b7cfc0aa12729b69e21',
+    'android': '5cf54b0cb371f423dc80da402cebdc18f719221b3296a02105b3733ae74e0877',
+    'ios': 'da183ca1636b3d463d803cf27efa739441e278b0127e77b39898ee5f4e725a45',
+    'linux-x64': 'b5670fe9aaa93e66e09e1f93eb9db33f771132563b73d73ad9851275540eea59',
+    'linux-arm64': '6d002a86a488e2a0402e7dd936552cd257d34239b58c67932d3445c9ef76d55f',
+    'mac-x64': 'f9a81767291d38863ef7fba1d64d4511b717f544672675ca6c21a74101d01bb6',
+    'mac-arm64': '7563aa53c8cd5a3b7b0bb2c219e334cdf1f735bf212f628b7294c277423e2408',
+    'windows-x64': 'd0130dc8c384535189bbd01993060e222843d79a02e68eafc29aebf4ca3f7fd5',
+    'windows-arm64': '715126e8dcd5c0290361b5599bd4200fe4587b852d93da684c5dbcfddf4a6b0f',
 }
 
 
diff --git a/config/version.properties b/config/version.properties
index 25fded64..7f8bf246 100644
--- a/config/version.properties
+++ b/config/version.properties
@@ -1,4 +1,4 @@
-webrtc.version=6723a
+webrtc.version=6723b
 
 ringrtc.version.major=2
 ringrtc.version.minor=48

From 10c418b182f96d1631bbfeb200bcc241231f302a Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Fri, 22 Nov 2024 12:37:07 -0500
Subject: [PATCH 18/29] Run ringrtc ADM tests in CI.

---
 .github/workflows/ringrtc.yml | 10 ++++++++--
 bin/build-electron            | 10 ++++++++++
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ringrtc.yml b/.github/workflows/ringrtc.yml
index c08db87a..8e2713ae 100644
--- a/.github/workflows/ringrtc.yml
+++ b/.github/workflows/ringrtc.yml
@@ -118,7 +118,7 @@ jobs:
         os: [ubuntu-22.04, windows-latest, macos-13]
         include:
         - os: ubuntu-22.04
-          install-deps: sudo apt-get update && sudo apt-get install -y protobuf-compiler libpulse-dev
+          install-deps: sudo add-apt-repository ppa:pipewire-debian/pipewire-upstream && sudo apt-get update && sudo apt-get install -y protobuf-compiler libpulse-dev libpulse0 pipewire && systemctl --user daemon-reload && systemctl --user --now enable pipewire pipewire-pulse
           test-runner: xvfb-run --auto-servernum
         - os: windows-latest
           install-deps: choco install protoc cmake
@@ -131,13 +131,19 @@ jobs:
     steps:
     - name: Install dependencies
       run: ${{ matrix.install-deps }}
+    - name: Set up virtual sound devices
+      uses: LABSN/sound-ci-helpers@v1
+      if: ${{ matrix.os == 'windows-latest' }}
+    - name: Start audiosrv
+      run: net start audiosrv
+      if: ${{ matrix.os == 'windows-latest' }}
     - uses: actions/checkout@v4
     - uses: actions/setup-node@v4
       with:
         node-version-file: 'src/node/.nvmrc'
     - run: rustup toolchain install $(cat rust-toolchain) --profile minimal
     - run: bin/fetch-artifact -p desktop
-    - run: bin/build-electron --release --ringrtc-only
+    - run: bin/build-electron --release --ringrtc-only --test-ringrtc-adm
     - run: npm ci
       working-directory: src/node
     - run: npm run build
diff --git a/bin/build-electron b/bin/build-electron
index 20543979..eeee71ef 100755
--- a/bin/build-electron
+++ b/bin/build-electron
@@ -24,6 +24,7 @@ usage()
         --webrtc-only builds libwebrtc.a only
         --ringrtc-only builds libringrtc-ARCH.node only
         --webrtc-tests also builds webrtc tests
+        --test-ringrtc-adm also tests the ringrtc ADM.
 
         --archive-webrtc generates an archive suitable for later --ringrtc-only builds'
 }
@@ -40,6 +41,7 @@ clean()
 BUILD_TYPE=debug
 BUILD_WHAT=all
 ARCHIVE_WEBRTC=
+TEST_RINGRTC_ADM=
 
 while [ "$1" != "" ]; do
     case $1 in
@@ -61,6 +63,9 @@ while [ "$1" != "" ]; do
         --archive-webrtc )
             ARCHIVE_WEBRTC=yes
             ;;
+        --test-ringrtc-adm )
+            TEST_RINGRTC_ADM=yes
+            ;;
         -c | --clean )
             clean
             exit
@@ -226,6 +231,11 @@ then
         export CARGO_BUILD_TARGET="${CARGO_TARGET}"
         RUSTFLAGS="${RUSTFLAGS}" OUTPUT_DIR="${OUTPUT_DIR}" cargo rustc --package ringrtc --target ${CARGO_TARGET} --features electron ${INCLUDE_RELEASE_FLAG:+"--release"} --crate-type cdylib
 
+        if [ "${TEST_RINGRTC_ADM}" = "yes" ]; then
+          RUSTFLAGS="${RUSTFLAGS}" OUTPUT_DIR="${OUTPUT_DIR}" cargo test --package ringrtc --target ${CARGO_TARGET} --features electron ${INCLUDE_RELEASE_FLAG:+"--release"} -- --nocapture audio_device_module_tests
+        fi
+
+
         mkdir -p src/node/build/${DEFAULT_PLATFORM}
         copy_to_node target/${CARGO_TARGET}/${BUILD_TYPE}/${OUTPUT_LIBRARY} src/node/build/${DEFAULT_PLATFORM}/libringrtc-"${TARGET_ARCH}".node
 

From fbf1b58d44ec11b82fc4254db564bd946dcbfe54 Mon Sep 17 00:00:00 2001
From: Jim Gustafson 
Date: Fri, 22 Nov 2024 14:05:30 -0800
Subject: [PATCH 19/29] Use large runner for iOS tests

---
 .github/workflows/ringrtc.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/ringrtc.yml b/.github/workflows/ringrtc.yml
index 8e2713ae..c259101d 100644
--- a/.github/workflows/ringrtc.yml
+++ b/.github/workflows/ringrtc.yml
@@ -196,7 +196,7 @@ jobs:
 
   ios:
     name: iOS Tests
-    runs-on: macos-13
+    runs-on: macos-13-large
     steps:
     - uses: actions/checkout@v4
     - run: brew install protobuf coreutils # for grealpath

From 61fa876f7b83946cec0cc4954886a1e0d680df9b Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Fri, 22 Nov 2024 17:33:05 -0500
Subject: [PATCH 20/29] Fix broken windows build.

---
 .github/workflows/ringrtc.yml | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/ringrtc.yml b/.github/workflows/ringrtc.yml
index c259101d..e821bb38 100644
--- a/.github/workflows/ringrtc.yml
+++ b/.github/workflows/ringrtc.yml
@@ -131,12 +131,6 @@ jobs:
     steps:
     - name: Install dependencies
       run: ${{ matrix.install-deps }}
-    - name: Set up virtual sound devices
-      uses: LABSN/sound-ci-helpers@v1
-      if: ${{ matrix.os == 'windows-latest' }}
-    - name: Start audiosrv
-      run: net start audiosrv
-      if: ${{ matrix.os == 'windows-latest' }}
     - uses: actions/checkout@v4
     - uses: actions/setup-node@v4
       with:
@@ -144,6 +138,9 @@ jobs:
     - run: rustup toolchain install $(cat rust-toolchain) --profile minimal
     - run: bin/fetch-artifact -p desktop
     - run: bin/build-electron --release --ringrtc-only --test-ringrtc-adm
+      if: ${{ matrix.os != 'windows-latest' }}
+    - run: bin/build-electron --release --ringrtc-only
+      if: ${{ matrix.os == 'windows-latest' }}
     - run: npm ci
       working-directory: src/node
     - run: npm run build

From 40a95ef0a0701c8ea2192ddc92bd7de353797cca Mon Sep 17 00:00:00 2001
From: Jim Gustafson 
Date: Mon, 25 Nov 2024 09:12:50 -0800
Subject: [PATCH 21/29] Increase priority of non-relay candidates

---
 src/android/api/org/signal/ringrtc/CallManager.java         | 1 +
 src/ios/SignalRingRTC/SignalRingRTC/CallManagerGlobal.swift | 1 +
 src/node/ringrtc/Service.ts                                 | 1 +
 3 files changed, 3 insertions(+)

diff --git a/src/android/api/org/signal/ringrtc/CallManager.java b/src/android/api/org/signal/ringrtc/CallManager.java
index 410b0f80..2c5eb65e 100644
--- a/src/android/api/org/signal/ringrtc/CallManager.java
+++ b/src/android/api/org/signal/ringrtc/CallManager.java
@@ -111,6 +111,7 @@ public static void initialize(Context applicationContext, Log.Logger logger, Map
       Map fieldTrialsWithDefaults = new HashMap<>();
       fieldTrialsWithDefaults.put("RingRTC-PruneTurnPorts", "Enabled");
       fieldTrialsWithDefaults.put("WebRTC-Bwe-ProbingConfiguration", "skip_if_est_larger_than_fraction_of_max:0.99");
+      fieldTrialsWithDefaults.put("WebRTC-IncreaseIceCandidatePriorityHostSrflx", "Enabled");
       fieldTrialsWithDefaults.putAll(fieldTrials);
 
       String fieldTrialsString = buildFieldTrialsString(fieldTrialsWithDefaults);
diff --git a/src/ios/SignalRingRTC/SignalRingRTC/CallManagerGlobal.swift b/src/ios/SignalRingRTC/SignalRingRTC/CallManagerGlobal.swift
index 55292460..d6f5d307 100644
--- a/src/ios/SignalRingRTC/SignalRingRTC/CallManagerGlobal.swift
+++ b/src/ios/SignalRingRTC/SignalRingRTC/CallManagerGlobal.swift
@@ -91,6 +91,7 @@ public class CallManagerGlobal {
             "RingRTC-AnyAddressPortsKillSwitch": "Enabled",
             "RingRTC-PruneTurnPorts": "Enabled",
             "WebRTC-Bwe-ProbingConfiguration": "skip_if_est_larger_than_fraction_of_max:0.99",
+            "WebRTC-IncreaseIceCandidatePriorityHostSrflx": "Enabled",
         ]) { (provided, _) in provided }
         RTCInitFieldTrialDictionary(fieldTrialsWithDefaults)
         Logger.info("Initialized field trials with \(fieldTrialsWithDefaults)")
diff --git a/src/node/ringrtc/Service.ts b/src/node/ringrtc/Service.ts
index 268f480d..0708da76 100644
--- a/src/node/ringrtc/Service.ts
+++ b/src/node/ringrtc/Service.ts
@@ -50,6 +50,7 @@ class NativeCallManager {
         'RingRTC-PruneTurnPorts': 'Enabled',
         'WebRTC-Bwe-ProbingConfiguration':
           'skip_if_est_larger_than_fraction_of_max:0.99',
+        'WebRTC-IncreaseIceCandidatePriorityHostSrflx': 'Enabled',
       },
       config.field_trials
     );

From aba8b13b247c052cdda5bb4f54f8d963b4781830 Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Tue, 26 Nov 2024 12:16:26 -0500
Subject: [PATCH 22/29] Remove redundant ADM creation

---
 src/rust/src/webrtc/peer_connection_factory.rs | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/rust/src/webrtc/peer_connection_factory.rs b/src/rust/src/webrtc/peer_connection_factory.rs
index 56b7763a..c8c04159 100644
--- a/src/rust/src/webrtc/peer_connection_factory.rs
+++ b/src/rust/src/webrtc/peer_connection_factory.rs
@@ -212,8 +212,7 @@ impl AudioConfig {
             adm.init();
             let backend_name = adm.backend_name();
             (
-                webrtc::ptr::Borrowed::from_ptr(Box::into_raw(Box::new(AudioDeviceModule::new())))
-                    .to_void(),
+                webrtc::ptr::Borrowed::from_ptr(Box::into_raw(Box::new(adm))).to_void(),
                 backend_name,
             )
         };

From 1a629bf182bb751e9b61fc221771ae0c1534cbaa Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Wed, 27 Nov 2024 12:45:52 -0500
Subject: [PATCH 23/29] Don't install cmake on windows.

---
 .github/workflows/ringrtc.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ringrtc.yml b/.github/workflows/ringrtc.yml
index e821bb38..5f02f9ee 100644
--- a/.github/workflows/ringrtc.yml
+++ b/.github/workflows/ringrtc.yml
@@ -121,7 +121,7 @@ jobs:
           install-deps: sudo add-apt-repository ppa:pipewire-debian/pipewire-upstream && sudo apt-get update && sudo apt-get install -y protobuf-compiler libpulse-dev libpulse0 pipewire && systemctl --user daemon-reload && systemctl --user --now enable pipewire pipewire-pulse
           test-runner: xvfb-run --auto-servernum
         - os: windows-latest
-          install-deps: choco install protoc cmake
+          install-deps: choco install protoc
         - os: macos-13
           install-deps: brew install protobuf coreutils
     runs-on: ${{ matrix.os }}
@@ -160,7 +160,7 @@ jobs:
         - os: macos-13
           install-deps: brew install protobuf coreutils
         - os: windows-latest
-          install-deps: choco install protoc cmake
+          install-deps: choco install protoc
     runs-on: ${{ matrix.os }}
     defaults:
       run:

From 268f922a24cf0c23e64900bb796e15544163b626 Mon Sep 17 00:00:00 2001
From: adel-signal 
Date: Wed, 27 Nov 2024 21:21:22 -0800
Subject: [PATCH 24/29] Add prebuilt_webrtc_sim feature

---
 .github/workflows/webrtc_artifacts.yml |  6 +--
 bin/build-electron                     | 22 +++++++++--
 bin/build-webrtc.py                    | 54 +++++++++++++++++---------
 bin/fetch-artifact.py                  | 18 ++++++++-
 src/rust/Cargo.toml                    |  1 +
 src/rust/build.rs                      | 41 +++++++++++++------
 6 files changed, 103 insertions(+), 39 deletions(-)

diff --git a/.github/workflows/webrtc_artifacts.yml b/.github/workflows/webrtc_artifacts.yml
index ce59fab0..a3b1c420 100644
--- a/.github/workflows/webrtc_artifacts.yml
+++ b/.github/workflows/webrtc_artifacts.yml
@@ -92,7 +92,7 @@ jobs:
       - name: Install dependencies
         run: sudo apt-get update && sudo apt-get install -y protobuf-compiler crossbuild-essential-arm64
 
-      - run: ./bin/build-webrtc.py --target linux --release
+      - run: ./bin/build-webrtc.py --target linux --release --build-for-simulator
 
       - name: Output Artifact Checksum
         run: |
@@ -135,7 +135,7 @@ jobs:
       # Installs the pkg_resources python module needed to build webrtc on a mac host
       - run: pip install setuptools
 
-      - run: ./bin/build-webrtc.py --target mac --release
+      - run: ./bin/build-webrtc.py --target mac --release --build-for-simulator
 
       - name: Output Artifact Checksum
         run: |
@@ -179,7 +179,7 @@ jobs:
 
     - uses: actions/checkout@v4
 
-    - run: python bin/build-webrtc.py --target windows --release
+    - run: python bin/build-webrtc.py --target windows --release --build-for-simulator
       shell: bash
 
     - name: Output Artifact Checksum
diff --git a/bin/build-electron b/bin/build-electron
index eeee71ef..3d524177 100755
--- a/bin/build-electron
+++ b/bin/build-electron
@@ -15,7 +15,7 @@ TARGET_ARCH=${TARGET_ARCH:-$(uname -m)}
 
 usage()
 {
-    echo 'usage: build-electron [-d|-r|-c] [--webrtc-only|--ringrtc-only] [--archive-webrtc]
+    echo 'usage: build-electron [-d|-r|-c] [--webrtc-only|--ringrtc-only] [--archive-webrtc] [--build-for-simulator]
     where:
         -d to create a debug build (default)
         -r to create a release build
@@ -25,6 +25,7 @@ usage()
         --ringrtc-only builds libringrtc-ARCH.node only
         --webrtc-tests also builds webrtc tests
         --test-ringrtc-adm also tests the ringrtc ADM.
+        --build-for-simulator also builds extra test tools used in simulators
 
         --archive-webrtc generates an archive suitable for later --ringrtc-only builds'
 }
@@ -42,6 +43,7 @@ BUILD_TYPE=debug
 BUILD_WHAT=all
 ARCHIVE_WEBRTC=
 TEST_RINGRTC_ADM=
+BUILD_FOR_SIMULATOR=
 
 while [ "$1" != "" ]; do
     case $1 in
@@ -66,6 +68,9 @@ while [ "$1" != "" ]; do
         --test-ringrtc-adm )
             TEST_RINGRTC_ADM=yes
             ;;
+        --build-for-simulator )
+            BUILD_FOR_SIMULATOR=yes
+            ;;
         -c | --clean )
             clean
             exit
@@ -110,6 +115,12 @@ then
     echo "Building WebRTC for ${GN_ARCH}"
 
     WEBRTC_ARGS="target_cpu=\"${GN_ARCH}\" rtc_build_examples=false rtc_build_tools=false rtc_use_x11=false rtc_enable_sctp=false rtc_libvpx_build_vp9=true rtc_include_ilbc=false rtc_disable_metrics=true rtc_disable_trace_events=true"
+
+    if [ -n "${BUILD_FOR_SIMULATOR}" ]
+    then
+      WEBRTC_ARGS="${WEBRTC_ARGS} rtc_use_dummy_audio_file_devices=true"
+    fi
+
     if [ -n "${BUILD_WEBRTC_TESTS}" ]
     then
       WEBRTC_ARGS="${WEBRTC_ARGS} rtc_include_tests=true rtc_enable_protobuf=true"
@@ -143,7 +154,12 @@ then
         if [ ! -e "${OUTPUT_DIR}/${STATIC_LIB_PATH}" ]; then
             STATIC_LIB_PATH="${BUILD_TYPE}"/obj/libwebrtc.a
         fi
-        tar -c --auto-compress --dereference -f "${OUTPUT_DIR}"/webrtc-"${WEBRTC_VERSION}"-"${HOST_PLATFORM}"-"${TARGET_ARCH}"-${BUILD_TYPE}.tar.bz2 -C "${OUTPUT_DIR}" "${STATIC_LIB_PATH}" "${BUILD_TYPE}/LICENSE.md"
+        ARCHIVE_NAME=webrtc-"${WEBRTC_VERSION}"-"${HOST_PLATFORM}"-"${TARGET_ARCH}"-"${BUILD_TYPE}"
+        if [ -n "${BUILD_FOR_SIMULATOR}" ]
+        then
+          ARCHIVE_NAME="${ARCHIVE_NAME}"-sim
+        fi
+        tar -c --auto-compress --dereference -f "${OUTPUT_DIR}"/"${ARCHIVE_NAME}".tar.bz2 -C "${OUTPUT_DIR}" "${STATIC_LIB_PATH}" "${BUILD_TYPE}/LICENSE.md"
     fi
 fi
 
@@ -175,7 +191,7 @@ then
             exit 1
     esac
 
-    echo "Building for platform ${DEFAULT_PLATFORM}, TARGET_ARCH=${TARGET_ARCH}, CARGO_TARGET=${CARGO_TARGET}"
+    echo "Building for platform ${DEFAULT_PLATFORM}, TARGET_ARCH=${TARGET_ARCH}, CARGO_TARGET=${CARGO_TARGET}, BUILD_FOR_SIMULATOR=${BUILD_FOR_SIMULATOR}"
 
     # Build and link the final RingRTC library.
     (
diff --git a/bin/build-webrtc.py b/bin/build-webrtc.py
index 50e9c366..b38d3869 100755
--- a/bin/build-webrtc.py
+++ b/bin/build-webrtc.py
@@ -37,6 +37,9 @@ def parse_args():
     parser.add_argument('--target',
                         required=True,
                         help='build target: ' + ', '.join(TARGET_PLATFORMS))
+    parser.add_argument('--build-for-simulator',
+                        action='store_true',
+                        help='Also build simulator version. Only for Desktop platforms. Default is only non-simulator version')
     parser.add_argument('--release',
                         action='store_true',
                         help='Build a release version. Default is both')
@@ -95,10 +98,17 @@ def main() -> None:
         build_types.append("debug")
         build_types.append("release")
 
+    sim_targets = [[]]
+    if args.target in ['android', 'ios'] and args.build_for_simulator:
+        raise Exception('Simulator builds are only supported for Desktop platforms')
+    elif args.build_for_simulator:
+        sim_targets = [["--build-for-simulator"], []]
+
     logging.info('''
 Target platform : {}
 Build type      : {}
-    '''.format(args.target, build_types))
+Sim targets     : {}
+    '''.format(args.target, build_types, sim_targets))
 
     verify_build_host_platform(args.target)
 
@@ -141,9 +151,10 @@ def main() -> None:
             # Build WebRTC for x86_64
             env['TARGET_ARCH'] = "x64"
             for build_type in build_types:
-                run_cmd(args.dry_run,
-                        ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type],
-                        env=env)
+                for sim_target in sim_targets:
+                    run_cmd(args.dry_run,
+                            ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type] + sim_target,
+                            env=env)
 
             # Build WebRTC for arm64
             run_cmd(args.dry_run,
@@ -152,9 +163,10 @@ def main() -> None:
             env['TARGET_ARCH'] = "arm64"
             env['OUTPUT_DIR'] = "out_arm"
             for build_type in build_types:
-                run_cmd(args.dry_run,
-                        ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type],
-                        env=env)
+                for sim_target in sim_targets:
+                    run_cmd(args.dry_run,
+                            ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type] + sim_target,
+                            env=env)
 
     elif args.target == 'ios' or args.target == 'mac':
         # Get grealpath
@@ -188,16 +200,18 @@ def main() -> None:
 
             env['TARGET_ARCH'] = "x64"
             for build_type in build_types:
-                run_cmd(args.dry_run,
-                        ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type],
-                        env=env)
+                for sim_target in sim_targets:
+                    run_cmd(args.dry_run,
+                            ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type] + sim_target,
+                            env=env)
 
             env['TARGET_ARCH'] = "arm64"
             env['OUTPUT_DIR'] = "out_arm"
             for build_type in build_types:
-                run_cmd(args.dry_run,
-                        ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type],
-                        env=env)
+                for sim_target in sim_targets:
+                    run_cmd(args.dry_run,
+                            ['bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type] + sim_target,
+                            env=env)
 
     elif args.target == 'windows':
         bash = 'C:\\Program Files\\Git\\bin\\bash.exe'
@@ -207,9 +221,10 @@ def main() -> None:
 
         env['TARGET_ARCH'] = "x64"
         for build_type in build_types:
-            run_cmd(args.dry_run,
-                    [bash, 'bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type],
-                    env=env)
+            for sim_target in sim_targets:
+                run_cmd(args.dry_run,
+                        [bash, 'bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type] + sim_target,
+                        env=env)
 
         # Prepare workspace for arm
         env['OUTPUT_DIR'] = "out_arm"
@@ -217,9 +232,10 @@ def main() -> None:
 
         env['TARGET_ARCH'] = "arm64"
         for build_type in build_types:
-            run_cmd(args.dry_run,
-                    [bash, 'bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type],
-                    env=env)
+            for sim_target in sim_targets:
+                run_cmd(args.dry_run,
+                        [bash, 'bin/build-electron', '--webrtc-only', '--archive-webrtc', '--' + build_type] + sim_target,
+                        env=env)
 
 
 if __name__ == '__main__':
diff --git a/bin/fetch-artifact.py b/bin/fetch-artifact.py
index 874bc342..764fcc76 100755
--- a/bin/fetch-artifact.py
+++ b/bin/fetch-artifact.py
@@ -20,11 +20,17 @@
 PREBUILD_CHECKSUMS = {
     'android': '5cf54b0cb371f423dc80da402cebdc18f719221b3296a02105b3733ae74e0877',
     'ios': 'da183ca1636b3d463d803cf27efa739441e278b0127e77b39898ee5f4e725a45',
+    'linux-x64-sim': 'c15064f7be8cdbc6b16ade624596325fd0e39f3aa5f1adf983d197e7bbf8ebdf',
     'linux-x64': 'b5670fe9aaa93e66e09e1f93eb9db33f771132563b73d73ad9851275540eea59',
+    'linux-arm64-sim': '36751abcb7861c5b36eea806b92527c9834f7bcaa21c8a794e87fe755e89ef3f',
     'linux-arm64': '6d002a86a488e2a0402e7dd936552cd257d34239b58c67932d3445c9ef76d55f',
+    'mac-x64-sim': 'bc3759aab9391f406adb6ae8ca0376b8c2027968d7f7acc19d0c257e143b1c7e',
     'mac-x64': 'f9a81767291d38863ef7fba1d64d4511b717f544672675ca6c21a74101d01bb6',
+    'mac-arm64-sim': '8b19debc282bc0317261464966493eca3fcf4557b30f378324896a2b31ba6dad',
     'mac-arm64': '7563aa53c8cd5a3b7b0bb2c219e334cdf1f735bf212f628b7294c277423e2408',
+    'windows-x64-sim': '39421ff596a127c23b3e4736a2c13bec1004201904a8d8fa49541fb9a7ce37be',
     'windows-x64': 'd0130dc8c384535189bbd01993060e222843d79a02e68eafc29aebf4ca3f7fd5',
+    'windows-arm64-sim': 'e1677b9f172607d485bcc1227f2afd37b093601fe19b30d378663e39fe0a1c6d',
     'windows-arm64': '715126e8dcd5c0290361b5599bd4200fe4587b852d93da684c5dbcfddf4a6b0f',
 }
 
@@ -74,6 +80,9 @@ def build_argument_parser() -> argparse.ArgumentParser:
     source_group.add_argument('-p', '--platform',
                               help='WebRTC prebuild platform to fetch artifacts for')
 
+    parser.add_argument('--for-simulator', action='store_true',
+                        help='get WebRTC prebuild for a Desktop platform')
+
     parser.add_argument('-c', '--checksum',
                         help='sha256sum of the unexpanded artifact archive (can be omitted for standard prebuilds)')
     parser.add_argument('--skip-extract', action='store_true',
@@ -143,10 +152,15 @@ def main() -> None:
         if not args.webrtc_version:
             parser.error(message='--platform requires --webrtc-version')
         platform_name = resolve_platform(args.platform)
+        if platform_name in ["android", "ios"] and args.for_simulator:
+            raise Exception("Simulator artifacts are only for desktop platforms")
+
         build_mode = 'debug' if args.debug else 'release'
-        url = "https://build-artifacts.signal.org/libraries/webrtc-{}-{}-{}.tar.bz2".format(args.webrtc_version, platform_name, build_mode)
+        sim = '-sim' if args.for_simulator else ''
+        url = "https://build-artifacts.signal.org/libraries/webrtc-{}-{}-{}{}.tar.bz2".format(args.webrtc_version, platform_name, build_mode, sim)
+        prebuild_platform_name = "{}{}".format(platform_name, sim)
         if not checksum:
-            checksum = PREBUILD_CHECKSUMS[platform_name]
+            checksum = PREBUILD_CHECKSUMS[prebuild_platform_name]
 
     if not checksum:
         parser.error(message='missing --checksum')
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 13b5d3ac..2b258020 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -113,6 +113,7 @@ sim = []
 electron = ["neon", "native"]
 native = ["cubeb", "cubeb-core", "windows", "regex"]
 prebuilt_webrtc = ["native"]
+prebuilt_webrtc_sim = ["native", "simnet"]
 simnet = ["injectable_network"]
 injectable_network = []
 sim_http = ["ureq", "rustls"]
diff --git a/src/rust/build.rs b/src/rust/build.rs
index cc3cccbc..ff2ce04d 100644
--- a/src/rust/build.rs
+++ b/src/rust/build.rs
@@ -54,17 +54,28 @@ fn main() {
     // Explicitly state that by depending on build.rs itself, as recommended.
     println!("cargo:rerun-if-changed=build.rs");
 
+    if cfg!(feature = "prebuilt_webrtc") && cfg!(feature = "prebuilt_webrtc_sim") {
+        panic!("Cannot enable both prebuilt_webrtc and prebuilt_webrtc_sim features");
+    }
+
     if cfg!(feature = "native") {
-        let webrtc_dir = if cfg!(feature = "prebuilt_webrtc") {
-            if let Err(e) = fs::create_dir_all(&out_dir) {
-                panic!("Failed to create webrtc out directory: {:?}", e);
-            }
-            fetch_webrtc_artifact(&target_os, &target_arch, &out_dir).unwrap();
-            // Ignore build type since we only have release prebuilts
-            format!("{}/release/obj/", out_dir)
-        } else {
-            format!("{}/{}/obj", out_dir, build_type)
-        };
+        let webrtc_dir =
+            if cfg!(feature = "prebuilt_webrtc") || cfg!(feature = "prebuilt_webrtc_sim") {
+                if let Err(e) = fs::create_dir_all(&out_dir) {
+                    panic!("Failed to create webrtc out directory: {:?}", e);
+                }
+                fetch_webrtc_artifact(
+                    &target_os,
+                    &target_arch,
+                    &out_dir,
+                    cfg!(feature = "prebuilt_webrtc_sim"),
+                )
+                .unwrap();
+                // Ignore build type since we only have release prebuilts
+                format!("{}/release/obj/", out_dir)
+            } else {
+                format!("{}/{}/obj", out_dir, build_type)
+            };
         println!("cargo:rerun-if-changed={}", webrtc_dir);
         println!("cargo:rerun-if-changed={}", config_dir());
         println!("cargo:rustc-link-search=native={}", webrtc_dir);
@@ -134,6 +145,7 @@ fn fetch_webrtc_artifact(
     target_os: &str,
     target_arch: &str,
     artifact_out_dir: &str,
+    target_sim: bool,
 ) -> Result<(), String> {
     let fetch_script = format!("{}/bin/fetch-artifact", project_dir());
     let platform = format!("{}-{}", target_os, target_arch);
@@ -142,12 +154,17 @@ fn fetch_webrtc_artifact(
         platform, artifact_out_dir
     );
 
-    let output = Command::new("bash")
+    let mut command = Command::new("bash");
+    command
         .current_dir(project_dir())
         .env("OUTPUT_DIR", artifact_out_dir)
         .arg(fetch_script)
         .arg("--platform")
-        .arg(platform)
+        .arg(platform);
+    if target_sim {
+        command.arg("--for-simulator");
+    }
+    let output = command
         .output()
         .expect("bin/fetch-artifact failed to complete");
 

From 5613fdac2dad0deef7a3a2a148d7d47e4622e426 Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Mon, 2 Dec 2024 16:42:44 -0500
Subject: [PATCH 25/29] Disable voice processing on inputs for macos

---
 Cargo.lock                                 | 12 ++++----
 acknowledgments/acknowledgments.html       |  6 ++--
 acknowledgments/acknowledgments.md         |  2 +-
 acknowledgments/acknowledgments.plist      |  2 +-
 src/rust/Cargo.toml                        |  4 +--
 src/rust/src/webrtc/audio_device_module.rs | 35 +++++++---------------
 6 files changed, 24 insertions(+), 37 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index bc19e3b0..125835b2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -758,18 +758,18 @@ dependencies = [
 
 [[package]]
 name = "cubeb"
-version = "0.17.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a342fa08004b09f8301c328cd714004c33fe718d311aaf215c372ff52f5ad37"
+checksum = "3c5fafe45094c126e9134c08c16f5fe8ff6001d7308b96624a6d9dfb3776470b"
 dependencies = [
  "cubeb-core",
 ]
 
 [[package]]
 name = "cubeb-core"
-version = "0.17.0"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b5dfbff806ffe83a09663e65dc9d00edae4da7977ced9dc6b44c2b036e33d3"
+checksum = "cc5b2403820cb2ca2ece53e5c7d524310006bf447fa4306c950db7eebe7a45f2"
 dependencies = [
  "bitflags 1.3.2",
  "cc",
@@ -778,9 +778,9 @@ dependencies = [
 
 [[package]]
 name = "cubeb-sys"
-version = "0.17.1"
+version = "0.18.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa5ea151dfe7491d9f7bc109fd60b660b3c3dd6d50dc9cbbd1a0327624f5fd14"
+checksum = "2265920e8485e352ce5f8aa6bd84b75894f3c15d9e8ee044061184ee14a3d955"
 dependencies = [
  "cmake",
  "pkg-config",
diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html
index c8ede87e..65917d87 100644
--- a/acknowledgments/acknowledgments.html
+++ b/acknowledgments/acknowledgments.html
@@ -1540,9 +1540,9 @@ 

Used by:

ISC License

Used by:

Copyright © 2017 Mozilla Foundation
 
diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md
index 98c34f56..2e3f71f8 100644
--- a/acknowledgments/acknowledgments.md
+++ b/acknowledgments/acknowledgments.md
@@ -1453,7 +1453,7 @@ THIS SOFTWARE.
 
 ```
 
-## cubeb-core 0.17.0, cubeb-sys 0.17.1, cubeb 0.17.0
+## cubeb-core 0.18.0, cubeb-sys 0.18.0, cubeb 0.18.0
 
 ```
 Copyright © 2017 Mozilla Foundation
diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist
index 9647f69b..76c3cc19 100644
--- a/acknowledgments/acknowledgments.plist
+++ b/acknowledgments/acknowledgments.plist
@@ -1518,7 +1518,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 			License
 			ISC License
 			Title
-			cubeb-core 0.17.0, cubeb-sys 0.17.1, cubeb 0.17.0
+			cubeb-core 0.18.0, cubeb-sys 0.18.0, cubeb 0.18.0
 			Type
 			PSGroupSpecifier
 		
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
index 2b258020..1878d5f4 100644
--- a/src/rust/Cargo.toml
+++ b/src/rust/Cargo.toml
@@ -95,8 +95,8 @@ call_protobuf = { path = "../../protobuf", package = "protobuf"}
 mrp = { path = "../../mrp" }
 
 # Optional, needed by "native" feature
-cubeb = {  version = "0.17.0", optional = true }
-cubeb-core = {  version = "0.17.0", optional = true }
+cubeb = {  version = "0.18.0", optional = true }
+cubeb-core = {  version = "0.18.0", optional = true }
 regex = {  version = "1.11.1", optional = true }
 
 [target.'cfg(windows)'.dependencies]
diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs
index df6e7180..26266e59 100644
--- a/src/rust/src/webrtc/audio_device_module.rs
+++ b/src/rust/src/webrtc/audio_device_module.rs
@@ -8,7 +8,7 @@ use crate::webrtc::audio_device_module_utils::{copy_and_truncate_string, DeviceC
 use crate::webrtc::ffi::audio_device_module::RffiAudioTransport;
 use anyhow::anyhow;
 use cubeb::{Context, DeviceId, DeviceType, MonoFrame, Stream, StreamPrefs};
-use cubeb_core::{log_enabled, set_logging, InputProcessingParams, LogLevel};
+use cubeb_core::{log_enabled, set_logging, LogLevel};
 use lazy_static::lazy_static;
 use regex::Regex;
 use std::collections::{HashMap, VecDeque};
@@ -694,13 +694,19 @@ impl AudioDeviceModule {
             error!("Tried to init recording without a ctx");
             return -1;
         };
-        let params = cubeb::StreamParamsBuilder::new()
+        let builder = cubeb::StreamParamsBuilder::new()
             .format(STREAM_FORMAT)
             .rate(SAMPLE_FREQUENCY)
             .channels(NUM_CHANNELS)
-            .layout(cubeb::ChannelLayout::MONO)
-            .prefs(StreamPrefs::VOICE)
-            .take();
+            .layout(cubeb::ChannelLayout::MONO);
+        // On Mac, the AEC pipeline runs at 24kHz (FB15839727 tracks this). For now,
+        // disable it.
+        let params = if cfg!(not(target_os = "macos")) {
+            builder.prefs(StreamPrefs::VOICE)
+        } else {
+            builder
+        }
+        .take();
         let mut builder = cubeb::StreamBuilder::::new();
         let transport = Arc::clone(&self.audio_transport);
         let min_latency = ctx.min_latency(¶ms).unwrap_or_else(|e| {
@@ -760,25 +766,6 @@ impl AudioDeviceModule {
             });
         match builder.init(ctx) {
             Ok(stream) => {
-                match ctx.supported_input_processing_params() {
-                    Ok(params) => {
-                        // With cubeb-coreaudio-rs, the VPIO input is inaudible without these settings.
-                        // See https://github.com/mozilla/cubeb-coreaudio-rs/issues/239#issuecomment-2430361990
-                        info!("Available input processing params: {:?}", params);
-                        let mut desired_params = InputProcessingParams::empty();
-                        if params.contains(InputProcessingParams::AUTOMATIC_GAIN_CONTROL) {
-                            desired_params |= InputProcessingParams::AUTOMATIC_GAIN_CONTROL;
-                        }
-                        // With the coreaudio-rust backend, these settings must be set together.
-                        if params.contains(InputProcessingParams::ECHO_CANCELLATION | InputProcessingParams::NOISE_SUPPRESSION) {
-                           desired_params |= InputProcessingParams::ECHO_CANCELLATION | InputProcessingParams::NOISE_SUPPRESSION;
-                        }
-                        if let Err(e) = stream.set_input_processing_params(desired_params) {
-                            error!("couldn't set input params: {:?}", e);
-                        }
-                    }
-                    Err(e) => warn!("Failed to get supported input processing parameters; proceeding without: {}", e)
-                }
                 self.input_stream = Some(stream);
                 0
             }

From 74619b69cefc90afac6492596aa23f23367bfff1 Mon Sep 17 00:00:00 2001
From: Miriam Zimmerman 
Date: Tue, 3 Dec 2024 15:45:26 -0500
Subject: [PATCH 26/29] Cache output of enumerate_devices

---
 src/rust/src/webrtc/audio_device_module.rs    | 230 +++++++++++++-----
 .../src/webrtc/audio_device_module_utils.rs   |  72 ++++--
 2 files changed, 225 insertions(+), 77 deletions(-)

diff --git a/src/rust/src/webrtc/audio_device_module.rs b/src/rust/src/webrtc/audio_device_module.rs
index 26266e59..389cc44c 100644
--- a/src/rust/src/webrtc/audio_device_module.rs
+++ b/src/rust/src/webrtc/audio_device_module.rs
@@ -6,14 +6,17 @@
 use crate::webrtc;
 use crate::webrtc::audio_device_module_utils::{copy_and_truncate_string, DeviceCollectionWrapper};
 use crate::webrtc::ffi::audio_device_module::RffiAudioTransport;
-use anyhow::anyhow;
+use anyhow::{anyhow, bail};
 use cubeb::{Context, DeviceId, DeviceType, MonoFrame, Stream, StreamPrefs};
 use cubeb_core::{log_enabled, set_logging, LogLevel};
 use lazy_static::lazy_static;
 use regex::Regex;
 use std::collections::{HashMap, VecDeque};
-use std::ffi::{c_uchar, c_void, CStr, CString};
-use std::sync::{Arc, Mutex, RwLock};
+use std::ffi::{c_uchar, c_void, CStr};
+use std::sync::{
+    atomic::{AtomicBool, Ordering},
+    Arc, Mutex, RwLock,
+};
 use std::time::{Duration, Instant};
 #[cfg(target_os = "windows")]
 use windows::Win32::System::Com;
@@ -65,12 +68,22 @@ pub struct AudioDeviceModule {
     audio_transport: Arc>,
     cubeb_ctx: Option,
     initialized: bool,
+    // Note that the DeviceIds must not outlive the cubeb_ctx.
     playout_device: Option,
     recording_device: Option,
+    // Note that the streams must not outlive the cubeb_ctx.
     output_stream: Option>,
     input_stream: Option>,
     playing: bool,
     recording: bool,
+    // Note that the caches must not outlive the cubeb_ctx.
+    input_device_cache: Option,
+    output_device_cache: Option,
+    // Flags to track whether we need to refresh caches.
+    // As these are shared with threads in cubeb, we create them once at ADM init
+    // and never free them.
+    pending_input_device_refresh: &'static AtomicBool,
+    pending_output_device_refresh: &'static AtomicBool,
 }
 
 impl Default for AudioDeviceModule {
@@ -87,6 +100,12 @@ impl Default for AudioDeviceModule {
             input_stream: None,
             playing: false,
             recording: false,
+            input_device_cache: None,
+            output_device_cache: None,
+            // Start these both as true to request a cache refresh, and leak them for reasons
+            // mentioned at the struct declaration site.
+            pending_input_device_refresh: Box::leak(Box::new(AtomicBool::new(true))),
+            pending_output_device_refresh: Box::leak(Box::new(AtomicBool::new(true))),
         }
     }
 }
@@ -108,7 +127,7 @@ const ADM_MAX_DEVICE_NAME_SIZE: usize = 128;
 const ADM_MAX_GUID_SIZE: usize = 128;
 
 /// Arbitrary string to uniquely identify ringrtc for creating the cubeb object.
-const ADM_CONTEXT: &str = "ringrtc";
+const ADM_CONTEXT: &CStr = c"ringrtc";
 
 const SAMPLE_FREQUENCY: u32 = 48_000;
 // Target sample latency. The actual sample latency will
@@ -213,6 +232,34 @@ impl AudioDeviceModule {
         0
     }
 
+    /// Safety: Must be called with a valid |flag| pointer. (NULL is okay.)
+    unsafe extern "C" fn device_changed(_ctx: *mut cubeb::ffi::cubeb, flag: *mut c_void) {
+        // Flag that an update is needed; this will be processed on the next enumerate_devices call.
+        if let Some(b) = (flag as *mut AtomicBool).as_ref() {
+            b.store(true, Ordering::SeqCst)
+        }
+    }
+
+    fn register_device_collection_changed(
+        &mut self,
+        device_type: DeviceType,
+        flag: &'static AtomicBool,
+    ) -> anyhow::Result<()> {
+        let ctx = match &self.cubeb_ctx {
+            Some(ctx) => ctx,
+            None => bail!("Cannot register device changed callback without a ctx"),
+        };
+        unsafe {
+            // Safety: |callback| will remain a valid pointer for the lifetime of the program,
+            // as will |flag| (since it's static)
+            Ok(ctx.register_device_collection_changed(
+                device_type,
+                Some(AudioDeviceModule::device_changed),
+                flag.as_ptr() as *mut c_void,
+            )?)
+        }
+    }
+
     // Main initialization and termination
     pub fn init(&mut self) -> i32 {
         // Don't bother re-initializing.
@@ -238,8 +285,7 @@ impl AudioDeviceModule {
                 return -1;
             }
         }
-        let ctx_name = CString::new(ADM_CONTEXT).unwrap();
-        match Context::init(Some(ctx_name.as_c_str()), None) {
+        match Context::init(Some(ADM_CONTEXT), None) {
             Ok(ctx) => {
                 info!(
                     "Successfully initialized cubeb backend {}",
@@ -247,13 +293,33 @@ impl AudioDeviceModule {
                 );
                 self.cubeb_ctx = Some(ctx);
                 self.initialized = true;
-                0
             }
             Err(e) => {
                 error!("Failed to initialize: {}", e);
-                -1
+                return -1;
             }
         }
+        if let Err(e) = self.register_device_collection_changed(
+            DeviceType::INPUT,
+            self.pending_input_device_refresh,
+        ) {
+            error!("Failed to register input device callback: {}", e);
+            return -1;
+        }
+        if let Err(e) = self.register_device_collection_changed(
+            DeviceType::OUTPUT,
+            self.pending_output_device_refresh,
+        ) {
+            error!("Failed to register input device callback: {}", e);
+            return -1;
+        }
+        // Caches are not set up, so request a refresh.
+        self.pending_input_device_refresh
+            .store(true, Ordering::SeqCst);
+        self.pending_output_device_refresh
+            .store(true, Ordering::SeqCst);
+        self.initialized = true;
+        0
     }
 
     pub fn backend_name(&self) -> Option {
@@ -269,9 +335,37 @@ impl AudioDeviceModule {
         if self.playing {
             self.stop_playout();
         }
-        // Cause these to Drop
+        // Cause these to Drop.
         self.input_stream = None;
         self.output_stream = None;
+        // Ensure these are not reused.
+        self.playout_device = None;
+        self.recording_device = None;
+        self.input_device_cache = None;
+        self.output_device_cache = None;
+        if let Some(ctx) = &self.cubeb_ctx {
+            // Clear callbacks.
+            unsafe {
+                // Safety: We are calling this with None, which will unset the callback,
+                // so passing null is safe.
+                if let Err(e) = ctx.register_device_collection_changed(
+                    DeviceType::INPUT,
+                    None,
+                    std::ptr::null_mut(),
+                ) {
+                    warn!("failed to reset input callback: {}", e);
+                }
+                if let Err(e) = ctx.register_device_collection_changed(
+                    DeviceType::OUTPUT,
+                    None,
+                    std::ptr::null_mut(),
+                ) {
+                    warn!("failed to reset output callback: {}", e);
+                }
+            }
+        }
+        // Invalidate the ctx (note that any references to it, like `DeviceId`s,
+        // must have already been dropped).
         self.cubeb_ctx = None;
         self.initialized = false;
         #[cfg(target_os = "windows")]
@@ -288,15 +382,52 @@ impl AudioDeviceModule {
         self.initialized
     }
 
+    fn refresh_device_cache(&mut self, device_type: DeviceType) -> anyhow::Result<()> {
+        let ctx = match &self.cubeb_ctx {
+            Some(ctx) => ctx,
+            None => bail!("cannot refresh device cache without a ctx"),
+        };
+        let devices = ctx.enumerate_devices(device_type)?;
+        for device in devices.iter() {
+            info!(
+                "{:?} device: ({})",
+                device_type,
+                AudioDeviceModule::device_str(device)
+            );
+        }
+        let collection = DeviceCollectionWrapper::new(devices);
+        match device_type {
+            DeviceType::INPUT => self.input_device_cache = Some(collection),
+            DeviceType::OUTPUT => self.output_device_cache = Some(collection),
+            _ => bail!("Bad device type {:?}", device_type),
+        }
+        Ok(())
+    }
+
     fn enumerate_devices(
-        &self,
+        &mut self,
         device_type: DeviceType,
-    ) -> anyhow::Result {
-        match &self.cubeb_ctx {
-            Some(ctx) => Ok(DeviceCollectionWrapper::new(
-                ctx.enumerate_devices(device_type)?,
-            )),
-            None => Err(anyhow!("Cannot enumerate devices without a cubeb ctx"))?,
+    ) -> anyhow::Result<&DeviceCollectionWrapper> {
+        let pending_update = match device_type {
+            DeviceType::INPUT => self
+                .pending_input_device_refresh
+                .swap(false, Ordering::SeqCst),
+            DeviceType::OUTPUT => self
+                .pending_output_device_refresh
+                .swap(false, Ordering::SeqCst),
+            _ => bail!("Bad device type {:?}", device_type),
+        };
+        if pending_update {
+            self.refresh_device_cache(device_type)?;
+        }
+        let collection = match device_type {
+            DeviceType::INPUT => self.input_device_cache.as_ref(),
+            DeviceType::OUTPUT => self.output_device_cache.as_ref(),
+            _ => bail!("Bad device type {:?}", device_type),
+        };
+        match collection {
+            Some(c) => Ok(c),
+            None => Err(anyhow!("No {:?} collection found", device_type)),
         }
     }
 
@@ -345,7 +476,7 @@ impl AudioDeviceModule {
     }
 
     // Device enumeration
-    pub fn playout_devices(&self) -> i16 {
+    pub fn playout_devices(&mut self) -> i16 {
         match self.enumerate_devices(DeviceType::OUTPUT) {
             Ok(device_collection) => device_collection.count().try_into().unwrap_or(-1),
             Err(e) => {
@@ -355,7 +486,7 @@ impl AudioDeviceModule {
         }
     }
 
-    pub fn recording_devices(&self) -> i16 {
+    pub fn recording_devices(&mut self) -> i16 {
         match self.enumerate_devices(DeviceType::INPUT) {
             Ok(device_collection) => device_collection.count().try_into().unwrap_or(-1),
             Err(e) => {
@@ -367,12 +498,12 @@ impl AudioDeviceModule {
 
     fn copy_name_and_id(
         index: u16,
-        devices: DeviceCollectionWrapper,
+        devices: &DeviceCollectionWrapper,
         name_out: webrtc::ptr::Borrowed,
         guid_out: webrtc::ptr::Borrowed,
     ) -> anyhow::Result<()> {
         if let Some(d) = devices.get(index.into()) {
-            if let Some(name) = d.friendly_name() {
+            if let Some(name) = &d.friendly_name {
                 let mut name_copy = name.to_string();
                 // TODO(mutexlox): Localize these strings.
                 #[cfg(not(target_os = "windows"))]
@@ -391,7 +522,7 @@ impl AudioDeviceModule {
             } else {
                 return Err(anyhow!("Could not get device name"));
             }
-            if let Some(id) = d.device_id() {
+            if let Some(id) = &d.device_id {
                 copy_and_truncate_string(id, guid_out, ADM_MAX_GUID_SIZE)?;
             } else {
                 return Err(anyhow!("Could not get device ID"));
@@ -407,7 +538,7 @@ impl AudioDeviceModule {
     }
 
     pub fn playout_device_name(
-        &self,
+        &mut self,
         index: u16,
         name_out: webrtc::ptr::Borrowed,
         guid_out: webrtc::ptr::Borrowed,
@@ -430,7 +561,7 @@ impl AudioDeviceModule {
     }
 
     pub fn recording_device_name(
-        &self,
+        &mut self,
         index: u16,
         name_out: webrtc::ptr::Borrowed,
         guid_out: webrtc::ptr::Borrowed,
@@ -455,26 +586,17 @@ impl AudioDeviceModule {
     // Device selection
     pub fn set_playout_device(&mut self, index: u16) -> i32 {
         let device = match self.enumerate_devices(DeviceType::OUTPUT) {
-            Ok(devices) => {
-                for device in devices.iter() {
-                    info!(
-                        "Playout device: ({})",
-                        AudioDeviceModule::device_str(device)
+            Ok(devices) => match devices.get(index as usize) {
+                Some(device) => device.devid,
+                None => {
+                    error!(
+                        "Invalid playout device index {} requested (len {})",
+                        index,
+                        devices.count()
                     );
+                    return -1;
                 }
-
-                match devices.get(index as usize) {
-                    Some(device) => device.devid(),
-                    None => {
-                        error!(
-                            "Invalid device index {} requested (len {})",
-                            index,
-                            devices.count()
-                        );
-                        return -1;
-                    }
-                }
-            }
+            },
             Err(e) => {
                 error!("failed to enumerate devices for playout device: {}", e);
                 return -1;
@@ -495,25 +617,17 @@ impl AudioDeviceModule {
 
     pub fn set_recording_device(&mut self, index: u16) -> i32 {
         let device = match self.enumerate_devices(DeviceType::INPUT) {
-            Ok(devices) => {
-                for device in devices.iter() {
-                    info!(
-                        "Recording device: ({})",
-                        AudioDeviceModule::device_str(device)
+            Ok(devices) => match devices.get(index as usize) {
+                Some(device) => device.devid,
+                None => {
+                    error!(
+                        "Invalid recording device index {} requested (len {})",
+                        index,
+                        devices.count()
                     );
+                    return -1;
                 }
-                match devices.get(index as usize) {
-                    Some(device) => device.devid(),
-                    None => {
-                        error!(
-                            "Invalid device index {} requested (len {})",
-                            index,
-                            devices.count()
-                        );
-                        return -1;
-                    }
-                }
-            }
+            },
             Err(e) => {
                 error!("failed to enumerate devices for playout device: {}", e);
                 return -1;
diff --git a/src/rust/src/webrtc/audio_device_module_utils.rs b/src/rust/src/webrtc/audio_device_module_utils.rs
index 90d60970..01fdccbf 100644
--- a/src/rust/src/webrtc/audio_device_module_utils.rs
+++ b/src/rust/src/webrtc/audio_device_module_utils.rs
@@ -8,51 +8,85 @@
 
 use crate::webrtc;
 use anyhow::anyhow;
-use cubeb::{DeviceCollection, DeviceInfo, DeviceState};
+use cubeb::{DeviceCollection, DeviceState};
 use cubeb_core::DevicePref;
-use std::ffi::{c_uchar, CString};
+#[cfg(target_os = "linux")]
+use cubeb_core::DeviceType;
+use std::ffi::{c_uchar, c_void, CString};
+
+pub struct MinimalDeviceInfo {
+    pub devid: *const c_void,
+    pub device_id: Option,
+    pub friendly_name: Option,
+    #[cfg(target_os = "linux")]
+    device_type: DeviceType,
+    preferred: DevicePref,
+    state: DeviceState,
+}
 
 /// Wrapper struct for DeviceCollection that handles default devices.
-pub struct DeviceCollectionWrapper<'a> {
-    device_collection: DeviceCollection<'a>,
+///
+/// Rather than storing the DeviceCollection directly, which raises complex
+/// lifetime issues, store just the fields we need.
+///
+/// Note that, in some cases, `devid` may be a pointer to state in the cubeb ctx,
+/// so in no event should this outlive the associated ctx.
+pub struct DeviceCollectionWrapper {
+    device_collection: Vec,
 }
 
 #[cfg(target_os = "linux")]
-fn device_is_monitor(device: &DeviceInfo) -> bool {
-    device.device_type() == cubeb::DeviceType::INPUT
+fn device_is_monitor(device: &MinimalDeviceInfo) -> bool {
+    device.device_type == DeviceType::INPUT
         && device
-            .device_id()
+            .device_id
             .as_ref()
             .map_or(false, |s| s.ends_with(".monitor"))
 }
 
-impl DeviceCollectionWrapper<'_> {
-    pub fn new(device_collection: DeviceCollection<'_>) -> DeviceCollectionWrapper<'_> {
-        DeviceCollectionWrapper { device_collection }
+impl DeviceCollectionWrapper {
+    pub fn new(device_collection: DeviceCollection<'_>) -> DeviceCollectionWrapper {
+        let mut out = Vec::new();
+        for device in device_collection.iter() {
+            out.push(MinimalDeviceInfo {
+                devid: device.devid(),
+                device_id: device.device_id().as_ref().map(|s| s.to_string()),
+                friendly_name: device.friendly_name().as_ref().map(|s| s.to_string()),
+                #[cfg(target_os = "linux")]
+                device_type: device.device_type(),
+                preferred: device.preferred(),
+                state: device.state(),
+            })
+        }
+        DeviceCollectionWrapper {
+            device_collection: out,
+        }
     }
 
     /// Iterate over all Enabled devices (those that are plugged in and not disabled by the OS)
     pub fn iter(
         &self,
-    ) -> std::iter::Filter, fn(&&DeviceInfo) -> bool> {
+    ) -> std::iter::Filter, fn(&&MinimalDeviceInfo) -> bool>
+    {
         self.device_collection
             .iter()
-            .filter(|d| d.state() == DeviceState::Enabled)
+            .filter(|d| d.state == DeviceState::Enabled)
     }
 
     // For linux only, a method that will ignore "monitor" devices.
     #[cfg(target_os = "linux")]
     pub fn iter_non_monitor(
         &self,
-    ) -> std::iter::Filter, fn(&&DeviceInfo) -> bool> {
+    ) -> std::iter::Filter, fn(&&MinimalDeviceInfo) -> bool>
+    {
         self.device_collection
             .iter()
-            .filter(|&d| d.state() == DeviceState::Enabled && !device_is_monitor(d))
+            .filter(|&d| d.state == DeviceState::Enabled && !device_is_monitor(d))
     }
 
     #[cfg(target_os = "windows")]
     /// Get a specified device index, accounting for the two default devices.
-    pub fn get(&self, idx: usize) -> Option<&DeviceInfo> {
+    pub fn get(&self, idx: usize) -> Option<&MinimalDeviceInfo> {
         // 0 should be "default device" and 1 should be "default communications device".
         // Note: On windows, CUBEB_DEVICE_PREF_VOICE will be set for default communications device,
         // and CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION for default device.
@@ -64,17 +98,17 @@ impl DeviceCollectionWrapper<'_> {
         } else if idx == 1 {
             // Find a device that's preferred for VOICE -- device 1 is the "default communications"
             self.iter()
-                .find(|&device| device.preferred().contains(DevicePref::VOICE))
+                .find(|&device| device.preferred.contains(DevicePref::VOICE))
         } else {
             // Find a device that's preferred for MULTIMEDIA -- device 0 is the "default"
             self.iter()
-                .find(|&device| device.preferred().contains(DevicePref::MULTIMEDIA))
+                .find(|&device| device.preferred.contains(DevicePref::MULTIMEDIA))
         }
     }
 
     #[cfg(not(target_os = "windows"))]
     /// Get a specified device index, accounting for the default device.
-    pub fn get(&self, idx: usize) -> Option<&DeviceInfo> {
+    pub fn get(&self, idx: usize) -> Option<&MinimalDeviceInfo> {
         if self.count() == 0 {
             None
         } else if idx > 0 {
@@ -92,7 +126,7 @@ impl DeviceCollectionWrapper<'_> {
             // Even on linux, we do *NOT* filter monitor devices -- if the user specified that as
             // default, we respect it.
             self.iter()
-                .find(|&device| device.preferred().contains(DevicePref::VOICE))
+                .find(|&device| device.preferred.contains(DevicePref::VOICE))
         }
     }
 

From 9eec8cf6795899e0e0f6a17d4e902c160cc96f00 Mon Sep 17 00:00:00 2001
From: Jim Gustafson 
Date: Tue, 3 Dec 2024 17:48:27 -0800
Subject: [PATCH 27/29] Bump version to v2.49.0

---
 CHANGELOG.md                                | 25 +++++++++++++++++++--
 Cargo.lock                                  |  6 ++---
 Cargo.toml                                  |  2 +-
 SignalRingRTC.podspec                       |  2 +-
 acknowledgments/acknowledgments.html        |  6 ++---
 acknowledgments/acknowledgments.md          |  2 +-
 acknowledgments/acknowledgments.plist       |  2 +-
 call_sim/docker/signaling_server/Cargo.lock |  2 +-
 config/version.properties                   |  4 ++--
 src/node/package-lock.json                  |  4 ++--
 src/node/package.json                       |  2 +-
 11 files changed, 39 insertions(+), 18 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1d65e20..4726c586 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,29 @@
 # Changelog
 
+## v2.49.0
+
+- Remove support for unencrypted audio header
+
+- Desktop: New Audio Device Module improvements
+  - Fix broken windows build
+  - Remove redundant ADM creation
+  - Disable voice processing on inputs for macos
+  - Cache output of enumerate_devices
+
+- Update to webrtc 6723b
+  - Enable video layers allocation header extension in group calls
+  - Remove checks for dependency descriptors
+  - Fix some tests and disable others
+
+- Increase priority of non-relay candidates
+
+- Add prebuilt_webrtc_sim feature
+
+- Build improvements
+
 ## v2.48.7
 
-- Desktop:
+- Desktop: New Audio Device Module improvements
   - Logging improvements for ringrtc ADM
   - Use a dedicated runner to build for linux ARM, fixing crash
 
@@ -10,7 +31,7 @@
 
 ## v2.48.6
 
-- Desktop:
+- Desktop: New Audio Device Module improvements
   - Don't show `Monitor of` devices as inputs to match existing ADM behavior
   - Uprev cubeb to 0.17.0 to fix cross-compilation
 
diff --git a/Cargo.lock b/Cargo.lock
index 125835b2..42126aec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1703,7 +1703,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1"
 
 [[package]]
 name = "mrp"
-version = "2.48.7"
+version = "2.49.0"
 dependencies = [
  "anyhow",
  "log",
@@ -2117,7 +2117,7 @@ dependencies = [
 
 [[package]]
 name = "protobuf"
-version = "2.48.7"
+version = "2.49.0"
 dependencies = [
  "prost-build",
  "tonic-build",
@@ -2261,7 +2261,7 @@ dependencies = [
 
 [[package]]
 name = "ringrtc"
-version = "2.48.7"
+version = "2.49.0"
 dependencies = [
  "aes",
  "aes-gcm-siv",
diff --git a/Cargo.toml b/Cargo.toml
index e970b972..c2dcc7c9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ members = [
 ]
 
 [workspace.package]
-version = "2.48.7"
+version = "2.49.0"
 authors = ["Calling Team "]
 
 [patch.crates-io]
diff --git a/SignalRingRTC.podspec b/SignalRingRTC.podspec
index 1211b270..8d8a4a7e 100644
--- a/SignalRingRTC.podspec
+++ b/SignalRingRTC.podspec
@@ -8,7 +8,7 @@
 
 Pod::Spec.new do |s|
   s.name             = "SignalRingRTC"
-  s.version          = "2.48.7"
+  s.version          = "2.49.0"
   s.summary          = "A Swift & Objective-C library used by the Signal iOS app for WebRTC interactions."
 
   s.description      = <<-DESC
diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html
index 65917d87..01085137 100644
--- a/acknowledgments/acknowledgments.html
+++ b/acknowledgments/acknowledgments.html
@@ -733,9 +733,9 @@ 

GNU Affero General Public License v3.0 (synthesized)

Used by:

diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md index 2e3f71f8..43339b89 100644 --- a/acknowledgments/acknowledgments.md +++ b/acknowledgments/acknowledgments.md @@ -669,7 +669,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ``` -## libsignal-core 0.1.0, mrp 2.48.7, protobuf 2.48.7, ringrtc 2.48.7, regex-aot 0.1.0, partial-default-derive 0.1.0 +## libsignal-core 0.1.0, mrp 2.49.0, protobuf 2.49.0, ringrtc 2.49.0, regex-aot 0.1.0, partial-default-derive 0.1.0 ``` GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist index 76c3cc19..b28c45fe 100644 --- a/acknowledgments/acknowledgments.plist +++ b/acknowledgments/acknowledgments.plist @@ -924,7 +924,7 @@ You should also get your employer (if you work as a programmer) or school, if an License GNU Affero General Public License v3.0 Title - libsignal-core 0.1.0, mrp 2.48.7, protobuf 2.48.7, ringrtc 2.48.7, regex-aot 0.1.0, partial-default-derive 0.1.0 + libsignal-core 0.1.0, mrp 2.49.0, protobuf 2.49.0, ringrtc 2.49.0, regex-aot 0.1.0, partial-default-derive 0.1.0 Type PSGroupSpecifier diff --git a/call_sim/docker/signaling_server/Cargo.lock b/call_sim/docker/signaling_server/Cargo.lock index be56866d..6f0e8ac9 100644 --- a/call_sim/docker/signaling_server/Cargo.lock +++ b/call_sim/docker/signaling_server/Cargo.lock @@ -761,7 +761,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.48.7" +version = "2.49.0" dependencies = [ "prost-build", "tonic-build", diff --git a/config/version.properties b/config/version.properties index 7f8bf246..697b41b6 100644 --- a/config/version.properties +++ b/config/version.properties @@ -1,5 +1,5 @@ webrtc.version=6723b ringrtc.version.major=2 -ringrtc.version.minor=48 -ringrtc.version.revision=7 +ringrtc.version.minor=49 +ringrtc.version.revision=0 diff --git a/src/node/package-lock.json b/src/node/package-lock.json index a2c94ffd..2af78b74 100644 --- a/src/node/package-lock.json +++ b/src/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.7", + "version": "2.49.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@signalapp/ringrtc", - "version": "2.48.7", + "version": "2.49.0", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/node/package.json b/src/node/package.json index 54777b5b..e249e57b 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -1,6 +1,6 @@ { "name": "@signalapp/ringrtc", - "version": "2.48.7", + "version": "2.49.0", "description": "Signal Messenger voice and video calling library.", "main": "dist/index.js", "types": "dist/index.d.ts", From 517e1c963b5b04a455db66f8450b4c91892db362 Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Mon, 9 Dec 2024 13:11:01 -0500 Subject: [PATCH 28/29] Fix occasional-crash bug. --- .../src/webrtc/peer_connection_factory.rs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/rust/src/webrtc/peer_connection_factory.rs b/src/rust/src/webrtc/peer_connection_factory.rs index c8c04159..f1f82992 100644 --- a/src/rust/src/webrtc/peer_connection_factory.rs +++ b/src/rust/src/webrtc/peer_connection_factory.rs @@ -205,17 +205,20 @@ impl AudioConfig { }; #[cfg(all(not(feature = "sim"), feature = "native"))] - let (adm_borrowed, backend_name) = { - let mut adm = AudioDeviceModule::new(); - // Initialize the ADM here. This isn't strictly necessary, but allows - // us to log the backend name (e.g. audiounit vs audiounit-rust). - adm.init(); - let backend_name = adm.backend_name(); - ( - webrtc::ptr::Borrowed::from_ptr(Box::into_raw(Box::new(adm))).to_void(), - backend_name, - ) - }; + let (adm_borrowed, backend_name) = + if self.audio_device_module_type == RffiAudioDeviceModuleType::RingRtc { + let mut adm = AudioDeviceModule::new(); + // Initialize the ADM here. This isn't strictly necessary, but allows + // us to log the backend name (e.g. audiounit vs audiounit-rust). + adm.init(); + let backend_name = adm.backend_name(); + ( + webrtc::ptr::Borrowed::from_ptr(Box::into_raw(Box::new(adm))).to_void(), + backend_name, + ) + } else { + (webrtc::ptr::Borrowed::null(), None) + }; #[cfg(any(feature = "sim", not(feature = "native")))] let backend_name = None; From 10c949f8cd30db6dc228d7e6d68871e2cae93e9c Mon Sep 17 00:00:00 2001 From: Miriam Zimmerman Date: Mon, 9 Dec 2024 13:42:36 -0500 Subject: [PATCH 29/29] Bump version to v2.49.1 --- CHANGELOG.md | 4 ++++ Cargo.lock | 6 +++--- Cargo.toml | 2 +- SignalRingRTC.podspec | 2 +- acknowledgments/acknowledgments.html | 6 +++--- acknowledgments/acknowledgments.md | 2 +- acknowledgments/acknowledgments.plist | 2 +- call_sim/docker/signaling_server/Cargo.lock | 2 +- config/version.properties | 2 +- src/node/package-lock.json | 4 ++-- src/node/package.json | 2 +- 11 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4726c586..41d2196f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v2.49.1 + +Desktop: Fix crash caused by new Audio Device Module + ## v2.49.0 - Remove support for unencrypted audio header diff --git a/Cargo.lock b/Cargo.lock index 42126aec..dd5cdb8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1703,7 +1703,7 @@ checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" [[package]] name = "mrp" -version = "2.49.0" +version = "2.49.1" dependencies = [ "anyhow", "log", @@ -2117,7 +2117,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.49.0" +version = "2.49.1" dependencies = [ "prost-build", "tonic-build", @@ -2261,7 +2261,7 @@ dependencies = [ [[package]] name = "ringrtc" -version = "2.49.0" +version = "2.49.1" dependencies = [ "aes", "aes-gcm-siv", diff --git a/Cargo.toml b/Cargo.toml index c2dcc7c9..1ee33734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ members = [ ] [workspace.package] -version = "2.49.0" +version = "2.49.1" authors = ["Calling Team "] [patch.crates-io] diff --git a/SignalRingRTC.podspec b/SignalRingRTC.podspec index 8d8a4a7e..672d14cd 100644 --- a/SignalRingRTC.podspec +++ b/SignalRingRTC.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "SignalRingRTC" - s.version = "2.49.0" + s.version = "2.49.1" s.summary = "A Swift & Objective-C library used by the Signal iOS app for WebRTC interactions." s.description = <<-DESC diff --git a/acknowledgments/acknowledgments.html b/acknowledgments/acknowledgments.html index 01085137..4c23e369 100644 --- a/acknowledgments/acknowledgments.html +++ b/acknowledgments/acknowledgments.html @@ -733,9 +733,9 @@

GNU Affero General Public License v3.0 (synthesized)

Used by:

diff --git a/acknowledgments/acknowledgments.md b/acknowledgments/acknowledgments.md index 43339b89..f63f7d2e 100644 --- a/acknowledgments/acknowledgments.md +++ b/acknowledgments/acknowledgments.md @@ -669,7 +669,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see ``` -## libsignal-core 0.1.0, mrp 2.49.0, protobuf 2.49.0, ringrtc 2.49.0, regex-aot 0.1.0, partial-default-derive 0.1.0 +## libsignal-core 0.1.0, mrp 2.49.1, protobuf 2.49.1, ringrtc 2.49.1, regex-aot 0.1.0, partial-default-derive 0.1.0 ``` GNU AFFERO GENERAL PUBLIC LICENSE diff --git a/acknowledgments/acknowledgments.plist b/acknowledgments/acknowledgments.plist index b28c45fe..14622393 100644 --- a/acknowledgments/acknowledgments.plist +++ b/acknowledgments/acknowledgments.plist @@ -924,7 +924,7 @@ You should also get your employer (if you work as a programmer) or school, if an License GNU Affero General Public License v3.0 Title - libsignal-core 0.1.0, mrp 2.49.0, protobuf 2.49.0, ringrtc 2.49.0, regex-aot 0.1.0, partial-default-derive 0.1.0 + libsignal-core 0.1.0, mrp 2.49.1, protobuf 2.49.1, ringrtc 2.49.1, regex-aot 0.1.0, partial-default-derive 0.1.0 Type PSGroupSpecifier diff --git a/call_sim/docker/signaling_server/Cargo.lock b/call_sim/docker/signaling_server/Cargo.lock index 6f0e8ac9..22e566be 100644 --- a/call_sim/docker/signaling_server/Cargo.lock +++ b/call_sim/docker/signaling_server/Cargo.lock @@ -761,7 +761,7 @@ dependencies = [ [[package]] name = "protobuf" -version = "2.49.0" +version = "2.49.1" dependencies = [ "prost-build", "tonic-build", diff --git a/config/version.properties b/config/version.properties index 697b41b6..56de12c6 100644 --- a/config/version.properties +++ b/config/version.properties @@ -2,4 +2,4 @@ webrtc.version=6723b ringrtc.version.major=2 ringrtc.version.minor=49 -ringrtc.version.revision=0 +ringrtc.version.revision=1 diff --git a/src/node/package-lock.json b/src/node/package-lock.json index 2af78b74..e9155a5d 100644 --- a/src/node/package-lock.json +++ b/src/node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@signalapp/ringrtc", - "version": "2.49.0", + "version": "2.49.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@signalapp/ringrtc", - "version": "2.49.0", + "version": "2.49.1", "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/src/node/package.json b/src/node/package.json index e249e57b..b0a125a0 100644 --- a/src/node/package.json +++ b/src/node/package.json @@ -1,6 +1,6 @@ { "name": "@signalapp/ringrtc", - "version": "2.49.0", + "version": "2.49.1", "description": "Signal Messenger voice and video calling library.", "main": "dist/index.js", "types": "dist/index.d.ts",