summaryrefslogtreecommitdiffstats
path: root/third_party/rust/audioipc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/audioipc-client/.cargo-checksum.json1
-rw-r--r--third_party/rust/audioipc-client/Cargo.toml23
-rw-r--r--third_party/rust/audioipc-client/cbindgen.toml28
-rw-r--r--third_party/rust/audioipc-client/src/context.rs431
-rw-r--r--third_party/rust/audioipc-client/src/lib.rs83
-rw-r--r--third_party/rust/audioipc-client/src/send_recv.rs73
-rw-r--r--third_party/rust/audioipc-client/src/stream.rs422
-rw-r--r--third_party/rust/audioipc-server/.cargo-checksum.json1
-rw-r--r--third_party/rust/audioipc-server/Cargo.toml28
-rw-r--r--third_party/rust/audioipc-server/cbindgen.toml28
-rw-r--r--third_party/rust/audioipc-server/src/lib.rs208
-rw-r--r--third_party/rust/audioipc-server/src/server.rs904
-rw-r--r--third_party/rust/audioipc/.cargo-checksum.json1
-rw-r--r--third_party/rust/audioipc/Cargo.toml49
-rw-r--r--third_party/rust/audioipc/build.rs5
-rw-r--r--third_party/rust/audioipc/src/async_msg.rs194
-rw-r--r--third_party/rust/audioipc/src/cmsg.rs180
-rw-r--r--third_party/rust/audioipc/src/cmsghdr.c23
-rw-r--r--third_party/rust/audioipc/src/codec.rs187
-rw-r--r--third_party/rust/audioipc/src/core.rs85
-rw-r--r--third_party/rust/audioipc/src/errors.rs18
-rw-r--r--third_party/rust/audioipc/src/framing.rs396
-rw-r--r--third_party/rust/audioipc/src/lib.rs207
-rw-r--r--third_party/rust/audioipc/src/messages.rs555
-rw-r--r--third_party/rust/audioipc/src/messagestream_unix.rs106
-rw-r--r--third_party/rust/audioipc/src/messagestream_win.rs111
-rw-r--r--third_party/rust/audioipc/src/msg.rs115
-rw-r--r--third_party/rust/audioipc/src/rpc/client/mod.rs162
-rw-r--r--third_party/rust/audioipc/src/rpc/client/proxy.rs136
-rw-r--r--third_party/rust/audioipc/src/rpc/driver.rs171
-rw-r--r--third_party/rust/audioipc/src/rpc/mod.rs36
-rw-r--r--third_party/rust/audioipc/src/rpc/server.rs184
-rw-r--r--third_party/rust/audioipc/src/shm.rs334
-rw-r--r--third_party/rust/audioipc/src/tokio_named_pipes.rs155
-rw-r--r--third_party/rust/audioipc/src/tokio_uds_stream.rs361
-rw-r--r--third_party/rust/audioipc2-client/.cargo-checksum.json1
-rw-r--r--third_party/rust/audioipc2-client/Cargo.toml20
-rw-r--r--third_party/rust/audioipc2-client/cbindgen.toml28
-rw-r--r--third_party/rust/audioipc2-client/src/context.rs385
-rw-r--r--third_party/rust/audioipc2-client/src/lib.rs81
-rw-r--r--third_party/rust/audioipc2-client/src/send_recv.rs73
-rw-r--r--third_party/rust/audioipc2-client/src/stream.rs342
-rw-r--r--third_party/rust/audioipc2-server/.cargo-checksum.json1
-rw-r--r--third_party/rust/audioipc2-server/Cargo.toml26
-rw-r--r--third_party/rust/audioipc2-server/cbindgen.toml28
-rw-r--r--third_party/rust/audioipc2-server/src/lib.rs221
-rw-r--r--third_party/rust/audioipc2-server/src/server.rs902
-rw-r--r--third_party/rust/audioipc2/.cargo-checksum.json1
-rw-r--r--third_party/rust/audioipc2/Cargo.toml55
-rw-r--r--third_party/rust/audioipc2/benches/serialization.rs93
-rw-r--r--third_party/rust/audioipc2/build.rs7
-rw-r--r--third_party/rust/audioipc2/src/codec.rs198
-rw-r--r--third_party/rust/audioipc2/src/errors.rs18
-rw-r--r--third_party/rust/audioipc2/src/ipccore.rs917
-rw-r--r--third_party/rust/audioipc2/src/lib.rs214
-rw-r--r--third_party/rust/audioipc2/src/messages.rs632
-rw-r--r--third_party/rust/audioipc2/src/rpccore.rs340
-rw-r--r--third_party/rust/audioipc2/src/shm.rs334
-rw-r--r--third_party/rust/audioipc2/src/sys/mod.rs77
-rw-r--r--third_party/rust/audioipc2/src/sys/unix/cmsg.rs103
-rw-r--r--third_party/rust/audioipc2/src/sys/unix/cmsghdr.c23
-rw-r--r--third_party/rust/audioipc2/src/sys/unix/mod.rs126
-rw-r--r--third_party/rust/audioipc2/src/sys/unix/msg.rs81
-rw-r--r--third_party/rust/audioipc2/src/sys/windows/mod.rs102
64 files changed, 11430 insertions, 0 deletions
diff --git a/third_party/rust/audioipc-client/.cargo-checksum.json b/third_party/rust/audioipc-client/.cargo-checksum.json
new file mode 100644
index 0000000000..b6934efe0a
--- /dev/null
+++ b/third_party/rust/audioipc-client/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"1a69f4283975d1dff38d6ff0652032ea80b0c90eef78f9970f2d47a15f91ee22","cbindgen.toml":"bd89c5a9f52395b1c703ff04d1c0019dc3c92b691d571ae503c4b85753a44a39","src/context.rs":"6c50d02883421c3dc7de70721a099636dc9ae2285fb5a86279868b1c840630f2","src/lib.rs":"7016a5de8770d3269e62dd7ad096fdd00cf821febf916bbc3163225cf6a53002","src/send_recv.rs":"064a657c845762be1dbcbbfc18b3f8a51582eb540def8d2ceecf200184ad4f7a","src/stream.rs":"f493ddc3ae950fc18fe42249b13194bc0a80bd0ce88bc8c7d3295f9ee10a932e"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc-client/Cargo.toml b/third_party/rust/audioipc-client/Cargo.toml
new file mode 100644
index 0000000000..481e257c30
--- /dev/null
+++ b/third_party/rust/audioipc-client/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "audioipc-client"
+version = "0.4.0"
+authors = [
+ "Matthew Gregan <kinetik@flim.org>",
+ "Dan Glastonbury <dan.glastonbury@gmail.com>"
+ ]
+license = "ISC"
+description = "Cubeb Backend for talking to remote cubeb server."
+edition = "2018"
+
+[dependencies]
+audioipc = { path="../audioipc" }
+cubeb-backend = "0.10"
+futures = { version="0.1.18", default-features=false, features=["use_std"] }
+futures-cpupool = { version="0.1.8", default-features=false }
+log = "0.4"
+tokio = { version="0.1", default-features=false, features = ["rt-full"] }
+
+[dependencies.audio_thread_priority]
+version = "0.26.1"
+default-features = false
+features = ["winapi"]
diff --git a/third_party/rust/audioipc-client/cbindgen.toml b/third_party/rust/audioipc-client/cbindgen.toml
new file mode 100644
index 0000000000..9a7c204fe4
--- /dev/null
+++ b/third_party/rust/audioipc-client/cbindgen.toml
@@ -0,0 +1,28 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "audioipc"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[parse]
+parse_deps = true
+include = ['audioipc']
+
+[fn]
+args = "Vertical"
+rename_args = "GeckoCase"
+
+[struct]
+rename_fields = "GeckoCase"
+
+[defines]
+"windows" = "XP_WIN"
+"unix" = "XP_UNIX"
diff --git a/third_party/rust/audioipc-client/src/context.rs b/third_party/rust/audioipc-client/src/context.rs
new file mode 100644
index 0000000000..2737d37c08
--- /dev/null
+++ b/third_party/rust/audioipc-client/src/context.rs
@@ -0,0 +1,431 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::stream;
+use crate::{assert_not_in_callback, run_in_callback};
+use crate::{ClientStream, AUDIOIPC_INIT_PARAMS};
+#[cfg(target_os = "linux")]
+use audio_thread_priority::get_current_thread_info;
+#[cfg(not(target_os = "linux"))]
+use audio_thread_priority::promote_current_thread_to_real_time;
+use audioipc::codec::LengthDelimitedCodec;
+use audioipc::framing::{framed, Framed};
+use audioipc::{core, rpc};
+use audioipc::{
+ messages, messages::DeviceCollectionReq, messages::DeviceCollectionResp, ClientMessage,
+ ServerMessage,
+};
+use cubeb_backend::{
+ ffi, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceType, Error, Ops, Result,
+ Stream, StreamParams, StreamParamsRef,
+};
+use futures::Future;
+use futures_cpupool::{CpuFuture, CpuPool};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_void;
+use std::sync::mpsc;
+use std::sync::{Arc, Mutex};
+use std::thread;
+use std::{fmt, mem, ptr};
+use tokio::reactor;
+use tokio::runtime::current_thread;
+
+struct CubebClient;
+
+impl rpc::Client for CubebClient {
+ type Request = ServerMessage;
+ type Response = ClientMessage;
+ type Transport =
+ Framed<audioipc::AsyncMessageStream, LengthDelimitedCodec<Self::Request, Self::Response>>;
+}
+
+pub const CLIENT_OPS: Ops = capi_new!(ClientContext, ClientStream);
+
+// ClientContext's layout *must* match cubeb.c's `struct cubeb` for the
+// common fields.
+#[repr(C)]
+pub struct ClientContext {
+ _ops: *const Ops,
+ rpc: rpc::ClientProxy<ServerMessage, ClientMessage>,
+ core: core::CoreThread,
+ cpu_pool: CpuPool,
+ backend_id: CString,
+ device_collection_rpc: bool,
+ input_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+ output_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+}
+
+impl ClientContext {
+ #[doc(hidden)]
+ pub fn handle(&self) -> current_thread::Handle {
+ self.core.handle()
+ }
+
+ #[doc(hidden)]
+ pub fn rpc(&self) -> rpc::ClientProxy<ServerMessage, ClientMessage> {
+ self.rpc.clone()
+ }
+
+ #[doc(hidden)]
+ pub fn cpu_pool(&self) -> CpuPool {
+ self.cpu_pool.clone()
+ }
+}
+
+#[cfg(target_os = "linux")]
+fn promote_thread(rpc: &rpc::ClientProxy<ServerMessage, ClientMessage>) {
+ match get_current_thread_info() {
+ Ok(info) => {
+ let bytes = info.serialize();
+ // Don't wait for the response, this is on the callback thread, which must not block.
+ rpc.call(ServerMessage::PromoteThreadToRealTime(bytes));
+ }
+ Err(_) => {
+ warn!("Could not remotely promote thread to RT.");
+ }
+ }
+}
+
+#[cfg(not(target_os = "linux"))]
+fn promote_thread(_rpc: &rpc::ClientProxy<ServerMessage, ClientMessage>) {
+ match promote_current_thread_to_real_time(0, 48000) {
+ Ok(_) => {
+ info!("Audio thread promoted to real-time.");
+ }
+ Err(_) => {
+ warn!("Could not promote thread to real-time.");
+ }
+ }
+}
+
+fn register_thread(callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>) {
+ if let Some(func) = callback {
+ let thr = thread::current();
+ let name = CString::new(thr.name().unwrap()).unwrap();
+ func(name.as_ptr());
+ }
+}
+
+fn unregister_thread(callback: Option<extern "C" fn()>) {
+ if let Some(func) = callback {
+ func();
+ }
+}
+
+fn promote_and_register_thread(
+ rpc: &rpc::ClientProxy<ServerMessage, ClientMessage>,
+ callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+) {
+ promote_thread(rpc);
+ register_thread(callback);
+}
+
+#[derive(Default)]
+struct DeviceCollectionCallback {
+ cb: ffi::cubeb_device_collection_changed_callback,
+ user_ptr: usize,
+}
+
+struct DeviceCollectionServer {
+ input_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+ output_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+ cpu_pool: CpuPool,
+}
+
+impl rpc::Server for DeviceCollectionServer {
+ type Request = DeviceCollectionReq;
+ type Response = DeviceCollectionResp;
+ type Future = CpuFuture<Self::Response, ()>;
+ type Transport =
+ Framed<audioipc::AsyncMessageStream, LengthDelimitedCodec<Self::Response, Self::Request>>;
+
+ fn process(&mut self, req: Self::Request) -> Self::Future {
+ match req {
+ DeviceCollectionReq::DeviceChange(device_type) => {
+ trace!(
+ "ctx_thread: DeviceChange Callback: device_type={}",
+ device_type
+ );
+
+ let devtype = cubeb_backend::DeviceType::from_bits_truncate(device_type);
+
+ let (input_cb, input_user_ptr) = {
+ let dcb = self.input_device_callback.lock().unwrap();
+ (dcb.cb, dcb.user_ptr)
+ };
+ let (output_cb, output_user_ptr) = {
+ let dcb = self.output_device_callback.lock().unwrap();
+ (dcb.cb, dcb.user_ptr)
+ };
+
+ self.cpu_pool.spawn_fn(move || {
+ run_in_callback(|| {
+ if devtype.contains(cubeb_backend::DeviceType::INPUT) {
+ unsafe {
+ input_cb.unwrap()(ptr::null_mut(), input_user_ptr as *mut c_void)
+ }
+ }
+ if devtype.contains(cubeb_backend::DeviceType::OUTPUT) {
+ unsafe {
+ output_cb.unwrap()(ptr::null_mut(), output_user_ptr as *mut c_void)
+ }
+ }
+ });
+
+ Ok(DeviceCollectionResp::DeviceChange)
+ })
+ }
+ }
+ }
+}
+
+impl ContextOps for ClientContext {
+ fn init(_context_name: Option<&CStr>) -> Result<Context> {
+ fn bind_and_send_client(
+ stream: audioipc::AsyncMessageStream,
+ tx_rpc: &mpsc::Sender<rpc::ClientProxy<ServerMessage, ClientMessage>>,
+ ) {
+ let transport = framed(stream, Default::default());
+ let rpc = rpc::bind_client::<CubebClient>(transport);
+ // If send fails then the rx end has closed
+ // which is unlikely here.
+ let _ = tx_rpc.send(rpc);
+ }
+
+ assert_not_in_callback();
+
+ let (tx_rpc, rx_rpc) = mpsc::channel();
+
+ let params = AUDIOIPC_INIT_PARAMS.with(|p| p.replace(None).unwrap());
+ let thread_create_callback = params.thread_create_callback;
+ let thread_destroy_callback = params.thread_destroy_callback;
+
+ let server_stream =
+ unsafe { audioipc::MessageStream::from_raw_handle(params.server_connection) };
+
+ let core = core::spawn_thread(
+ "AudioIPC Client RPC",
+ move || {
+ let handle = reactor::Handle::default();
+
+ register_thread(thread_create_callback);
+
+ let stream = server_stream.into_tokio_ipc(&handle).unwrap();
+ bind_and_send_client(stream, &tx_rpc);
+ Ok(())
+ },
+ move || unregister_thread(thread_destroy_callback),
+ )
+ .map_err(|_| Error::default())?;
+
+ let rpc = rx_rpc.recv().map_err(|_| Error::default())?;
+ let rpc2 = rpc.clone();
+
+ // Don't let errors bubble from here. Later calls against this context
+ // will return errors the caller expects to handle.
+ let _ = send_recv!(rpc, ClientConnect(std::process::id()) => ClientConnected);
+
+ let backend_id = send_recv!(rpc, ContextGetBackendId => ContextBackendId())
+ .unwrap_or_else(|_| "(remote error)".to_string());
+ let backend_id = CString::new(backend_id).expect("backend_id query failed");
+
+ let cpu_pool = futures_cpupool::Builder::new()
+ .name_prefix("AudioIPC")
+ .after_start(move || promote_and_register_thread(&rpc2, thread_create_callback))
+ .before_stop(move || unregister_thread(thread_destroy_callback))
+ .pool_size(params.pool_size)
+ .stack_size(params.stack_size)
+ .create();
+
+ let ctx = Box::new(ClientContext {
+ _ops: &CLIENT_OPS as *const _,
+ rpc,
+ core,
+ cpu_pool,
+ backend_id,
+ device_collection_rpc: false,
+ input_device_callback: Arc::new(Mutex::new(Default::default())),
+ output_device_callback: Arc::new(Mutex::new(Default::default())),
+ });
+ Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) })
+ }
+
+ fn backend_id(&mut self) -> &CStr {
+ assert_not_in_callback();
+ self.backend_id.as_c_str()
+ }
+
+ fn max_channel_count(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ send_recv!(self.rpc(), ContextGetMaxChannelCount => ContextMaxChannelCount())
+ }
+
+ fn min_latency(&mut self, params: StreamParams) -> Result<u32> {
+ assert_not_in_callback();
+ let params = messages::StreamParams::from(params.as_ref());
+ send_recv!(self.rpc(), ContextGetMinLatency(params) => ContextMinLatency())
+ }
+
+ fn preferred_sample_rate(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ send_recv!(self.rpc(), ContextGetPreferredSampleRate => ContextPreferredSampleRate())
+ }
+
+ fn enumerate_devices(
+ &mut self,
+ devtype: DeviceType,
+ collection: &DeviceCollectionRef,
+ ) -> Result<()> {
+ assert_not_in_callback();
+ let v: Vec<ffi::cubeb_device_info> = send_recv!(
+ self.rpc(), ContextGetDeviceEnumeration(devtype.bits()) => ContextEnumeratedDevices())?
+ .into_iter()
+ .map(|i| i.into())
+ .collect();
+ let mut vs = v.into_boxed_slice();
+ let coll = unsafe { &mut *collection.as_ptr() };
+ coll.device = vs.as_mut_ptr();
+ coll.count = vs.len();
+ // Giving away the memory owned by vs. Don't free it!
+ // Reclaimed in `device_collection_destroy`.
+ mem::forget(vs);
+ Ok(())
+ }
+
+ fn device_collection_destroy(&mut self, collection: &mut DeviceCollectionRef) -> Result<()> {
+ assert_not_in_callback();
+ unsafe {
+ let coll = &mut *collection.as_ptr();
+ let mut devices = Vec::from_raw_parts(
+ coll.device as *mut ffi::cubeb_device_info,
+ coll.count,
+ coll.count,
+ );
+ for dev in &mut devices {
+ if !dev.device_id.is_null() {
+ let _ = CString::from_raw(dev.device_id as *mut _);
+ }
+ if !dev.group_id.is_null() {
+ let _ = CString::from_raw(dev.group_id as *mut _);
+ }
+ if !dev.vendor_name.is_null() {
+ let _ = CString::from_raw(dev.vendor_name as *mut _);
+ }
+ if !dev.friendly_name.is_null() {
+ let _ = CString::from_raw(dev.friendly_name as *mut _);
+ }
+ }
+ coll.device = ptr::null_mut();
+ coll.count = 0;
+ Ok(())
+ }
+ }
+
+ fn stream_init(
+ &mut self,
+ stream_name: Option<&CStr>,
+ input_device: DeviceId,
+ input_stream_params: Option<&StreamParamsRef>,
+ output_device: DeviceId,
+ output_stream_params: Option<&StreamParamsRef>,
+ latency_frames: u32,
+ // These params aren't sent to the server
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+ ) -> Result<Stream> {
+ assert_not_in_callback();
+
+ let stream_name = stream_name.map(|name| name.to_bytes_with_nul().to_vec());
+
+ let input_stream_params = input_stream_params.map(messages::StreamParams::from);
+ let output_stream_params = output_stream_params.map(messages::StreamParams::from);
+
+ let init_params = messages::StreamInitParams {
+ stream_name,
+ input_device: input_device as usize,
+ input_stream_params,
+ output_device: output_device as usize,
+ output_stream_params,
+ latency_frames,
+ };
+ stream::init(self, init_params, data_callback, state_callback, user_ptr)
+ }
+
+ fn register_device_collection_changed(
+ &mut self,
+ devtype: DeviceType,
+ collection_changed_callback: ffi::cubeb_device_collection_changed_callback,
+ user_ptr: *mut c_void,
+ ) -> Result<()> {
+ assert_not_in_callback();
+
+ if !self.device_collection_rpc {
+ let mut fd = send_recv!(self.rpc(),
+ ContextSetupDeviceCollectionCallback =>
+ ContextSetupDeviceCollectionCallback())?;
+
+ let stream = unsafe {
+ audioipc::MessageStream::from_raw_handle(
+ fd.platform_handle.take_handle().into_raw(),
+ )
+ };
+
+ let server = DeviceCollectionServer {
+ input_device_callback: self.input_device_callback.clone(),
+ output_device_callback: self.output_device_callback.clone(),
+ cpu_pool: self.cpu_pool(),
+ };
+
+ let (wait_tx, wait_rx) = mpsc::channel();
+ self.handle()
+ .spawn(futures::future::lazy(move || {
+ let handle = reactor::Handle::default();
+ let stream = stream.into_tokio_ipc(&handle).unwrap();
+ let transport = framed(stream, Default::default());
+ rpc::bind_server(transport, server);
+ wait_tx.send(()).unwrap();
+ Ok(())
+ }))
+ .expect("Failed to spawn DeviceCollectionServer");
+ wait_rx.recv().unwrap();
+ self.device_collection_rpc = true;
+ }
+
+ if devtype.contains(cubeb_backend::DeviceType::INPUT) {
+ let mut cb = self.input_device_callback.lock().unwrap();
+ cb.cb = collection_changed_callback;
+ cb.user_ptr = user_ptr as usize;
+ }
+ if devtype.contains(cubeb_backend::DeviceType::OUTPUT) {
+ let mut cb = self.output_device_callback.lock().unwrap();
+ cb.cb = collection_changed_callback;
+ cb.user_ptr = user_ptr as usize;
+ }
+
+ let enable = collection_changed_callback.is_some();
+ send_recv!(self.rpc(),
+ ContextRegisterDeviceCollectionChanged(devtype.bits(), enable) =>
+ ContextRegisteredDeviceCollectionChanged)
+ }
+}
+
+impl Drop for ClientContext {
+ fn drop(&mut self) {
+ debug!("ClientContext dropped...");
+ let _ = send_recv!(self.rpc(), ClientDisconnect => ClientDisconnected);
+ }
+}
+
+impl fmt::Debug for ClientContext {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("ClientContext")
+ .field("_ops", &self._ops)
+ .field("rpc", &self.rpc)
+ .field("core", &self.core)
+ .field("cpu_pool", &"...")
+ .finish()
+ }
+}
diff --git a/third_party/rust/audioipc-client/src/lib.rs b/third_party/rust/audioipc-client/src/lib.rs
new file mode 100644
index 0000000000..48d785d8e1
--- /dev/null
+++ b/third_party/rust/audioipc-client/src/lib.rs
@@ -0,0 +1,83 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+#![warn(unused_extern_crates)]
+
+#[macro_use]
+extern crate cubeb_backend;
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+mod send_recv;
+mod context;
+mod stream;
+
+use crate::context::ClientContext;
+use crate::stream::ClientStream;
+use audioipc::PlatformHandleType;
+use cubeb_backend::{capi, ffi};
+use std::os::raw::{c_char, c_int};
+
+thread_local!(static IN_CALLBACK: std::cell::RefCell<bool> = std::cell::RefCell::new(false));
+thread_local!(static AUDIOIPC_INIT_PARAMS: std::cell::RefCell<Option<AudioIpcInitParams>> = std::cell::RefCell::new(None));
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct AudioIpcInitParams {
+ // Fields only need to be public for ipctest.
+ pub server_connection: PlatformHandleType,
+ pub pool_size: usize,
+ pub stack_size: usize,
+ pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+ pub thread_destroy_callback: Option<extern "C" fn()>,
+}
+
+unsafe impl Send for AudioIpcInitParams {}
+
+fn set_in_callback(in_callback: bool) {
+ IN_CALLBACK.with(|b| {
+ assert_eq!(*b.borrow(), !in_callback);
+ *b.borrow_mut() = in_callback;
+ });
+}
+
+fn run_in_callback<F, R>(f: F) -> R
+where
+ F: FnOnce() -> R,
+{
+ set_in_callback(true);
+
+ let r = f();
+
+ set_in_callback(false);
+
+ r
+}
+
+fn assert_not_in_callback() {
+ IN_CALLBACK.with(|b| {
+ assert!(!*b.borrow());
+ });
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+/// Entry point from C code.
+pub unsafe extern "C" fn audioipc_client_init(
+ c: *mut *mut ffi::cubeb,
+ context_name: *const c_char,
+ init_params: *const AudioIpcInitParams,
+) -> c_int {
+ if init_params.is_null() {
+ return cubeb_backend::ffi::CUBEB_ERROR;
+ }
+
+ let init_params = &*init_params;
+
+ AUDIOIPC_INIT_PARAMS.with(|p| {
+ *p.borrow_mut() = Some(*init_params);
+ });
+ capi::capi_init::<ClientContext>(c, context_name)
+}
diff --git a/third_party/rust/audioipc-client/src/send_recv.rs b/third_party/rust/audioipc-client/src/send_recv.rs
new file mode 100644
index 0000000000..b0d41aca2c
--- /dev/null
+++ b/third_party/rust/audioipc-client/src/send_recv.rs
@@ -0,0 +1,73 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use cubeb_backend::Error;
+use std::os::raw::c_int;
+
+#[doc(hidden)]
+pub fn _err<E>(e: E) -> Error
+where
+ E: Into<Option<c_int>>,
+{
+ match e.into() {
+ Some(e) => Error::from_raw(e),
+ None => Error::error(),
+ }
+}
+
+#[macro_export]
+macro_rules! send_recv {
+ ($rpc:expr, $smsg:ident => $rmsg:ident) => {{
+ let resp = send_recv!(__send $rpc, $smsg);
+ send_recv!(__recv resp, $rmsg)
+ }};
+ ($rpc:expr, $smsg:ident => $rmsg:ident()) => {{
+ let resp = send_recv!(__send $rpc, $smsg);
+ send_recv!(__recv resp, $rmsg __result)
+ }};
+ ($rpc:expr, $smsg:ident($($a:expr),*) => $rmsg:ident) => {{
+ let resp = send_recv!(__send $rpc, $smsg, $($a),*);
+ send_recv!(__recv resp, $rmsg)
+ }};
+ ($rpc:expr, $smsg:ident($($a:expr),*) => $rmsg:ident()) => {{
+ let resp = send_recv!(__send $rpc, $smsg, $($a),*);
+ send_recv!(__recv resp, $rmsg __result)
+ }};
+ //
+ (__send $rpc:expr, $smsg:ident) => ({
+ $rpc.call(ServerMessage::$smsg)
+ });
+ (__send $rpc:expr, $smsg:ident, $($a:expr),*) => ({
+ $rpc.call(ServerMessage::$smsg($($a),*))
+ });
+ (__recv $resp:expr, $rmsg:ident) => ({
+ match $resp.wait() {
+ Ok(ClientMessage::$rmsg) => Ok(()),
+ Ok(ClientMessage::Error(e)) => Err($crate::send_recv::_err(e)),
+ Ok(m) => {
+ debug!("received wrong message - got={:?}", m);
+ Err($crate::send_recv::_err(None))
+ },
+ Err(e) => {
+ debug!("received error from rpc - got={:?}", e);
+ Err($crate::send_recv::_err(None))
+ },
+ }
+ });
+ (__recv $resp:expr, $rmsg:ident __result) => ({
+ match $resp.wait() {
+ Ok(ClientMessage::$rmsg(v)) => Ok(v),
+ Ok(ClientMessage::Error(e)) => Err($crate::send_recv::_err(e)),
+ Ok(m) => {
+ debug!("received wrong message - got={:?}", m);
+ Err($crate::send_recv::_err(None))
+ },
+ Err(e) => {
+ debug!("received error - got={:?}", e);
+ Err($crate::send_recv::_err(None))
+ },
+ }
+ })
+}
diff --git a/third_party/rust/audioipc-client/src/stream.rs b/third_party/rust/audioipc-client/src/stream.rs
new file mode 100644
index 0000000000..e4d4b02b3c
--- /dev/null
+++ b/third_party/rust/audioipc-client/src/stream.rs
@@ -0,0 +1,422 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::ClientContext;
+use crate::{assert_not_in_callback, run_in_callback};
+use audioipc::rpc;
+use audioipc::shm::SharedMem;
+use audioipc::{codec::LengthDelimitedCodec, messages::StreamCreateParams};
+use audioipc::{
+ framing::{framed, Framed},
+ messages::{self, CallbackReq, CallbackResp, ClientMessage, ServerMessage},
+};
+use cubeb_backend::{ffi, DeviceRef, Error, Result, Stream, StreamOps};
+use futures::Future;
+use futures_cpupool::{CpuFuture, CpuPool};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_void;
+use std::ptr;
+use std::sync::mpsc;
+use std::sync::{Arc, Mutex};
+use tokio::reactor;
+
+pub struct Device(ffi::cubeb_device);
+
+impl Drop for Device {
+ fn drop(&mut self) {
+ unsafe {
+ if !self.0.input_name.is_null() {
+ let _ = CString::from_raw(self.0.input_name as *mut _);
+ }
+ if !self.0.output_name.is_null() {
+ let _ = CString::from_raw(self.0.output_name as *mut _);
+ }
+ }
+ }
+}
+
+// ClientStream's layout *must* match cubeb.c's `struct cubeb_stream` for the
+// common fields.
+#[repr(C)]
+#[derive(Debug)]
+pub struct ClientStream<'ctx> {
+ // This must be a reference to Context for cubeb, cubeb accesses
+ // stream methods via stream->context->ops
+ context: &'ctx ClientContext,
+ user_ptr: *mut c_void,
+ token: usize,
+ device_change_cb: Arc<Mutex<ffi::cubeb_device_changed_callback>>,
+ // Signals ClientStream that CallbackServer has dropped.
+ shutdown_rx: mpsc::Receiver<()>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum StreamDirection {
+ Input,
+ Output,
+ Duplex,
+}
+
+struct CallbackServer {
+ dir: StreamDirection,
+ shm: Option<SharedMem>,
+ duplex_input: Option<Vec<u8>>,
+ data_cb: ffi::cubeb_data_callback,
+ state_cb: ffi::cubeb_state_callback,
+ user_ptr: usize,
+ cpu_pool: CpuPool,
+ device_change_cb: Arc<Mutex<ffi::cubeb_device_changed_callback>>,
+ // Signals ClientStream that CallbackServer has dropped.
+ _shutdown_tx: mpsc::Sender<()>,
+}
+
+impl rpc::Server for CallbackServer {
+ type Request = CallbackReq;
+ type Response = CallbackResp;
+ type Future = CpuFuture<Self::Response, ()>;
+ type Transport =
+ Framed<audioipc::AsyncMessageStream, LengthDelimitedCodec<Self::Response, Self::Request>>;
+
+ fn process(&mut self, req: Self::Request) -> Self::Future {
+ match req {
+ CallbackReq::Data {
+ nframes,
+ input_frame_size,
+ output_frame_size,
+ } => {
+ trace!(
+ "stream_thread: Data Callback: nframes={} input_fs={} output_fs={}",
+ nframes,
+ input_frame_size,
+ output_frame_size,
+ );
+
+ let input_nbytes = nframes as usize * input_frame_size;
+ let output_nbytes = nframes as usize * output_frame_size;
+
+ // Clone values that need to be moved into the cpu pool thread.
+ let mut shm = unsafe { self.shm.as_ref().unwrap().unsafe_view() };
+
+ let duplex_copy_ptr = match &mut self.duplex_input {
+ Some(buf) => {
+ assert_eq!(self.dir, StreamDirection::Duplex);
+ assert!(input_frame_size > 0);
+ assert!(buf.capacity() >= input_nbytes);
+ buf.as_mut_ptr()
+ }
+ None => ptr::null_mut(),
+ } as usize;
+ let user_ptr = self.user_ptr;
+ let cb = self.data_cb.unwrap();
+ let dir = self.dir;
+
+ self.cpu_pool.spawn_fn(move || {
+ // Input and output reuse the same shmem backing. Unfortunately, cubeb's data_callback isn't
+ // specified in such a way that would require the callee to consume all of the input before
+ // writing to the output (i.e., it is passed as two pointers that aren't expected to alias).
+ // That means we need to copy the input here.
+ let (input_ptr, output_ptr) = match dir {
+ StreamDirection::Duplex => unsafe {
+ assert!(input_frame_size > 0);
+ assert!(output_frame_size > 0);
+ assert_ne!(duplex_copy_ptr, 0);
+ let input = shm.get_slice(input_nbytes).unwrap();
+ ptr::copy_nonoverlapping(
+ input.as_ptr(),
+ duplex_copy_ptr as *mut _,
+ input.len(),
+ );
+ (
+ duplex_copy_ptr as _,
+ shm.get_mut_slice(output_nbytes).unwrap().as_mut_ptr(),
+ )
+ },
+ StreamDirection::Input => unsafe {
+ assert!(input_frame_size > 0);
+ assert_eq!(output_frame_size, 0);
+ (
+ shm.get_slice(input_nbytes).unwrap().as_ptr(),
+ ptr::null_mut(),
+ )
+ },
+ StreamDirection::Output => unsafe {
+ assert!(output_frame_size > 0);
+ assert_eq!(input_frame_size, 0);
+ (
+ ptr::null(),
+ shm.get_mut_slice(output_nbytes).unwrap().as_mut_ptr(),
+ )
+ },
+ };
+
+ run_in_callback(|| {
+ let nframes = unsafe {
+ cb(
+ ptr::null_mut(), // https://github.com/kinetiknz/cubeb/issues/518
+ user_ptr as *mut c_void,
+ input_ptr as *const _,
+ output_ptr as *mut _,
+ nframes as _,
+ )
+ };
+
+ Ok(CallbackResp::Data(nframes as isize))
+ })
+ })
+ }
+ CallbackReq::State(state) => {
+ trace!("stream_thread: State Callback: {:?}", state);
+ let user_ptr = self.user_ptr;
+ let cb = self.state_cb.unwrap();
+ self.cpu_pool.spawn_fn(move || {
+ run_in_callback(|| unsafe {
+ cb(ptr::null_mut(), user_ptr as *mut _, state);
+ });
+
+ Ok(CallbackResp::State)
+ })
+ }
+ CallbackReq::DeviceChange => {
+ let cb = self.device_change_cb.clone();
+ let user_ptr = self.user_ptr;
+ self.cpu_pool.spawn_fn(move || {
+ run_in_callback(|| {
+ let cb = cb.lock().unwrap();
+ if let Some(cb) = *cb {
+ unsafe {
+ cb(user_ptr as *mut _);
+ }
+ } else {
+ warn!("DeviceChange received with null callback");
+ }
+ });
+
+ Ok(CallbackResp::DeviceChange)
+ })
+ }
+ CallbackReq::SharedMem(mut handle, shm_area_size) => {
+ self.shm = match unsafe { SharedMem::from(handle.take_handle(), shm_area_size) } {
+ Ok(shm) => Some(shm),
+ Err(e) => {
+ warn!(
+ "sharedmem client mapping failed (size={}, err={:?})",
+ shm_area_size, e
+ );
+ return self
+ .cpu_pool
+ .spawn_fn(move || Ok(CallbackResp::Error(ffi::CUBEB_ERROR)));
+ }
+ };
+
+ self.duplex_input = if let StreamDirection::Duplex = self.dir {
+ let mut duplex_input = Vec::new();
+ match duplex_input.try_reserve_exact(shm_area_size) {
+ Ok(()) => Some(duplex_input),
+ Err(e) => {
+ warn!(
+ "duplex_input allocation failed (size={}, err={:?})",
+ shm_area_size, e
+ );
+ return self
+ .cpu_pool
+ .spawn_fn(move || Ok(CallbackResp::Error(ffi::CUBEB_ERROR)));
+ }
+ }
+ } else {
+ None
+ };
+ self.cpu_pool.spawn_fn(move || Ok(CallbackResp::SharedMem))
+ }
+ }
+ }
+}
+
+impl<'ctx> ClientStream<'ctx> {
+ fn init(
+ ctx: &'ctx ClientContext,
+ init_params: messages::StreamInitParams,
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+ ) -> Result<Stream> {
+ assert_not_in_callback();
+
+ let rpc = ctx.rpc();
+ let create_params = StreamCreateParams {
+ input_stream_params: init_params.input_stream_params,
+ output_stream_params: init_params.output_stream_params,
+ };
+ let mut data = send_recv!(rpc, StreamCreate(create_params) => StreamCreated())?;
+
+ debug!(
+ "token = {}, handle = {:?}",
+ data.token, data.platform_handle
+ );
+
+ let stream = unsafe {
+ audioipc::MessageStream::from_raw_handle(data.platform_handle.take_handle().into_raw())
+ };
+
+ let user_data = user_ptr as usize;
+
+ let cpu_pool = ctx.cpu_pool();
+
+ let null_cb: ffi::cubeb_device_changed_callback = None;
+ let device_change_cb = Arc::new(Mutex::new(null_cb));
+
+ let (_shutdown_tx, shutdown_rx) = mpsc::channel();
+
+ let dir = match (
+ init_params.input_stream_params,
+ init_params.output_stream_params,
+ ) {
+ (Some(_), Some(_)) => StreamDirection::Duplex,
+ (Some(_), None) => StreamDirection::Input,
+ (None, Some(_)) => StreamDirection::Output,
+ (None, None) => unreachable!(),
+ };
+
+ let server = CallbackServer {
+ dir,
+ shm: None,
+ duplex_input: None,
+ data_cb: data_callback,
+ state_cb: state_callback,
+ user_ptr: user_data,
+ cpu_pool,
+ device_change_cb: device_change_cb.clone(),
+ _shutdown_tx,
+ };
+
+ let (wait_tx, wait_rx) = mpsc::channel();
+ ctx.handle()
+ .spawn(futures::future::lazy(move || {
+ let handle = reactor::Handle::default();
+ let stream = stream.into_tokio_ipc(&handle).unwrap();
+ let transport = framed(stream, Default::default());
+ rpc::bind_server(transport, server);
+ wait_tx.send(()).unwrap();
+ Ok(())
+ }))
+ .expect("Failed to spawn CallbackServer");
+ wait_rx.recv().unwrap();
+
+ send_recv!(rpc, StreamInit(data.token, init_params) => StreamInitialized)?;
+
+ let stream = Box::into_raw(Box::new(ClientStream {
+ context: ctx,
+ user_ptr,
+ token: data.token,
+ device_change_cb,
+ shutdown_rx,
+ }));
+ Ok(unsafe { Stream::from_ptr(stream as *mut _) })
+ }
+}
+
+impl<'ctx> Drop for ClientStream<'ctx> {
+ fn drop(&mut self) {
+ debug!("ClientStream drop");
+ let rpc = self.context.rpc();
+ let _ = send_recv!(rpc, StreamDestroy(self.token) => StreamDestroyed);
+ debug!("ClientStream drop - stream destroyed");
+ // Wait for CallbackServer to shutdown. The remote server drops the RPC
+ // connection during StreamDestroy, which will cause CallbackServer to drop
+ // once the connection close is detected. Dropping CallbackServer will
+ // cause the shutdown channel to error on recv, which we rely on to
+ // synchronize with CallbackServer dropping.
+ let _ = self.shutdown_rx.recv();
+ debug!("ClientStream dropped");
+ }
+}
+
+impl<'ctx> StreamOps for ClientStream<'ctx> {
+ fn start(&mut self) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamStart(self.token) => StreamStarted)
+ }
+
+ fn stop(&mut self) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamStop(self.token) => StreamStopped)
+ }
+
+ fn position(&mut self) -> Result<u64> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamGetPosition(self.token) => StreamPosition())
+ }
+
+ fn latency(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamGetLatency(self.token) => StreamLatency())
+ }
+
+ fn input_latency(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamGetInputLatency(self.token) => StreamInputLatency())
+ }
+
+ fn set_volume(&mut self, volume: f32) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamSetVolume(self.token, volume) => StreamVolumeSet)
+ }
+
+ fn set_name(&mut self, name: &CStr) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ send_recv!(rpc, StreamSetName(self.token, name.to_owned()) => StreamNameSet)
+ }
+
+ fn current_device(&mut self) -> Result<&DeviceRef> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ match send_recv!(rpc, StreamGetCurrentDevice(self.token) => StreamCurrentDevice()) {
+ Ok(d) => Ok(unsafe { DeviceRef::from_ptr(Box::into_raw(Box::new(d.into()))) }),
+ Err(e) => Err(e),
+ }
+ }
+
+ fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> {
+ assert_not_in_callback();
+ // It's all unsafe...
+ if device.as_ptr().is_null() {
+ Err(Error::error())
+ } else {
+ unsafe {
+ let _: Box<Device> = Box::from_raw(device.as_ptr() as *mut _);
+ }
+ Ok(())
+ }
+ }
+
+ fn register_device_changed_callback(
+ &mut self,
+ device_changed_callback: ffi::cubeb_device_changed_callback,
+ ) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc();
+ let enable = device_changed_callback.is_some();
+ *self.device_change_cb.lock().unwrap() = device_changed_callback;
+ send_recv!(rpc, StreamRegisterDeviceChangeCallback(self.token, enable) => StreamRegisterDeviceChangeCallback)
+ }
+}
+
+pub fn init(
+ ctx: &ClientContext,
+ init_params: messages::StreamInitParams,
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+) -> Result<Stream> {
+ let stm = ClientStream::init(ctx, init_params, data_callback, state_callback, user_ptr)?;
+ debug_assert_eq!(stm.user_ptr(), user_ptr);
+ Ok(stm)
+}
diff --git a/third_party/rust/audioipc-server/.cargo-checksum.json b/third_party/rust/audioipc-server/.cargo-checksum.json
new file mode 100644
index 0000000000..3f4b7a62fa
--- /dev/null
+++ b/third_party/rust/audioipc-server/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"a90f1f22f12b7e82aa0940766dee91dbf27901c21680930d38d9b1940df247b7","cbindgen.toml":"bd89c5a9f52395b1c703ff04d1c0019dc3c92b691d571ae503c4b85753a44a39","src/lib.rs":"bb467df305bdb1403ffffd7960c5e55c14813d5098f0d88051d0c7542ed059dd","src/server.rs":"1fa365b9329d1873fb51893872a1e99ac86087428ca3bd6959611f3bd6d9549a"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc-server/Cargo.toml b/third_party/rust/audioipc-server/Cargo.toml
new file mode 100644
index 0000000000..ed99b400c4
--- /dev/null
+++ b/third_party/rust/audioipc-server/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "audioipc-server"
+version = "0.2.3"
+authors = [
+ "Matthew Gregan <kinetik@flim.org>",
+ "Dan Glastonbury <dan.glastonbury@gmail.com>"
+ ]
+license = "ISC"
+description = "Remote cubeb server"
+edition = "2018"
+
+[dependencies]
+audioipc = { path = "../audioipc" }
+cubeb-core = "0.10.0"
+futures = "0.1.29"
+once_cell = "1.2.0"
+log = "0.4"
+slab = "0.4"
+tokio = { version="0.1", default-features=false, features = ["rt-full"] }
+
+[dependencies.error-chain]
+version = "0.12.0"
+default-features = false
+
+[dependencies.audio_thread_priority]
+version = "0.26.1"
+default-features = false
+features = ["winapi"]
diff --git a/third_party/rust/audioipc-server/cbindgen.toml b/third_party/rust/audioipc-server/cbindgen.toml
new file mode 100644
index 0000000000..9a7c204fe4
--- /dev/null
+++ b/third_party/rust/audioipc-server/cbindgen.toml
@@ -0,0 +1,28 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "audioipc"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[parse]
+parse_deps = true
+include = ['audioipc']
+
+[fn]
+args = "Vertical"
+rename_args = "GeckoCase"
+
+[struct]
+rename_fields = "GeckoCase"
+
+[defines]
+"windows" = "XP_WIN"
+"unix" = "XP_UNIX"
diff --git a/third_party/rust/audioipc-server/src/lib.rs b/third_party/rust/audioipc-server/src/lib.rs
new file mode 100644
index 0000000000..a6ef02f87a
--- /dev/null
+++ b/third_party/rust/audioipc-server/src/lib.rs
@@ -0,0 +1,208 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+#![warn(unused_extern_crates)]
+
+#[macro_use]
+extern crate error_chain;
+#[macro_use]
+extern crate log;
+
+use audio_thread_priority::promote_current_thread_to_real_time;
+use audioipc::core;
+use audioipc::framing::framed;
+use audioipc::rpc;
+use audioipc::{MessageStream, PlatformHandle, PlatformHandleType};
+use futures::sync::oneshot;
+use futures::Future;
+use once_cell::sync::Lazy;
+use std::ffi::{CStr, CString};
+use std::os::raw::c_void;
+use std::ptr;
+use std::sync::Mutex;
+use tokio::reactor;
+
+mod server;
+
+struct CubebContextParams {
+ context_name: CString,
+ backend_name: Option<CString>,
+}
+
+static G_CUBEB_CONTEXT_PARAMS: Lazy<Mutex<CubebContextParams>> = Lazy::new(|| {
+ Mutex::new(CubebContextParams {
+ context_name: CString::new("AudioIPC Server").unwrap(),
+ backend_name: None,
+ })
+});
+
+#[allow(deprecated)]
+pub mod errors {
+ #![allow(clippy::upper_case_acronyms)]
+ error_chain! {
+ links {
+ AudioIPC(::audioipc::errors::Error, ::audioipc::errors::ErrorKind);
+ }
+ foreign_links {
+ Cubeb(cubeb_core::Error);
+ Io(::std::io::Error);
+ Canceled(::futures::sync::oneshot::Canceled);
+ }
+ }
+}
+
+use crate::errors::*;
+
+struct ServerWrapper {
+ core_thread: core::CoreThread,
+ callback_thread: core::CoreThread,
+}
+
+fn register_thread(callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>) {
+ if let Some(func) = callback {
+ let thr = std::thread::current();
+ let name = CString::new(thr.name().unwrap()).unwrap();
+ func(name.as_ptr());
+ }
+}
+
+fn unregister_thread(callback: Option<extern "C" fn()>) {
+ if let Some(func) = callback {
+ func();
+ }
+}
+
+fn init_threads(
+ thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+ thread_destroy_callback: Option<extern "C" fn()>,
+) -> Result<ServerWrapper> {
+ trace!("Starting up cubeb audio server event loop thread...");
+
+ let callback_thread = core::spawn_thread(
+ "AudioIPC Callback RPC",
+ move || {
+ match promote_current_thread_to_real_time(0, 48000) {
+ Ok(_) => {}
+ Err(_) => {
+ debug!("Failed to promote audio callback thread to real-time.");
+ }
+ }
+ register_thread(thread_create_callback);
+ trace!("Starting up cubeb audio callback event loop thread...");
+ Ok(())
+ },
+ move || {
+ unregister_thread(thread_destroy_callback);
+ },
+ )
+ .map_err(|e| {
+ debug!(
+ "Failed to start cubeb audio callback event loop thread: {:?}",
+ e
+ );
+ e
+ })?;
+
+ let core_thread = core::spawn_thread(
+ "AudioIPC Server RPC",
+ move || {
+ register_thread(thread_create_callback);
+ audioipc::server_platform_init();
+ Ok(())
+ },
+ move || {
+ unregister_thread(thread_destroy_callback);
+ },
+ )
+ .map_err(|e| {
+ debug!("Failed to cubeb audio core event loop thread: {:?}", e);
+ e
+ })?;
+
+ Ok(ServerWrapper {
+ core_thread,
+ callback_thread,
+ })
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct AudioIpcServerInitParams {
+ // Fields only need to be public for ipctest.
+ pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+ pub thread_destroy_callback: Option<extern "C" fn()>,
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+pub unsafe extern "C" fn audioipc_server_start(
+ context_name: *const std::os::raw::c_char,
+ backend_name: *const std::os::raw::c_char,
+ init_params: *const AudioIpcServerInitParams,
+) -> *mut c_void {
+ assert!(!init_params.is_null());
+ let mut params = G_CUBEB_CONTEXT_PARAMS.lock().unwrap();
+ if !context_name.is_null() {
+ params.context_name = CStr::from_ptr(context_name).to_owned();
+ }
+ if !backend_name.is_null() {
+ let backend_string = CStr::from_ptr(backend_name).to_owned();
+ params.backend_name = Some(backend_string);
+ }
+ match init_threads(
+ (*init_params).thread_create_callback,
+ (*init_params).thread_destroy_callback,
+ ) {
+ Ok(server) => Box::into_raw(Box::new(server)) as *mut _,
+ Err(_) => ptr::null_mut() as *mut _,
+ }
+}
+
+// A `shm_area_size` of 0 allows the server to calculate an appropriate shm size for each stream.
+// A non-zero `shm_area_size` forces all allocations to the specified size.
+#[no_mangle]
+pub extern "C" fn audioipc_server_new_client(
+ p: *mut c_void,
+ shm_area_size: usize,
+) -> PlatformHandleType {
+ let (wait_tx, wait_rx) = oneshot::channel();
+ let wrapper: &ServerWrapper = unsafe { &*(p as *mut _) };
+
+ let callback_thread_handle = wrapper.callback_thread.handle();
+
+ // We create a connected pair of anonymous IPC endpoints. One side
+ // is registered with the reactor core, the other side is returned
+ // to the caller.
+ MessageStream::anonymous_ipc_pair()
+ .map(|(ipc_server, ipc_client)| {
+ // Spawn closure to run on same thread as reactor::Core
+ // via remote handle.
+ wrapper
+ .core_thread
+ .handle()
+ .spawn(futures::future::lazy(move || {
+ trace!("Incoming connection");
+ let handle = reactor::Handle::default();
+ ipc_server.into_tokio_ipc(&handle)
+ .map(|sock| {
+ let transport = framed(sock, Default::default());
+ rpc::bind_server(transport, server::CubebServer::new(callback_thread_handle, shm_area_size));
+ }).map_err(|_| ())
+ // Notify waiting thread that server has been registered.
+ .and_then(|_| wait_tx.send(()))
+ }))
+ .expect("Failed to spawn CubebServer");
+ // Wait for notification that server has been registered
+ // with reactor::Core.
+ let _ = wait_rx.wait();
+ unsafe { PlatformHandle::from(ipc_client).into_raw() }
+ })
+ .unwrap_or(audioipc::INVALID_HANDLE_VALUE)
+}
+
+#[no_mangle]
+pub extern "C" fn audioipc_server_stop(p: *mut c_void) {
+ let wrapper = unsafe { Box::<ServerWrapper>::from_raw(p as *mut _) };
+ drop(wrapper);
+}
diff --git a/third_party/rust/audioipc-server/src/server.rs b/third_party/rust/audioipc-server/src/server.rs
new file mode 100644
index 0000000000..c4b1f62327
--- /dev/null
+++ b/third_party/rust/audioipc-server/src/server.rs
@@ -0,0 +1,904 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+#[cfg(target_os = "linux")]
+use audio_thread_priority::{promote_thread_to_real_time, RtPriorityThreadInfo};
+use audioipc::framing::{framed, Framed};
+use audioipc::messages::{
+ CallbackReq, CallbackResp, ClientMessage, Device, DeviceCollectionReq, DeviceCollectionResp,
+ DeviceInfo, RegisterDeviceCollectionChanged, ServerMessage, StreamCreate, StreamCreateParams,
+ StreamInitParams, StreamParams,
+};
+use audioipc::rpc;
+use audioipc::shm::SharedMem;
+use audioipc::{codec::LengthDelimitedCodec, messages::SerializableHandle};
+use audioipc::{MessageStream, PlatformHandle};
+use cubeb_core as cubeb;
+use cubeb_core::ffi;
+use futures::future::{self, FutureResult};
+use futures::sync::oneshot;
+use futures::Future;
+use std::convert::From;
+use std::ffi::CStr;
+use std::mem::size_of;
+use std::os::raw::{c_long, c_void};
+use std::rc::Rc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::{cell::RefCell, sync::Mutex};
+use std::{panic, slice};
+use tokio::reactor;
+use tokio::runtime::current_thread;
+
+use crate::errors::*;
+
+fn error(error: cubeb::Error) -> ClientMessage {
+ ClientMessage::Error(error.raw_code())
+}
+
+struct CubebDeviceCollectionManager {
+ servers: Mutex<Vec<Rc<RefCell<CubebServerCallbacks>>>>,
+}
+
+impl CubebDeviceCollectionManager {
+ fn new() -> CubebDeviceCollectionManager {
+ CubebDeviceCollectionManager {
+ servers: Mutex::new(Vec::new()),
+ }
+ }
+
+ fn register(
+ &mut self,
+ context: &cubeb::Context,
+ server: &Rc<RefCell<CubebServerCallbacks>>,
+ devtype: cubeb::DeviceType,
+ ) -> cubeb::Result<()> {
+ let mut servers = self.servers.lock().unwrap();
+ if servers.is_empty() {
+ self.internal_register(context, true)?;
+ }
+ server.borrow_mut().devtype.insert(devtype);
+ if !servers.iter().any(|s| Rc::ptr_eq(s, server)) {
+ servers.push(server.clone());
+ }
+ Ok(())
+ }
+
+ fn unregister(
+ &mut self,
+ context: &cubeb::Context,
+ server: &Rc<RefCell<CubebServerCallbacks>>,
+ devtype: cubeb::DeviceType,
+ ) -> cubeb::Result<()> {
+ let mut servers = self.servers.lock().unwrap();
+ server.borrow_mut().devtype.remove(devtype);
+ if server.borrow().devtype.is_empty() {
+ servers.retain(|s| !Rc::ptr_eq(s, server));
+ }
+ if servers.is_empty() {
+ self.internal_register(context, false)?;
+ }
+ Ok(())
+ }
+
+ fn internal_register(&self, context: &cubeb::Context, enable: bool) -> cubeb::Result<()> {
+ let user_ptr = if enable {
+ self as *const CubebDeviceCollectionManager as *mut c_void
+ } else {
+ std::ptr::null_mut()
+ };
+ for &(dir, cb) in &[
+ (
+ cubeb::DeviceType::INPUT,
+ device_collection_changed_input_cb_c as _,
+ ),
+ (
+ cubeb::DeviceType::OUTPUT,
+ device_collection_changed_output_cb_c as _,
+ ),
+ ] {
+ unsafe {
+ context.register_device_collection_changed(
+ dir,
+ if enable { Some(cb) } else { None },
+ user_ptr,
+ )?;
+ }
+ }
+ Ok(())
+ }
+
+ // Warning: this is called from an internal cubeb thread, so we must not mutate unprotected shared state.
+ unsafe fn device_collection_changed_callback(&self, device_type: ffi::cubeb_device_type) {
+ let servers = self.servers.lock().unwrap();
+ servers.iter().for_each(|server| {
+ if server
+ .borrow()
+ .devtype
+ .contains(cubeb::DeviceType::from_bits_truncate(device_type))
+ {
+ server
+ .borrow_mut()
+ .device_collection_changed_callback(device_type)
+ }
+ });
+ }
+}
+
+struct DevIdMap {
+ devices: Vec<usize>,
+}
+
+// A cubeb_devid is an opaque type which may be implemented with a stable
+// pointer in a cubeb backend. cubeb_devids received remotely must be
+// validated before use, so DevIdMap provides a simple 1:1 mapping between a
+// cubeb_devid and an IPC-transportable value suitable for use as a unique
+// handle.
+impl DevIdMap {
+ fn new() -> DevIdMap {
+ let mut d = DevIdMap {
+ devices: Vec::with_capacity(32),
+ };
+ // A null cubeb_devid is used for selecting the default device.
+ // Pre-populate the mapping with 0 -> 0 to handle nulls.
+ d.devices.push(0);
+ d
+ }
+
+ // Given a cubeb_devid, return a unique stable value suitable for use
+ // over IPC.
+ fn make_handle(&mut self, devid: usize) -> usize {
+ if let Some(i) = self.devices.iter().position(|&d| d == devid) {
+ return i;
+ }
+ self.devices.push(devid);
+ self.devices.len() - 1
+ }
+
+ // Given a handle produced by `make_handle`, return the associated
+ // cubeb_devid. Invalid handles result in a panic.
+ fn handle_to_id(&self, handle: usize) -> usize {
+ self.devices[handle]
+ }
+}
+
+struct CubebContextState {
+ context: cubeb::Result<cubeb::Context>,
+ manager: CubebDeviceCollectionManager,
+}
+
+thread_local!(static CONTEXT_KEY: RefCell<Option<CubebContextState>> = RefCell::new(None));
+
+fn cubeb_init_from_context_params() -> cubeb::Result<cubeb::Context> {
+ let params = super::G_CUBEB_CONTEXT_PARAMS.lock().unwrap();
+ let context_name = Some(params.context_name.as_c_str());
+ let backend_name = params.backend_name.as_deref();
+ let r = cubeb::Context::init(context_name, backend_name);
+ r.map_err(|e| {
+ info!("cubeb::Context::init failed r={:?}", e);
+ e
+ })
+}
+
+fn with_local_context<T, F>(f: F) -> T
+where
+ F: FnOnce(&cubeb::Result<cubeb::Context>, &mut CubebDeviceCollectionManager) -> T,
+{
+ CONTEXT_KEY.with(|k| {
+ let mut state = k.borrow_mut();
+ if state.is_none() {
+ *state = Some(CubebContextState {
+ context: cubeb_init_from_context_params(),
+ manager: CubebDeviceCollectionManager::new(),
+ });
+ }
+ let CubebContextState { context, manager } = state.as_mut().unwrap();
+ // Always reattempt to initialize cubeb, OS config may have changed.
+ if context.is_err() {
+ *context = cubeb_init_from_context_params();
+ }
+ f(context, manager)
+ })
+}
+
+struct DeviceCollectionClient;
+
+impl rpc::Client for DeviceCollectionClient {
+ type Request = DeviceCollectionReq;
+ type Response = DeviceCollectionResp;
+ type Transport =
+ Framed<audioipc::AsyncMessageStream, LengthDelimitedCodec<Self::Request, Self::Response>>;
+}
+
+struct CallbackClient;
+
+impl rpc::Client for CallbackClient {
+ type Request = CallbackReq;
+ type Response = CallbackResp;
+ type Transport =
+ Framed<audioipc::AsyncMessageStream, LengthDelimitedCodec<Self::Request, Self::Response>>;
+}
+
+struct ServerStreamCallbacks {
+ /// Size of input frame in bytes
+ input_frame_size: u16,
+ /// Size of output frame in bytes
+ output_frame_size: u16,
+ /// Shared memory buffer for transporting audio data to/from client
+ shm: SharedMem,
+ /// RPC interface to callback server running in client
+ rpc: rpc::ClientProxy<CallbackReq, CallbackResp>,
+}
+
+impl ServerStreamCallbacks {
+ fn data_callback(&mut self, input: &[u8], output: &mut [u8], nframes: isize) -> isize {
+ trace!(
+ "Stream data callback: {} {} {}",
+ nframes,
+ input.len(),
+ output.len()
+ );
+
+ unsafe {
+ if self.input_frame_size != 0 {
+ self.shm
+ .get_mut_slice(input.len())
+ .unwrap()
+ .copy_from_slice(input);
+ }
+ }
+
+ let r = self
+ .rpc
+ .call(CallbackReq::Data {
+ nframes,
+ input_frame_size: self.input_frame_size as usize,
+ output_frame_size: self.output_frame_size as usize,
+ })
+ .wait();
+
+ match r {
+ Ok(CallbackResp::Data(frames)) => {
+ if frames >= 0 {
+ let nbytes = frames as usize * self.output_frame_size as usize;
+ trace!("Reslice output to {}", nbytes);
+ unsafe {
+ if self.output_frame_size != 0 {
+ output[..nbytes].copy_from_slice(self.shm.get_slice(nbytes).unwrap());
+ }
+ }
+ }
+ frames
+ }
+ _ => {
+ debug!("Unexpected message {:?} during data_callback", r);
+ // TODO: Return a CUBEB_ERROR result here once
+ // https://github.com/kinetiknz/cubeb/issues/553 is
+ // fixed.
+ 0
+ }
+ }
+ }
+
+ fn state_callback(&mut self, state: cubeb::State) {
+ trace!("Stream state callback: {:?}", state);
+ let r = self.rpc.call(CallbackReq::State(state.into())).wait();
+ match r {
+ Ok(CallbackResp::State) => {}
+ _ => {
+ debug!("Unexpected message {:?} during state callback", r);
+ }
+ }
+ }
+
+ fn device_change_callback(&mut self) {
+ trace!("Stream device change callback");
+ let r = self.rpc.call(CallbackReq::DeviceChange).wait();
+ match r {
+ Ok(CallbackResp::DeviceChange) => {}
+ _ => {
+ debug!("Unexpected message {:?} during device change callback", r);
+ }
+ }
+ }
+}
+
+static SHM_ID: AtomicUsize = AtomicUsize::new(0);
+
+// Generate a temporary shm_id fragment that is unique to the process. This
+// path is used temporarily to create a shm segment, which is then
+// immediately deleted from the filesystem while retaining handles to the
+// shm to be shared between the server and client.
+fn get_shm_id() -> String {
+ format!(
+ "cubeb-shm-{}-{}",
+ std::process::id(),
+ SHM_ID.fetch_add(1, Ordering::SeqCst)
+ )
+}
+
+struct ServerStream {
+ stream: Option<cubeb::Stream>,
+ cbs: Box<ServerStreamCallbacks>,
+ shm_setup: Option<rpc::Response<CallbackResp>>,
+}
+
+impl Drop for ServerStream {
+ fn drop(&mut self) {
+ // `stream` *must* be dropped before `cbs`.
+ drop(self.stream.take());
+ }
+}
+
+struct CubebServerCallbacks {
+ rpc: rpc::ClientProxy<DeviceCollectionReq, DeviceCollectionResp>,
+ devtype: cubeb::DeviceType,
+}
+
+impl CubebServerCallbacks {
+ fn device_collection_changed_callback(&mut self, device_type: ffi::cubeb_device_type) {
+ // TODO: Assert device_type is in devtype.
+ debug!(
+ "Sending device collection ({:?}) changed event",
+ device_type
+ );
+ let _ = self
+ .rpc
+ .call(DeviceCollectionReq::DeviceChange(device_type))
+ .wait();
+ }
+}
+
+pub struct CubebServer {
+ callback_thread: current_thread::Handle,
+ streams: slab::Slab<ServerStream>,
+ remote_pid: Option<u32>,
+ cbs: Option<Rc<RefCell<CubebServerCallbacks>>>,
+ devidmap: DevIdMap,
+ shm_area_size: usize,
+}
+
+impl rpc::Server for CubebServer {
+ type Request = ServerMessage;
+ type Response = ClientMessage;
+ type Future = FutureResult<Self::Response, ()>;
+ type Transport =
+ Framed<audioipc::AsyncMessageStream, LengthDelimitedCodec<Self::Response, Self::Request>>;
+
+ fn process(&mut self, req: Self::Request) -> Self::Future {
+ if let ServerMessage::ClientConnect(pid) = req {
+ self.remote_pid = Some(pid);
+ }
+ let resp = with_local_context(|context, manager| match *context {
+ Err(_) => error(cubeb::Error::error()),
+ Ok(ref context) => self.process_msg(context, manager, &req),
+ });
+ future::ok(resp)
+ }
+}
+
+// Debugging for BMO 1594216/1612044.
+macro_rules! try_stream {
+ ($self:expr, $stm_tok:expr) => {
+ if $self.streams.contains($stm_tok) {
+ $self.streams[$stm_tok]
+ .stream
+ .as_mut()
+ .expect("uninitialized stream")
+ } else {
+ error!(
+ "{}:{}:{} - Stream({}): invalid token",
+ file!(),
+ line!(),
+ column!(),
+ $stm_tok
+ );
+ return error(cubeb::Error::invalid_parameter());
+ }
+ };
+}
+
+impl CubebServer {
+ pub fn new(callback_thread_handle: current_thread::Handle, shm_area_size: usize) -> Self {
+ CubebServer {
+ callback_thread: callback_thread_handle,
+ streams: slab::Slab::<ServerStream>::new(),
+ remote_pid: None,
+ cbs: None,
+ devidmap: DevIdMap::new(),
+ shm_area_size,
+ }
+ }
+
+ // Process a request coming from the client.
+ fn process_msg(
+ &mut self,
+ context: &cubeb::Context,
+ manager: &mut CubebDeviceCollectionManager,
+ msg: &ServerMessage,
+ ) -> ClientMessage {
+ let resp: ClientMessage = match *msg {
+ ServerMessage::ClientConnect(_) => {
+ // remote_pid is set before cubeb initialization, just verify here.
+ assert!(self.remote_pid.is_some());
+ ClientMessage::ClientConnected
+ }
+
+ ServerMessage::ClientDisconnect => {
+ // TODO:
+ //self.connection.client_disconnect();
+ ClientMessage::ClientDisconnected
+ }
+
+ ServerMessage::ContextGetBackendId => {
+ ClientMessage::ContextBackendId(context.backend_id().to_string())
+ }
+
+ ServerMessage::ContextGetMaxChannelCount => context
+ .max_channel_count()
+ .map(ClientMessage::ContextMaxChannelCount)
+ .unwrap_or_else(error),
+
+ ServerMessage::ContextGetMinLatency(ref params) => {
+ let format = cubeb::SampleFormat::from(params.format);
+ let layout = cubeb::ChannelLayout::from(params.layout);
+
+ let params = cubeb::StreamParamsBuilder::new()
+ .format(format)
+ .rate(params.rate)
+ .channels(params.channels)
+ .layout(layout)
+ .take();
+
+ context
+ .min_latency(&params)
+ .map(ClientMessage::ContextMinLatency)
+ .unwrap_or_else(error)
+ }
+
+ ServerMessage::ContextGetPreferredSampleRate => context
+ .preferred_sample_rate()
+ .map(ClientMessage::ContextPreferredSampleRate)
+ .unwrap_or_else(error),
+
+ ServerMessage::ContextGetDeviceEnumeration(device_type) => context
+ .enumerate_devices(cubeb::DeviceType::from_bits_truncate(device_type))
+ .map(|devices| {
+ let v: Vec<DeviceInfo> = devices
+ .iter()
+ .map(|i| {
+ let mut tmp: DeviceInfo = i.as_ref().into();
+ // Replace each cubeb_devid with a unique handle suitable for IPC.
+ tmp.devid = self.devidmap.make_handle(tmp.devid);
+ tmp
+ })
+ .collect();
+ ClientMessage::ContextEnumeratedDevices(v)
+ })
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamCreate(ref params) => self
+ .process_stream_create(params)
+ .unwrap_or_else(|_| error(cubeb::Error::error())),
+
+ ServerMessage::StreamInit(stm_tok, ref params) => self
+ .process_stream_init(context, stm_tok, params)
+ .unwrap_or_else(|_| error(cubeb::Error::error())),
+
+ ServerMessage::StreamDestroy(stm_tok) => {
+ if self.streams.contains(stm_tok) {
+ debug!("Unregistering stream {:?}", stm_tok);
+ self.streams.remove(stm_tok);
+ } else {
+ // Debugging for BMO 1594216/1612044.
+ error!("StreamDestroy({}): invalid token", stm_tok);
+ return error(cubeb::Error::invalid_parameter());
+ }
+ ClientMessage::StreamDestroyed
+ }
+
+ ServerMessage::StreamStart(stm_tok) => try_stream!(self, stm_tok)
+ .start()
+ .map(|_| ClientMessage::StreamStarted)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamStop(stm_tok) => try_stream!(self, stm_tok)
+ .stop()
+ .map(|_| ClientMessage::StreamStopped)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetPosition(stm_tok) => try_stream!(self, stm_tok)
+ .position()
+ .map(ClientMessage::StreamPosition)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetLatency(stm_tok) => try_stream!(self, stm_tok)
+ .latency()
+ .map(ClientMessage::StreamLatency)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetInputLatency(stm_tok) => try_stream!(self, stm_tok)
+ .input_latency()
+ .map(ClientMessage::StreamInputLatency)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamSetVolume(stm_tok, volume) => try_stream!(self, stm_tok)
+ .set_volume(volume)
+ .map(|_| ClientMessage::StreamVolumeSet)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamSetName(stm_tok, ref name) => try_stream!(self, stm_tok)
+ .set_name(name)
+ .map(|_| ClientMessage::StreamNameSet)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetCurrentDevice(stm_tok) => try_stream!(self, stm_tok)
+ .current_device()
+ .map(|device| ClientMessage::StreamCurrentDevice(Device::from(device)))
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamRegisterDeviceChangeCallback(stm_tok, enable) => {
+ try_stream!(self, stm_tok)
+ .register_device_changed_callback(if enable {
+ Some(device_change_cb_c)
+ } else {
+ None
+ })
+ .map(|_| ClientMessage::StreamRegisterDeviceChangeCallback)
+ .unwrap_or_else(error)
+ }
+
+ ServerMessage::ContextSetupDeviceCollectionCallback => {
+ if let Ok((ipc_server, ipc_client)) = MessageStream::anonymous_ipc_pair() {
+ debug!(
+ "Created device collection RPC pair: {:?}-{:?}",
+ ipc_server, ipc_client
+ );
+
+ // This code is currently running on the Client/Server RPC
+ // handling thread. We need to move the registration of the
+ // bind_client to the callback RPC handling thread. This is
+ // done by spawning a future on `handle`.
+ let (tx, rx) = oneshot::channel();
+ self.callback_thread
+ .spawn(futures::future::lazy(move || {
+ let handle = reactor::Handle::default();
+ let stream = ipc_server.into_tokio_ipc(&handle).unwrap();
+ let transport = framed(stream, Default::default());
+ let rpc = rpc::bind_client::<DeviceCollectionClient>(transport);
+ drop(tx.send(rpc));
+ Ok(())
+ }))
+ .expect("Failed to spawn DeviceCollectionClient");
+
+ if let Ok(rpc) = rx.wait() {
+ self.cbs = Some(Rc::new(RefCell::new(CubebServerCallbacks {
+ rpc,
+ devtype: cubeb::DeviceType::empty(),
+ })));
+ let fd = RegisterDeviceCollectionChanged {
+ platform_handle: SerializableHandle::new(
+ PlatformHandle::from(ipc_client),
+ self.remote_pid.unwrap(),
+ ),
+ };
+
+ ClientMessage::ContextSetupDeviceCollectionCallback(fd)
+ } else {
+ warn!("Failed to setup RPC client");
+ error(cubeb::Error::error())
+ }
+ } else {
+ warn!("Failed to create RPC pair");
+ error(cubeb::Error::error())
+ }
+ }
+
+ ServerMessage::ContextRegisterDeviceCollectionChanged(device_type, enable) => self
+ .process_register_device_collection_changed(
+ context,
+ manager,
+ cubeb::DeviceType::from_bits_truncate(device_type),
+ enable,
+ )
+ .unwrap_or_else(error),
+
+ #[cfg(target_os = "linux")]
+ ServerMessage::PromoteThreadToRealTime(thread_info) => {
+ let info = RtPriorityThreadInfo::deserialize(thread_info);
+ match promote_thread_to_real_time(info, 0, 48000) {
+ Ok(_) => {
+ info!("Promotion of content process thread to real-time OK");
+ }
+ Err(_) => {
+ warn!("Promotion of content process thread to real-time error");
+ }
+ }
+ ClientMessage::ThreadPromoted
+ }
+ };
+
+ trace!("process_msg: req={:?}, resp={:?}", msg, resp);
+
+ resp
+ }
+
+ fn process_register_device_collection_changed(
+ &mut self,
+ context: &cubeb::Context,
+ manager: &mut CubebDeviceCollectionManager,
+ devtype: cubeb::DeviceType,
+ enable: bool,
+ ) -> cubeb::Result<ClientMessage> {
+ if devtype == cubeb::DeviceType::UNKNOWN {
+ return Err(cubeb::Error::invalid_parameter());
+ }
+
+ assert!(self.cbs.is_some());
+ let cbs = self.cbs.as_ref().unwrap();
+
+ if enable {
+ manager.register(context, cbs, devtype)
+ } else {
+ manager.unregister(context, cbs, devtype)
+ }
+ .map(|_| ClientMessage::ContextRegisteredDeviceCollectionChanged)
+ }
+
+ // Stream create is special, so it's been separated from process_msg.
+ fn process_stream_create(&mut self, params: &StreamCreateParams) -> Result<ClientMessage> {
+ fn frame_size_in_bytes(params: Option<&StreamParams>) -> u16 {
+ params
+ .map(|p| {
+ let format = p.format.into();
+ let sample_size = match format {
+ cubeb::SampleFormat::S16LE
+ | cubeb::SampleFormat::S16BE
+ | cubeb::SampleFormat::S16NE => 2,
+ cubeb::SampleFormat::Float32LE
+ | cubeb::SampleFormat::Float32BE
+ | cubeb::SampleFormat::Float32NE => 4,
+ };
+ let channel_count = p.channels as u16;
+ sample_size * channel_count
+ })
+ .unwrap_or(0u16)
+ }
+
+ // Create the callback handling struct which is attached the cubeb stream.
+ let input_frame_size = frame_size_in_bytes(params.input_stream_params.as_ref());
+ let output_frame_size = frame_size_in_bytes(params.output_stream_params.as_ref());
+
+ let (ipc_server, ipc_client) = MessageStream::anonymous_ipc_pair()?;
+ debug!("Created callback pair: {:?}-{:?}", ipc_server, ipc_client);
+
+ // Estimate a safe shmem size for this stream configuration. If the server was configured with a fixed
+ // shm_area_size override, use that instead.
+ // TODO: Add a new cubeb API to query the precise buffer size required for a given stream config.
+ // https://github.com/mozilla/audioipc-2/issues/124
+ let shm_area_size = if self.shm_area_size == 0 {
+ let frame_size = output_frame_size.max(input_frame_size) as u32;
+ let in_rate = params.input_stream_params.map(|p| p.rate).unwrap_or(0);
+ let out_rate = params.output_stream_params.map(|p| p.rate).unwrap_or(0);
+ let rate = out_rate.max(in_rate);
+ // 1s of audio, rounded up to the nearest 64kB.
+ (((rate * frame_size) + 0xffff) & !0xffff) as usize
+ } else {
+ self.shm_area_size
+ };
+ debug!("shm_area_size = {}", shm_area_size);
+
+ let shm = SharedMem::new(&get_shm_id(), shm_area_size)?;
+
+ // This code is currently running on the Client/Server RPC
+ // handling thread. We need to move the registration of the
+ // bind_client to the callback RPC handling thread. This is
+ // done by spawning a future on `handle`.
+ let (tx, rx) = oneshot::channel();
+ self.callback_thread
+ .spawn(futures::future::lazy(move || {
+ let handle = reactor::Handle::default();
+ let stream = ipc_server.into_tokio_ipc(&handle).unwrap();
+ let transport = framed(stream, Default::default());
+ let rpc = rpc::bind_client::<CallbackClient>(transport);
+ drop(tx.send(rpc));
+ Ok(())
+ }))
+ .expect("Failed to spawn CallbackClient");
+
+ let rpc = match rx.wait() {
+ Ok(rpc) => rpc,
+ Err(_) => bail!("Failed to create callback rpc."),
+ };
+
+ let shm_handle = unsafe { shm.make_handle().unwrap() };
+ let shm_setup = Some(rpc.call(CallbackReq::SharedMem(
+ SerializableHandle::new(shm_handle, self.remote_pid.unwrap()),
+ shm_area_size,
+ )));
+
+ let cbs = Box::new(ServerStreamCallbacks {
+ input_frame_size,
+ output_frame_size,
+ shm,
+ rpc,
+ });
+
+ let entry = self.streams.vacant_entry();
+ let key = entry.key();
+ debug!("Registering stream {:?}", key);
+
+ entry.insert(ServerStream {
+ stream: None,
+ shm_setup,
+ cbs,
+ });
+
+ Ok(ClientMessage::StreamCreated(StreamCreate {
+ token: key,
+ platform_handle: SerializableHandle::new(
+ PlatformHandle::from(ipc_client),
+ self.remote_pid.unwrap(),
+ ),
+ }))
+ }
+
+ // Stream init is special, so it's been separated from process_msg.
+ fn process_stream_init(
+ &mut self,
+ context: &cubeb::Context,
+ stm_tok: usize,
+ params: &StreamInitParams,
+ ) -> Result<ClientMessage> {
+ // Create cubeb stream from params
+ let stream_name = params
+ .stream_name
+ .as_ref()
+ .and_then(|name| CStr::from_bytes_with_nul(name).ok());
+
+ // Map IPC handle back to cubeb_devid.
+ let input_device = self.devidmap.handle_to_id(params.input_device) as *const _;
+ let input_stream_params = params.input_stream_params.as_ref().map(|isp| unsafe {
+ cubeb::StreamParamsRef::from_ptr(isp as *const StreamParams as *mut _)
+ });
+
+ // Map IPC handle back to cubeb_devid.
+ let output_device = self.devidmap.handle_to_id(params.output_device) as *const _;
+ let output_stream_params = params.output_stream_params.as_ref().map(|osp| unsafe {
+ cubeb::StreamParamsRef::from_ptr(osp as *const StreamParams as *mut _)
+ });
+
+ let latency = params.latency_frames;
+
+ let server_stream = &mut self.streams[stm_tok];
+ assert!(size_of::<Box<ServerStreamCallbacks>>() == size_of::<usize>());
+ let user_ptr = server_stream.cbs.as_ref() as *const ServerStreamCallbacks as *mut c_void;
+
+ // SharedMem setup message should've been processed by client by now.
+ match server_stream.shm_setup.take().wait() {
+ Ok(Some(CallbackResp::SharedMem)) => {}
+ Ok(Some(CallbackResp::Error(e))) => {
+ // If the client replied with an error (e.g. client OOM), log error and fail stream init.
+ debug!(
+ "Shmem setup for stream {:?} failed (raw error {:?})",
+ stm_tok, e
+ );
+ return Ok(ClientMessage::Error(e));
+ }
+ Ok(r) => {
+ debug!(
+ "Shmem setup for stream {:?} failed (unexpected response {:?})",
+ stm_tok, r
+ );
+ return Ok(error(cubeb::Error::error()));
+ }
+ Err(e) => {
+ // If the client errored before responding, log error and fail stream init.
+ debug!(
+ "Shmem setup for stream {:?} failed (error {:?})",
+ stm_tok, e
+ );
+ return Err(e.into());
+ }
+ }
+
+ let stream = unsafe {
+ let stream = context.stream_init(
+ stream_name,
+ input_device,
+ input_stream_params,
+ output_device,
+ output_stream_params,
+ latency,
+ Some(data_cb_c),
+ Some(state_cb_c),
+ user_ptr,
+ );
+ match stream {
+ Ok(stream) => stream,
+ Err(e) => {
+ debug!("Unregistering stream {:?} (stream error {:?})", stm_tok, e);
+ self.streams.remove(stm_tok);
+ return Err(e.into());
+ }
+ }
+ };
+
+ server_stream.stream = Some(stream);
+
+ Ok(ClientMessage::StreamInitialized)
+ }
+}
+
+// C callable callbacks
+unsafe extern "C" fn data_cb_c(
+ _: *mut ffi::cubeb_stream,
+ user_ptr: *mut c_void,
+ input_buffer: *const c_void,
+ output_buffer: *mut c_void,
+ nframes: c_long,
+) -> c_long {
+ let ok = panic::catch_unwind(|| {
+ let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
+ let input = if input_buffer.is_null() {
+ &[]
+ } else {
+ let nbytes = nframes * c_long::from(cbs.input_frame_size);
+ slice::from_raw_parts(input_buffer as *const u8, nbytes as usize)
+ };
+ let output: &mut [u8] = if output_buffer.is_null() {
+ &mut []
+ } else {
+ let nbytes = nframes * c_long::from(cbs.output_frame_size);
+ slice::from_raw_parts_mut(output_buffer as *mut u8, nbytes as usize)
+ };
+ cbs.data_callback(input, output, nframes as isize) as c_long
+ });
+ // TODO: Return a CUBEB_ERROR result here once
+ // https://github.com/kinetiknz/cubeb/issues/553 is fixed.
+ ok.unwrap_or(0)
+}
+
+unsafe extern "C" fn state_cb_c(
+ _: *mut ffi::cubeb_stream,
+ user_ptr: *mut c_void,
+ state: ffi::cubeb_state,
+) {
+ let ok = panic::catch_unwind(|| {
+ let state = cubeb::State::from(state);
+ let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
+ cbs.state_callback(state);
+ });
+ ok.expect("State callback panicked");
+}
+
+unsafe extern "C" fn device_change_cb_c(user_ptr: *mut c_void) {
+ let ok = panic::catch_unwind(|| {
+ let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
+ cbs.device_change_callback();
+ });
+ ok.expect("Device change callback panicked");
+}
+
+unsafe extern "C" fn device_collection_changed_input_cb_c(
+ _: *mut ffi::cubeb,
+ user_ptr: *mut c_void,
+) {
+ let ok = panic::catch_unwind(|| {
+ let manager = &mut *(user_ptr as *mut CubebDeviceCollectionManager);
+ manager.device_collection_changed_callback(ffi::CUBEB_DEVICE_TYPE_INPUT);
+ });
+ ok.expect("Collection changed (input) callback panicked");
+}
+
+unsafe extern "C" fn device_collection_changed_output_cb_c(
+ _: *mut ffi::cubeb,
+ user_ptr: *mut c_void,
+) {
+ let ok = panic::catch_unwind(|| {
+ let manager = &mut *(user_ptr as *mut CubebDeviceCollectionManager);
+ manager.device_collection_changed_callback(ffi::CUBEB_DEVICE_TYPE_OUTPUT);
+ });
+ ok.expect("Collection changed (output) callback panicked");
+}
diff --git a/third_party/rust/audioipc/.cargo-checksum.json b/third_party/rust/audioipc/.cargo-checksum.json
new file mode 100644
index 0000000000..ed18b597c2
--- /dev/null
+++ b/third_party/rust/audioipc/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"cc366c0e99b334dfb1b281fdaff61931a60d4f759656ddfc60c7f439e8f71d4a","build.rs":"3f061cf9a989f63a71c693a543d26f7003e8b643c39c23ea555110252a2c39d2","src/async_msg.rs":"27c5c8215bcbe1364947065ac78198bcd4be7fbae0f5e49ea776cb454c5a6d2c","src/cmsg.rs":"97d8fe99ef94f75db9ed26cb4cf6faf9fbbc913cfa4152a8774ff4e76aead620","src/cmsghdr.c":"d7344b3dc15cdce410c68669b848bb81f7fe36362cd3699668cb613fa05180f8","src/codec.rs":"2e0a05968e07617adc6be0cbf04962c952c621e118f0db308eeeed5ccea4dfce","src/core.rs":"721de353d3b0b5126bf5b25cfb1f99244702309ce9f9f24cc2ce3c5858228794","src/errors.rs":"67a4a994d0724397657581cde153bdfc05ce86e7efc467f23fafc8f64df80fa4","src/framing.rs":"45122f0bc44458d8e111466437e784f0d17035309cb5f03d45a5861082168ea1","src/lib.rs":"1075018e9816776af743b31aa491b439bb28a300431cf24a183aa3ad7a1dec08","src/messages.rs":"45ca1c8aee63b991d0160f50f06d280527e7bc28972c28fa35f4f649836cadb0","src/messagestream_unix.rs":"786ea7d2d2993c21987d34c0617abd78dbaa57079de68ea3ebbf5611a052f60b","src/messagestream_win.rs":"f5b2a0e22f56a14af24a76a4c13a1d6b066fbea347132a5413bee0bd2b757753","src/msg.rs":"f5353e942f7818742190541e568685d6b4d6200b55bfc60e46ee3db05f802436","src/rpc/client/mod.rs":"04e80b689548e7888b34441a7224dfa8cf557b8b4164754daee95a95b76f9aee","src/rpc/client/proxy.rs":"8d9c9b38ecec4ab5ee3b6e4c2d7aea9dbb4f7cf5c25d39a5db0c76aa41008497","src/rpc/driver.rs":"dea4efc844485e98c21f766772422e3a5c9ac153ade32c0ff51287516a05690b","src/rpc/mod.rs":"3b14af0be2b4c7b30a0dab9cca353e092652a16e29002f5aeba24dca45e33d1e","src/rpc/server.rs":"7caf0b2d659783b4c5c9dd9efe4cb9a2e7d5955c0dfda3d2e79581116bb9334b","src/shm.rs":"94dee9454acfb4541b7757fd6763b8ecfe1961bb0da780caf463dc6509ba1d98","src/tokio_named_pipes.rs":"c0d74ab6330ebdec53db12f0f532b60897c37e70345c368f10165d9b31409643","src/tokio_uds_stream.rs":"652ab6c32855e9b57c7edbb6914f6f27c6b8c1cadebf87d4a127f0dc76aada52"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc/Cargo.toml b/third_party/rust/audioipc/Cargo.toml
new file mode 100644
index 0000000000..8a9dd3d484
--- /dev/null
+++ b/third_party/rust/audioipc/Cargo.toml
@@ -0,0 +1,49 @@
+[package]
+name = "audioipc"
+version = "0.2.5"
+authors = [
+ "Matthew Gregan <kinetik@flim.org>",
+ "Dan Glastonbury <dan.glastonbury@gmail.com>"
+ ]
+license = "ISC"
+description = "Remote Cubeb IPC"
+edition = "2018"
+
+[dependencies]
+bincode = "1.3"
+bytes = "0.4"
+cubeb = "0.10"
+futures = "0.1.29"
+log = "0.4"
+serde = "1"
+serde_derive = "1"
+tokio = { version="0.1", default-features=false, features = ["rt-full"] }
+tokio-io = "0.1"
+
+[target.'cfg(unix)'.dependencies]
+iovec = "0.1"
+libc = "0.2"
+mio = "0.6.19"
+mio-uds = "0.6.7"
+tokio-reactor = "0.1"
+memmap2 = "0.2"
+
+[target.'cfg(target_os = "linux")'.dependencies.audio_thread_priority]
+version = "0.26.1"
+default-features = false
+
+[target.'cfg(windows)'.dependencies]
+mio = "0.6.19"
+miow = "0.3.3"
+mio-named-pipes = { git = "https://github.com/kinetiknz/mio-named-pipes", rev = "21c26326f5f45f415c49eac4ba5bc41a2f961321" }
+winapi = { version = "0.3.6", features = ["combaseapi", "memoryapi", "objbase"] }
+
+[target.'cfg(target_os = "android")'.dependencies]
+ashmem = "0.1"
+
+[dependencies.error-chain]
+version = "0.12.0"
+default-features = false
+
+[build-dependencies]
+cc = "1.0"
diff --git a/third_party/rust/audioipc/build.rs b/third_party/rust/audioipc/build.rs
new file mode 100644
index 0000000000..30a08baf5a
--- /dev/null
+++ b/third_party/rust/audioipc/build.rs
@@ -0,0 +1,5 @@
+fn main() {
+ if std::env::var_os("CARGO_CFG_UNIX").is_some() {
+ cc::Build::new().file("src/cmsghdr.c").compile("cmsghdr");
+ }
+}
diff --git a/third_party/rust/audioipc/src/async_msg.rs b/third_party/rust/audioipc/src/async_msg.rs
new file mode 100644
index 0000000000..963cf2d533
--- /dev/null
+++ b/third_party/rust/audioipc/src/async_msg.rs
@@ -0,0 +1,194 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+//! Various async helpers modelled after futures-rs and tokio-io.
+
+#[cfg(unix)]
+use crate::msg::{RecvMsg, SendMsg};
+use bytes::{Buf, BufMut};
+use futures::{Async, Poll};
+#[cfg(unix)]
+use iovec::IoVec;
+#[cfg(unix)]
+use mio::Ready;
+use std::io;
+use tokio_io::{AsyncRead, AsyncWrite};
+
+pub trait AsyncRecvMsg: AsyncRead {
+ /// Pull some bytes from this source into the specified `Buf`, returning
+ /// how many bytes were read.
+ ///
+ /// The `buf` provided will have bytes read into it and the internal cursor
+ /// will be advanced if any bytes were read. Note that this method typically
+ /// will not reallocate the buffer provided.
+ fn recv_msg_buf<B>(&mut self, buf: &mut B, cmsg: &mut B) -> Poll<(usize, i32), io::Error>
+ where
+ Self: Sized,
+ B: BufMut;
+}
+
+/// A trait for writable objects which operated in an async fashion.
+///
+/// This trait inherits from `std::io::Write` and indicates that an I/O object is
+/// **nonblocking**, meaning that it will return an error instead of blocking
+/// when bytes cannot currently be written, but hasn't closed. Specifically
+/// this means that the `write` function for types that implement this trait
+/// can have a few return values:
+///
+/// * `Ok(n)` means that `n` bytes of data was immediately written .
+/// * `Err(e) if e.kind() == ErrorKind::WouldBlock` means that no data was
+/// written from the buffer provided. The I/O object is not currently
+/// writable but may become writable in the future.
+/// * `Err(e)` for other errors are standard I/O errors coming from the
+/// underlying object.
+pub trait AsyncSendMsg: AsyncWrite {
+ /// Write a `Buf` into this value, returning how many bytes were written.
+ ///
+ /// Note that this method will advance the `buf` provided automatically by
+ /// the number of bytes written.
+ fn send_msg_buf<B, C>(&mut self, buf: &mut B, cmsg: &C) -> Poll<usize, io::Error>
+ where
+ Self: Sized,
+ B: Buf,
+ C: Buf;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#[cfg(unix)]
+impl AsyncRecvMsg for super::AsyncMessageStream {
+ fn recv_msg_buf<B>(&mut self, buf: &mut B, cmsg: &mut B) -> Poll<(usize, i32), io::Error>
+ where
+ B: BufMut,
+ {
+ if let Async::NotReady =
+ <super::AsyncMessageStream>::poll_read_ready(self, Ready::readable())?
+ {
+ return Ok(Async::NotReady);
+ }
+ let r = unsafe {
+ // The `IoVec` type can't have a 0-length size, so we create a bunch
+ // of dummy versions on the stack with 1 length which we'll quickly
+ // overwrite.
+ let b1: &mut [u8] = &mut [0];
+ let b2: &mut [u8] = &mut [0];
+ let b3: &mut [u8] = &mut [0];
+ let b4: &mut [u8] = &mut [0];
+ let b5: &mut [u8] = &mut [0];
+ let b6: &mut [u8] = &mut [0];
+ let b7: &mut [u8] = &mut [0];
+ let b8: &mut [u8] = &mut [0];
+ let b9: &mut [u8] = &mut [0];
+ let b10: &mut [u8] = &mut [0];
+ let b11: &mut [u8] = &mut [0];
+ let b12: &mut [u8] = &mut [0];
+ let b13: &mut [u8] = &mut [0];
+ let b14: &mut [u8] = &mut [0];
+ let b15: &mut [u8] = &mut [0];
+ let b16: &mut [u8] = &mut [0];
+ let mut bufs: [&mut IoVec; 16] = [
+ b1.into(),
+ b2.into(),
+ b3.into(),
+ b4.into(),
+ b5.into(),
+ b6.into(),
+ b7.into(),
+ b8.into(),
+ b9.into(),
+ b10.into(),
+ b11.into(),
+ b12.into(),
+ b13.into(),
+ b14.into(),
+ b15.into(),
+ b16.into(),
+ ];
+ let n = buf.bytes_vec_mut(&mut bufs);
+ self.recv_msg(&mut bufs[..n], cmsg.bytes_mut())
+ };
+
+ match r {
+ Ok((n, cmsg_len, flags)) => {
+ unsafe {
+ buf.advance_mut(n);
+ }
+ unsafe {
+ cmsg.advance_mut(cmsg_len);
+ }
+ Ok((n, flags).into())
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ self.clear_read_ready(mio::Ready::readable())?;
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e),
+ }
+ }
+}
+
+#[cfg(unix)]
+impl AsyncSendMsg for super::AsyncMessageStream {
+ fn send_msg_buf<B, C>(&mut self, buf: &mut B, cmsg: &C) -> Poll<usize, io::Error>
+ where
+ B: Buf,
+ C: Buf,
+ {
+ if let Async::NotReady = <super::AsyncMessageStream>::poll_write_ready(self)? {
+ return Ok(Async::NotReady);
+ }
+ let r = {
+ // The `IoVec` type can't have a zero-length size, so create a dummy
+ // version from a 1-length slice which we'll overwrite with the
+ // `bytes_vec` method.
+ static DUMMY: &[u8] = &[0];
+ let nom = <&IoVec>::from(DUMMY);
+ let mut bufs = [
+ nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom, nom,
+ ];
+ let n = buf.bytes_vec(&mut bufs);
+ self.send_msg(&bufs[..n], cmsg.bytes())
+ };
+ match r {
+ Ok(n) => {
+ buf.advance(n);
+ Ok(Async::Ready(n))
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ self.clear_write_ready()?;
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e),
+ }
+ }
+}
+
+// AsyncRecvMsg/AsyncSendMsg are implemented for convenience on Windows and don't use the _cmsg parameter.
+#[cfg(windows)]
+impl AsyncRecvMsg for super::AsyncMessageStream {
+ fn recv_msg_buf<B>(&mut self, buf: &mut B, _cmsg: &mut B) -> Poll<(usize, i32), io::Error>
+ where
+ B: BufMut,
+ {
+ // _cmsg unused on Windows. Pass through to read_buf.
+ match <super::AsyncMessageStream>::read_buf(self, buf) {
+ Ok(Async::Ready(n)) => Ok(Async::Ready((n, 0))),
+ Ok(Async::NotReady) => Ok(Async::NotReady),
+ Err(e) => Err(e),
+ }
+ }
+}
+
+#[cfg(windows)]
+impl AsyncSendMsg for super::AsyncMessageStream {
+ fn send_msg_buf<B, C>(&mut self, buf: &mut B, _cmsg: &C) -> Poll<usize, io::Error>
+ where
+ B: Buf,
+ C: Buf,
+ {
+ // _cmsg unused on Windows. Pass through to write_buf.
+ <super::AsyncMessageStream>::write_buf(self, buf)
+ }
+}
diff --git a/third_party/rust/audioipc/src/cmsg.rs b/third_party/rust/audioipc/src/cmsg.rs
new file mode 100644
index 0000000000..70cb579755
--- /dev/null
+++ b/third_party/rust/audioipc/src/cmsg.rs
@@ -0,0 +1,180 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use bytes::{BufMut, Bytes, BytesMut};
+use libc::{self, cmsghdr};
+use std::convert::TryInto;
+use std::os::unix::io::RawFd;
+use std::{convert, mem, ops, slice};
+
+#[derive(Clone, Debug)]
+pub struct Fds {
+ fds: Bytes,
+}
+
+impl convert::AsRef<[RawFd]> for Fds {
+ fn as_ref(&self) -> &[RawFd] {
+ let n = self.fds.len() / mem::size_of::<RawFd>();
+ unsafe { slice::from_raw_parts(self.fds.as_ptr() as *const _, n) }
+ }
+}
+
+impl ops::Deref for Fds {
+ type Target = [RawFd];
+
+ #[inline]
+ fn deref(&self) -> &[RawFd] {
+ self.as_ref()
+ }
+}
+
+pub struct ControlMsgIter {
+ control: Bytes,
+}
+
+pub fn iterator(c: Bytes) -> ControlMsgIter {
+ ControlMsgIter { control: c }
+}
+
+impl Iterator for ControlMsgIter {
+ type Item = Fds;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ let control = self.control.clone();
+ let cmsghdr_len = len(0);
+
+ if control.len() < cmsghdr_len {
+ // No more entries---not enough data in `control` for a
+ // complete message.
+ return None;
+ }
+
+ let cmsg: &cmsghdr = unsafe { &*(control.as_ptr() as *const _) };
+ // The offset to the next cmsghdr in control. This must be
+ // aligned to a boundary that matches the type used to
+ // represent the length of the message.
+ let cmsg_len = cmsg.cmsg_len as usize;
+ let cmsg_space = space(cmsg_len - cmsghdr_len);
+ self.control = if cmsg_space > control.len() {
+ // No more entries---not enough data in `control` for a
+ // complete message.
+ Bytes::new()
+ } else {
+ control.slice_from(cmsg_space)
+ };
+
+ match (cmsg.cmsg_level, cmsg.cmsg_type) {
+ (libc::SOL_SOCKET, libc::SCM_RIGHTS) => {
+ trace!("Found SCM_RIGHTS...");
+ return Some(Fds {
+ fds: control.slice(cmsghdr_len, cmsg_len as _),
+ });
+ }
+ (level, kind) => {
+ trace!("Skipping cmsg level, {}, type={}...", level, kind);
+ }
+ }
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum Error {
+ /// Not enough space in storage to insert control mesage.
+ NoSpace,
+}
+
+#[must_use]
+pub struct ControlMsgBuilder {
+ result: Result<BytesMut, Error>,
+}
+
+pub fn builder(buf: &mut BytesMut) -> ControlMsgBuilder {
+ let buf = aligned(buf);
+ ControlMsgBuilder { result: Ok(buf) }
+}
+
+impl ControlMsgBuilder {
+ fn msg(mut self, level: libc::c_int, kind: libc::c_int, msg: &[u8]) -> Self {
+ self.result = self.result.and_then(|mut cmsg| {
+ let cmsg_space = space(msg.len());
+ if cmsg.remaining_mut() < cmsg_space {
+ return Err(Error::NoSpace);
+ }
+
+ // Some definitions of cmsghdr contain padding. Rather
+ // than try to keep an up-to-date #cfg list to handle
+ // that, just use a pre-zeroed struct to fill out any
+ // fields we don't care about.
+ let zeroed = unsafe { mem::zeroed() };
+ #[allow(clippy::needless_update)]
+ // `cmsg_len` is `usize` on some platforms, `u32` on others.
+ #[allow(clippy::useless_conversion)]
+ let cmsghdr = cmsghdr {
+ cmsg_len: len(msg.len()).try_into().unwrap(),
+ cmsg_level: level,
+ cmsg_type: kind,
+ ..zeroed
+ };
+
+ unsafe {
+ let cmsghdr_ptr = cmsg.bytes_mut().as_mut_ptr();
+ std::ptr::copy_nonoverlapping(
+ &cmsghdr as *const _ as *const _,
+ cmsghdr_ptr,
+ mem::size_of::<cmsghdr>(),
+ );
+ let cmsg_data_ptr = libc::CMSG_DATA(cmsghdr_ptr as _);
+ std::ptr::copy_nonoverlapping(msg.as_ptr(), cmsg_data_ptr, msg.len());
+ cmsg.advance_mut(cmsg_space);
+ }
+
+ Ok(cmsg)
+ });
+
+ self
+ }
+
+ pub fn rights(self, fds: &[RawFd]) -> Self {
+ self.msg(libc::SOL_SOCKET, libc::SCM_RIGHTS, fds.as_bytes())
+ }
+
+ pub fn finish(self) -> Result<Bytes, Error> {
+ self.result.map(|mut cmsg| cmsg.take().freeze())
+ }
+}
+
+trait AsBytes {
+ fn as_bytes(&self) -> &[u8];
+}
+
+impl<'a, T: Sized> AsBytes for &'a [T] {
+ fn as_bytes(&self) -> &[u8] {
+ // TODO: This should account for the alignment of T
+ let byte_count = self.len() * mem::size_of::<T>();
+ unsafe { slice::from_raw_parts(self.as_ptr() as *const _, byte_count) }
+ }
+}
+
+fn aligned(buf: &BytesMut) -> BytesMut {
+ let mut aligned_buf = buf.clone();
+ aligned_buf.reserve(buf.remaining_mut());
+ let cmsghdr_align = mem::align_of::<cmsghdr>();
+ let n = unsafe { aligned_buf.bytes_mut().as_ptr() } as usize & (cmsghdr_align - 1);
+ if n != 0 {
+ unsafe { aligned_buf.advance_mut(n) };
+ drop(aligned_buf.take());
+ }
+ aligned_buf
+}
+
+fn len(len: usize) -> usize {
+ unsafe { libc::CMSG_LEN(len.try_into().unwrap()) as usize }
+}
+
+pub fn space(len: usize) -> usize {
+ unsafe { libc::CMSG_SPACE(len.try_into().unwrap()) as usize }
+}
diff --git a/third_party/rust/audioipc/src/cmsghdr.c b/third_party/rust/audioipc/src/cmsghdr.c
new file mode 100644
index 0000000000..82d7852867
--- /dev/null
+++ b/third_party/rust/audioipc/src/cmsghdr.c
@@ -0,0 +1,23 @@
+#include <sys/socket.h>
+#include <inttypes.h>
+#include <string.h>
+
+const uint8_t*
+cmsghdr_bytes(size_t* size)
+{
+ int myfd = 0;
+
+ static union {
+ uint8_t buf[CMSG_SPACE(sizeof(myfd))];
+ struct cmsghdr align;
+ } u;
+
+ u.align.cmsg_len = CMSG_LEN(sizeof(myfd));
+ u.align.cmsg_level = SOL_SOCKET;
+ u.align.cmsg_type = SCM_RIGHTS;
+
+ memcpy(CMSG_DATA(&u.align), &myfd, sizeof(myfd));
+
+ *size = sizeof(u);
+ return (const uint8_t*)&u.buf;
+}
diff --git a/third_party/rust/audioipc/src/codec.rs b/third_party/rust/audioipc/src/codec.rs
new file mode 100644
index 0000000000..f53a13779d
--- /dev/null
+++ b/third_party/rust/audioipc/src/codec.rs
@@ -0,0 +1,187 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+//! `Encoder`s and `Decoder`s from items to/from `BytesMut` buffers.
+
+use bincode::{self, Options};
+use bytes::{BufMut, ByteOrder, BytesMut, LittleEndian};
+use serde::de::DeserializeOwned;
+use serde::ser::Serialize;
+use std::fmt::Debug;
+use std::io;
+use std::marker::PhantomData;
+
+////////////////////////////////////////////////////////////////////////////////
+// Split buffer into size delimited frames - This appears more complicated than
+// might be necessary due to handling the possibility of messages being split
+// across reads.
+
+pub trait Codec {
+ /// The type of items to be encoded into byte buffer
+ type In;
+
+ /// The type of items to be returned by decoding from byte buffer
+ type Out;
+
+ /// Attempts to decode a frame from the provided buffer of bytes.
+ fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Out>>;
+
+ /// A default method available to be called when there are no more bytes
+ /// available to be read from the I/O.
+ fn decode_eof(&mut self, buf: &mut BytesMut) -> io::Result<Self::Out> {
+ match self.decode(buf)? {
+ Some(frame) => Ok(frame),
+ None => Err(io::Error::new(
+ io::ErrorKind::Other,
+ "bytes remaining on stream",
+ )),
+ }
+ }
+
+ /// Encodes a frame intox the buffer provided.
+ fn encode(&mut self, msg: Self::In, buf: &mut BytesMut) -> io::Result<()>;
+}
+
+/// Codec based upon bincode serialization
+///
+/// Messages that have been serialized using bincode are prefixed with
+/// the length of the message to aid in deserialization, so that it's
+/// known if enough data has been received to decode a complete
+/// message.
+pub struct LengthDelimitedCodec<In, Out> {
+ state: State,
+ __in: PhantomData<In>,
+ __out: PhantomData<Out>,
+}
+
+enum State {
+ Length,
+ Data(usize),
+}
+
+const MAX_MESSAGE_LEN: u64 = 1024 * 1024;
+const MESSAGE_LENGTH_SIZE: usize = std::mem::size_of::<u32>();
+// TODO: static assert that MAX_MESSAGE_LEN can be encoded into MESSAGE_LENGTH_SIZE.
+
+impl<In, Out> Default for LengthDelimitedCodec<In, Out> {
+ fn default() -> Self {
+ LengthDelimitedCodec {
+ state: State::Length,
+ __in: PhantomData,
+ __out: PhantomData,
+ }
+ }
+}
+
+impl<In, Out> LengthDelimitedCodec<In, Out> {
+ // Lengths are encoded as little endian u32
+ fn decode_length(&mut self, buf: &mut BytesMut) -> Option<usize> {
+ if buf.len() < MESSAGE_LENGTH_SIZE {
+ // Not enough data
+ return None;
+ }
+
+ let n = LittleEndian::read_u32(buf.as_ref());
+
+ // Consume the length field
+ let _ = buf.split_to(MESSAGE_LENGTH_SIZE);
+
+ Some(n as usize)
+ }
+
+ fn decode_data(&mut self, buf: &mut BytesMut, n: usize) -> io::Result<Option<Out>>
+ where
+ Out: DeserializeOwned + Debug,
+ {
+ // At this point, the buffer has already had the required capacity
+ // reserved. All there is to do is read.
+ if buf.len() < n {
+ return Ok(None);
+ }
+
+ let buf = buf.split_to(n).freeze();
+
+ trace!("Attempting to decode");
+ let msg = bincode::options()
+ .deserialize::<Out>(buf.as_ref())
+ .map_err(|e| match *e {
+ bincode::ErrorKind::Io(e) => e,
+ _ => io::Error::new(io::ErrorKind::Other, *e),
+ })?;
+
+ trace!("... Decoded {:?}", msg);
+ Ok(Some(msg))
+ }
+}
+
+impl<In, Out> Codec for LengthDelimitedCodec<In, Out>
+where
+ In: Serialize + Debug,
+ Out: DeserializeOwned + Debug,
+{
+ type In = In;
+ type Out = Out;
+
+ fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Out>> {
+ let n = match self.state {
+ State::Length => {
+ match self.decode_length(buf) {
+ Some(n) => {
+ self.state = State::Data(n);
+
+ // Ensure that the buffer has enough space to read the
+ // incoming payload
+ buf.reserve(n);
+
+ n
+ }
+ None => return Ok(None),
+ }
+ }
+ State::Data(n) => n,
+ };
+
+ match self.decode_data(buf, n)? {
+ Some(data) => {
+ // Update the decode state
+ self.state = State::Length;
+
+ // Make sure the buffer has enough space to read the next head
+ buf.reserve(MESSAGE_LENGTH_SIZE);
+
+ Ok(Some(data))
+ }
+ None => Ok(None),
+ }
+ }
+
+ fn encode(&mut self, item: Self::In, buf: &mut BytesMut) -> io::Result<()> {
+ trace!("Attempting to encode");
+ let encoded_len = bincode::options().serialized_size(&item).unwrap();
+ if encoded_len > MAX_MESSAGE_LEN {
+ trace!("oversized message {}", encoded_len);
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "encoded message too big",
+ ));
+ }
+
+ buf.reserve((encoded_len as usize) + MESSAGE_LENGTH_SIZE);
+
+ buf.put_u32_le(encoded_len as u32);
+
+ if let Err(e) = bincode::options()
+ .with_limit(encoded_len)
+ .serialize_into::<_, Self::In>(&mut buf.writer(), &item)
+ {
+ match *e {
+ bincode::ErrorKind::Io(e) => return Err(e),
+ _ => return Err(io::Error::new(io::ErrorKind::Other, *e)),
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/third_party/rust/audioipc/src/core.rs b/third_party/rust/audioipc/src/core.rs
new file mode 100644
index 0000000000..0afc1f12f3
--- /dev/null
+++ b/third_party/rust/audioipc/src/core.rs
@@ -0,0 +1,85 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+// Ease accessing reactor::Core handles.
+
+use futures::sync::oneshot;
+use std::sync::mpsc;
+use std::{fmt, io, thread};
+use tokio::runtime::current_thread;
+
+struct Inner {
+ join: thread::JoinHandle<()>,
+ shutdown: oneshot::Sender<()>,
+}
+
+pub struct CoreThread {
+ inner: Option<Inner>,
+ handle: current_thread::Handle,
+}
+
+impl CoreThread {
+ pub fn handle(&self) -> current_thread::Handle {
+ self.handle.clone()
+ }
+}
+
+impl Drop for CoreThread {
+ fn drop(&mut self) {
+ debug!("Shutting down {:?}", self);
+ if let Some(inner) = self.inner.take() {
+ let _ = inner.shutdown.send(());
+ drop(inner.join.join());
+ }
+ }
+}
+
+impl fmt::Debug for CoreThread {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ // f.debug_tuple("CoreThread").field(&"...").finish()
+ f.debug_tuple("CoreThread").field(&self.handle).finish()
+ }
+}
+
+pub fn spawn_thread<S, F, D>(name: S, f: F, d: D) -> io::Result<CoreThread>
+where
+ S: Into<String>,
+ F: FnOnce() -> io::Result<()> + Send + 'static,
+ D: FnOnce() + Send + 'static,
+{
+ let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
+ let (handle_tx, handle_rx) = mpsc::channel::<current_thread::Handle>();
+
+ let join = thread::Builder::new().name(name.into()).spawn(move || {
+ let mut rt =
+ current_thread::Runtime::new().expect("Failed to create current_thread::Runtime");
+ let handle = rt.handle();
+ drop(handle_tx.send(handle));
+
+ rt.spawn(futures::future::lazy(|| {
+ let _ = f();
+ Ok(())
+ }));
+
+ let _ = rt.block_on(shutdown_rx);
+ trace!("thread shutdown...");
+ d();
+ })?;
+
+ let handle = handle_rx.recv().map_err(|_| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ "Failed to receive remote handle from spawned thread",
+ )
+ })?;
+
+ Ok(CoreThread {
+ inner: Some(Inner {
+ join,
+ shutdown: shutdown_tx,
+ }),
+ handle,
+ })
+}
diff --git a/third_party/rust/audioipc/src/errors.rs b/third_party/rust/audioipc/src/errors.rs
new file mode 100644
index 0000000000..f4f3e32f5f
--- /dev/null
+++ b/third_party/rust/audioipc/src/errors.rs
@@ -0,0 +1,18 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+error_chain! {
+ // Maybe replace with chain_err to improve the error info.
+ foreign_links {
+ Bincode(bincode::Error);
+ Io(std::io::Error);
+ Cubeb(cubeb::Error);
+ }
+
+ // Replace bail!(str) with explicit errors.
+ errors {
+ Disconnected
+ }
+}
diff --git a/third_party/rust/audioipc/src/framing.rs b/third_party/rust/audioipc/src/framing.rs
new file mode 100644
index 0000000000..3af16fb2b3
--- /dev/null
+++ b/third_party/rust/audioipc/src/framing.rs
@@ -0,0 +1,396 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::async_msg::{AsyncRecvMsg, AsyncSendMsg};
+#[cfg(unix)]
+use crate::cmsg;
+use crate::codec::Codec;
+#[cfg(windows)]
+use crate::duplicate_platform_handle;
+use crate::messages::AssocRawPlatformHandle;
+use bytes::{Bytes, BytesMut, IntoBuf};
+use futures::{task, AsyncSink, Poll, Sink, StartSend, Stream};
+use std::collections::VecDeque;
+#[cfg(unix)]
+use std::mem;
+#[cfg(unix)]
+use std::os::unix::io::RawFd;
+use std::{fmt, io};
+
+const INITIAL_CAPACITY: usize = 1024;
+const BACKPRESSURE_THRESHOLD: usize = 4 * INITIAL_CAPACITY;
+
+#[cfg(unix)]
+struct IncomingFd {
+ cmsg: BytesMut,
+ recv_fd: Option<cmsg::ControlMsgIter>,
+}
+
+#[cfg(unix)]
+impl IncomingFd {
+ pub fn new() -> Self {
+ IncomingFd {
+ cmsg: BytesMut::with_capacity(cmsg::space(mem::size_of::<RawFd>())),
+ recv_fd: None,
+ }
+ }
+
+ pub fn take_fd(&mut self) -> Option<RawFd> {
+ loop {
+ let fd = self
+ .recv_fd
+ .as_mut()
+ .and_then(|recv_fd| recv_fd.next())
+ .map(|fd| {
+ assert_eq!(fd.len(), 1);
+ fd[0]
+ });
+
+ if fd.is_some() {
+ return fd;
+ }
+
+ if self.cmsg.is_empty() {
+ return None;
+ }
+
+ self.recv_fd = Some(cmsg::iterator(self.cmsg.take().freeze()));
+ }
+ }
+
+ pub fn cmsg(&mut self) -> &mut BytesMut {
+ self.cmsg.reserve(cmsg::space(mem::size_of::<RawFd>()));
+ &mut self.cmsg
+ }
+}
+
+#[derive(Debug)]
+struct Frame {
+ msgs: Bytes,
+ handle: Option<Bytes>,
+}
+
+/// A unified `Stream` and `Sink` interface over an I/O object, using
+/// the `Codec` trait to encode and decode the payload.
+pub struct Framed<A, C> {
+ io: A,
+ codec: C,
+ // Stream
+ read_buf: BytesMut,
+ #[cfg(unix)]
+ incoming_fd: IncomingFd,
+ is_readable: bool,
+ eof: bool,
+ // Sink
+ frames: VecDeque<Frame>,
+ write_buf: BytesMut,
+ #[cfg(unix)]
+ outgoing_fd: BytesMut,
+}
+
+impl<A, C> Framed<A, C>
+where
+ A: AsyncSendMsg,
+{
+ // If there is a buffered frame, try to write it to `A`
+ fn do_write(&mut self) -> Poll<(), io::Error> {
+ trace!("do_write...");
+ // Create a frame from any pending message in `write_buf`.
+ if !self.write_buf.is_empty() {
+ self.set_frame(None);
+ }
+
+ trace!("pending frames: {:?}", self.frames);
+
+ let mut processed = 0;
+
+ loop {
+ let n = match self.frames.front() {
+ Some(frame) => {
+ trace!("sending msg {:?}, handle {:?}", frame.msgs, frame.handle);
+ let mut msgs = frame.msgs.clone().into_buf();
+ let handle = match frame.handle {
+ Some(ref handle) => handle.clone(),
+ None => Bytes::new(),
+ }
+ .into_buf();
+ try_ready!(self.io.send_msg_buf(&mut msgs, &handle))
+ }
+ _ => {
+ // No pending frames.
+ return Ok(().into());
+ }
+ };
+
+ match self.frames.pop_front() {
+ Some(mut frame) => {
+ processed += 1;
+
+ // Close any handle that was sent. The handle is
+ // encoded in cmsg format inside frame.handle. Use
+ // the cmsg iterator to access msg and extract
+ // raw handle to close.
+ #[cfg(unix)]
+ if let Some(cmsg) = frame.handle.take() {
+ for handle in cmsg::iterator(cmsg) {
+ assert_eq!(handle.len(), 1);
+ unsafe {
+ super::close_platform_handle(handle[0]);
+ }
+ }
+ }
+
+ if n != frame.msgs.len() {
+ // If only part of the message was sent then
+ // re-queue the remaining message at the head
+ // of the queue. (Don't need to resend the handle
+ // since it has been sent with the first
+ // part.)
+ drop(frame.msgs.split_to(n));
+ self.frames.push_front(frame);
+ break;
+ }
+ }
+ _ => panic!(),
+ }
+ }
+ trace!("process {} frames", processed);
+ trace!("pending frames: {:?}", self.frames);
+
+ Ok(().into())
+ }
+
+ fn set_frame(&mut self, handle: Option<Bytes>) {
+ if self.write_buf.is_empty() {
+ assert!(handle.is_none());
+ trace!("set_frame: No pending messages...");
+ return;
+ }
+
+ let msgs = self.write_buf.take().freeze();
+ trace!("set_frame: msgs={:?} handle={:?}", msgs, handle);
+
+ self.frames.push_back(Frame { msgs, handle });
+ }
+}
+
+impl<A, C> Stream for Framed<A, C>
+where
+ A: AsyncRecvMsg,
+ C: Codec,
+ C::Out: AssocRawPlatformHandle,
+{
+ type Item = C::Out;
+ type Error = io::Error;
+
+ fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
+ loop {
+ // Repeatedly call `decode` or `decode_eof` as long as it is
+ // "readable". Readable is defined as not having returned `None`. If
+ // the upstream has returned EOF, and the decoder is no longer
+ // readable, it can be assumed that the decoder will never become
+ // readable again, at which point the stream is terminated.
+ if self.is_readable {
+ if self.eof {
+ #[allow(unused_mut)]
+ let mut item = self.codec.decode_eof(&mut self.read_buf)?;
+ #[cfg(unix)]
+ item.set_owned_handle(|| self.incoming_fd.take_fd());
+ return Ok(Some(item).into());
+ }
+
+ trace!("attempting to decode a frame");
+
+ #[allow(unused_mut)]
+ if let Some(mut item) = self.codec.decode(&mut self.read_buf)? {
+ trace!("frame decoded from buffer");
+ #[cfg(unix)]
+ item.set_owned_handle(|| self.incoming_fd.take_fd());
+ return Ok(Some(item).into());
+ }
+
+ self.is_readable = false;
+ }
+
+ assert!(!self.eof);
+
+ // XXX(kinetik): work around tokio_named_pipes assuming at least 1kB available.
+ #[cfg(windows)]
+ self.read_buf.reserve(INITIAL_CAPACITY);
+
+ // Otherwise, try to read more data and try again. Make sure we've
+ // got room for at least one byte to read to ensure that we don't
+ // get a spurious 0 that looks like EOF
+ #[cfg(unix)]
+ let incoming_handle = self.incoming_fd.cmsg();
+ #[cfg(windows)]
+ let incoming_handle = &mut BytesMut::new();
+ let (n, _) = try_ready!(self.io.recv_msg_buf(&mut self.read_buf, incoming_handle));
+
+ if n == 0 {
+ self.eof = true;
+ }
+
+ self.is_readable = true;
+ }
+ }
+}
+
+impl<A, C> Sink for Framed<A, C>
+where
+ A: AsyncSendMsg,
+ C: Codec,
+ C::In: AssocRawPlatformHandle + fmt::Debug,
+{
+ type SinkItem = C::In;
+ type SinkError = io::Error;
+
+ fn start_send(
+ &mut self,
+ mut item: Self::SinkItem,
+ ) -> StartSend<Self::SinkItem, Self::SinkError> {
+ trace!("start_send: item={:?}", item);
+
+ // If the buffer is already over BACKPRESSURE_THRESHOLD,
+ // then attempt to flush it. If after flush it's *still*
+ // over BACKPRESSURE_THRESHOLD, then reject the send.
+ if self.write_buf.len() > BACKPRESSURE_THRESHOLD {
+ self.poll_complete()?;
+ if self.write_buf.len() > BACKPRESSURE_THRESHOLD {
+ return Ok(AsyncSink::NotReady(item));
+ }
+ }
+
+ // Take handle ownership here for `set_frame` to keep handle alive until `do_write`,
+ // otherwise handle would be closed too early (i.e. when `item` is dropped).
+ let handle = item.take_handle_for_send();
+
+ // On Windows, the handle is transferred by duplicating it into the target remote process during message send.
+ #[cfg(windows)]
+ if let Some((handle, target_pid)) = handle {
+ let remote_handle = unsafe { duplicate_platform_handle(handle, Some(target_pid))? };
+ trace!(
+ "item handle: {:?} remote_handle: {:?}",
+ handle,
+ remote_handle
+ );
+ // The new handle in the remote process is indicated by updating the handle stored in the item with the expected
+ // value on the remote.
+ item.set_remote_handle_value(|| Some(remote_handle));
+ }
+ // On Unix, the handle is encoded into a cmsg buffer for out-of-band transport via sendmsg.
+ #[cfg(unix)]
+ if let Some((handle, _)) = handle {
+ item.set_remote_handle_value(|| Some(handle));
+ }
+
+ self.codec.encode(item, &mut self.write_buf)?;
+
+ if handle.is_some() {
+ // Handle ownership is transferred into the cmsg buffer here; the local handle is closed
+ // after sending in `do_write`.
+ #[cfg(unix)]
+ let handle = handle.and_then(|handle| {
+ cmsg::builder(&mut self.outgoing_fd)
+ .rights(&[handle.0])
+ .finish()
+ .ok()
+ });
+
+ // No cmsg buffer on Windows, but we still want to trigger `set_frame`, so just pass `None`.
+ #[cfg(windows)]
+ let handle = None;
+
+ // Enforce splitting sends on messages that contain handles.
+ self.set_frame(handle);
+ }
+
+ Ok(AsyncSink::Ready)
+ }
+
+ fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
+ trace!("flushing framed transport");
+
+ try_ready!(self.do_write());
+
+ try_nb!(self.io.flush());
+
+ trace!("framed transport flushed");
+ Ok(().into())
+ }
+
+ fn close(&mut self) -> Poll<(), Self::SinkError> {
+ if task::is_in_task() {
+ try_ready!(self.poll_complete());
+ }
+ self.io.shutdown()
+ }
+}
+
+pub fn framed<A, C>(io: A, codec: C) -> Framed<A, C> {
+ Framed {
+ io,
+ codec,
+ read_buf: BytesMut::with_capacity(INITIAL_CAPACITY),
+ #[cfg(unix)]
+ incoming_fd: IncomingFd::new(),
+ is_readable: false,
+ eof: false,
+ frames: VecDeque::new(),
+ write_buf: BytesMut::with_capacity(INITIAL_CAPACITY),
+ #[cfg(unix)]
+ outgoing_fd: BytesMut::with_capacity(cmsg::space(mem::size_of::<RawFd>())),
+ }
+}
+
+#[cfg(all(test, unix))]
+mod tests {
+ use bytes::BufMut;
+
+ extern "C" {
+ fn cmsghdr_bytes(size: *mut libc::size_t) -> *const u8;
+ }
+
+ fn cmsg_bytes() -> &'static [u8] {
+ let mut size = 0;
+ unsafe {
+ let ptr = cmsghdr_bytes(&mut size);
+ std::slice::from_raw_parts(ptr, size)
+ }
+ }
+
+ #[test]
+ fn single_cmsg() {
+ let mut incoming = super::IncomingFd::new();
+
+ incoming.cmsg().put_slice(cmsg_bytes());
+ assert!(incoming.take_fd().is_some());
+ assert!(incoming.take_fd().is_none());
+ }
+
+ #[test]
+ fn multiple_cmsg_1() {
+ let mut incoming = super::IncomingFd::new();
+
+ incoming.cmsg().put_slice(cmsg_bytes());
+ assert!(incoming.take_fd().is_some());
+ incoming.cmsg().put_slice(cmsg_bytes());
+ assert!(incoming.take_fd().is_some());
+ assert!(incoming.take_fd().is_none());
+ }
+
+ #[test]
+ fn multiple_cmsg_2() {
+ let mut incoming = super::IncomingFd::new();
+
+ incoming.cmsg().put_slice(cmsg_bytes());
+ incoming.cmsg().put_slice(cmsg_bytes());
+ assert!(incoming.take_fd().is_some());
+ incoming.cmsg().put_slice(cmsg_bytes());
+ assert!(incoming.take_fd().is_some());
+ assert!(incoming.take_fd().is_some());
+ assert!(incoming.take_fd().is_none());
+ }
+}
diff --git a/third_party/rust/audioipc/src/lib.rs b/third_party/rust/audioipc/src/lib.rs
new file mode 100644
index 0000000000..02f7b2457b
--- /dev/null
+++ b/third_party/rust/audioipc/src/lib.rs
@@ -0,0 +1,207 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+#![warn(unused_extern_crates)]
+#![recursion_limit = "1024"]
+#[macro_use]
+extern crate error_chain;
+#[macro_use]
+extern crate log;
+#[macro_use]
+extern crate serde_derive;
+#[macro_use]
+extern crate futures;
+#[macro_use]
+extern crate tokio_io;
+
+mod async_msg;
+#[cfg(unix)]
+mod cmsg;
+pub mod codec;
+pub mod core;
+#[allow(deprecated)]
+pub mod errors;
+pub mod framing;
+pub mod messages;
+#[cfg(unix)]
+mod msg;
+pub mod rpc;
+pub mod shm;
+
+// TODO: Remove local fork when https://github.com/tokio-rs/tokio/pull/1294 is resolved.
+#[cfg(unix)]
+mod tokio_uds_stream;
+
+#[cfg(windows)]
+mod tokio_named_pipes;
+
+pub use crate::messages::{ClientMessage, ServerMessage};
+
+#[cfg(unix)]
+use std::os::unix::io::IntoRawFd;
+#[cfg(windows)]
+use std::os::windows::io::IntoRawHandle;
+
+// This must match the definition of
+// ipc::FileDescriptor::PlatformHandleType in Gecko.
+#[cfg(windows)]
+pub type PlatformHandleType = std::os::windows::raw::HANDLE;
+#[cfg(unix)]
+pub type PlatformHandleType = libc::c_int;
+
+// This stands in for RawFd/RawHandle.
+#[derive(Debug)]
+pub struct PlatformHandle(PlatformHandleType);
+
+#[cfg(unix)]
+pub const INVALID_HANDLE_VALUE: PlatformHandleType = -1isize as PlatformHandleType;
+
+#[cfg(windows)]
+pub const INVALID_HANDLE_VALUE: PlatformHandleType = winapi::um::handleapi::INVALID_HANDLE_VALUE;
+
+#[cfg(unix)]
+fn valid_handle(handle: PlatformHandleType) -> bool {
+ handle >= 0
+}
+
+#[cfg(windows)]
+fn valid_handle(handle: PlatformHandleType) -> bool {
+ handle != INVALID_HANDLE_VALUE && !handle.is_null()
+}
+
+impl PlatformHandle {
+ pub fn new(raw: PlatformHandleType) -> PlatformHandle {
+ assert!(valid_handle(raw));
+ PlatformHandle(raw)
+ }
+
+ #[cfg(windows)]
+ pub fn from<T: IntoRawHandle>(from: T) -> PlatformHandle {
+ PlatformHandle::new(from.into_raw_handle())
+ }
+
+ #[cfg(unix)]
+ pub fn from<T: IntoRawFd>(from: T) -> PlatformHandle {
+ PlatformHandle::new(from.into_raw_fd())
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn into_raw(self) -> PlatformHandleType {
+ let handle = self.0;
+ std::mem::forget(self);
+ handle
+ }
+
+ #[cfg(unix)]
+ pub fn duplicate(h: PlatformHandleType) -> Result<PlatformHandle, std::io::Error> {
+ unsafe {
+ let newfd = libc::dup(h);
+ if !valid_handle(newfd) {
+ return Err(std::io::Error::last_os_error());
+ }
+ Ok(PlatformHandle::from(newfd))
+ }
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ #[cfg(windows)]
+ pub unsafe fn duplicate(h: PlatformHandleType) -> Result<PlatformHandle, std::io::Error> {
+ let dup = duplicate_platform_handle(h, None)?;
+ Ok(PlatformHandle::new(dup))
+ }
+}
+
+impl Drop for PlatformHandle {
+ fn drop(&mut self) {
+ unsafe { close_platform_handle(self.0) }
+ }
+}
+
+#[cfg(unix)]
+unsafe fn close_platform_handle(handle: PlatformHandleType) {
+ libc::close(handle);
+}
+
+#[cfg(windows)]
+unsafe fn close_platform_handle(handle: PlatformHandleType) {
+ winapi::um::handleapi::CloseHandle(handle);
+}
+
+#[cfg(windows)]
+use winapi::shared::minwindef::DWORD;
+
+// Duplicate `source_handle`.
+// - If `target_pid` is `Some(...)`, `source_handle` is closed.
+// - If `target_pid` is `None`, `source_handle` is not closed.
+#[cfg(windows)]
+pub(crate) unsafe fn duplicate_platform_handle(
+ source_handle: PlatformHandleType,
+ target_pid: Option<DWORD>,
+) -> Result<PlatformHandleType, std::io::Error> {
+ use winapi::shared::minwindef::FALSE;
+ use winapi::um::{handleapi, processthreadsapi, winnt};
+
+ let source = processthreadsapi::GetCurrentProcess();
+ let (target, close_source) = if let Some(pid) = target_pid {
+ let target = processthreadsapi::OpenProcess(winnt::PROCESS_DUP_HANDLE, FALSE, pid);
+ if !valid_handle(target) {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "invalid target process",
+ ));
+ }
+ (target, true)
+ } else {
+ (source, false)
+ };
+
+ let mut target_handle = std::ptr::null_mut();
+ let mut options = winnt::DUPLICATE_SAME_ACCESS;
+ if close_source {
+ options |= winnt::DUPLICATE_CLOSE_SOURCE;
+ }
+ let ok = handleapi::DuplicateHandle(
+ source,
+ source_handle,
+ target,
+ &mut target_handle,
+ 0,
+ FALSE,
+ options,
+ );
+ handleapi::CloseHandle(target);
+ if ok == FALSE {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "DuplicateHandle failed",
+ ));
+ }
+ Ok(target_handle)
+}
+
+#[cfg(unix)]
+pub mod messagestream_unix;
+#[cfg(unix)]
+pub use crate::messagestream_unix::*;
+
+#[cfg(windows)]
+pub mod messagestream_win;
+#[cfg(windows)]
+pub use messagestream_win::*;
+
+#[cfg(windows)]
+pub fn server_platform_init() {
+ use winapi::shared::winerror;
+ use winapi::um::combaseapi;
+ use winapi::um::objbase;
+
+ unsafe {
+ let r = combaseapi::CoInitializeEx(std::ptr::null_mut(), objbase::COINIT_MULTITHREADED);
+ assert!(winerror::SUCCEEDED(r));
+ }
+}
+
+#[cfg(unix)]
+pub fn server_platform_init() {}
diff --git a/third_party/rust/audioipc/src/messages.rs b/third_party/rust/audioipc/src/messages.rs
new file mode 100644
index 0000000000..55a46df631
--- /dev/null
+++ b/third_party/rust/audioipc/src/messages.rs
@@ -0,0 +1,555 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::PlatformHandle;
+use crate::PlatformHandleType;
+#[cfg(target_os = "linux")]
+use audio_thread_priority::RtPriorityThreadInfo;
+use cubeb::{self, ffi};
+use std::ffi::{CStr, CString};
+use std::os::raw::{c_char, c_int, c_uint};
+use std::ptr;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Device {
+ pub output_name: Option<Vec<u8>>,
+ pub input_name: Option<Vec<u8>>,
+}
+
+impl<'a> From<&'a cubeb::DeviceRef> for Device {
+ fn from(info: &'a cubeb::DeviceRef) -> Self {
+ Self {
+ output_name: info.output_name_bytes().map(|s| s.to_vec()),
+ input_name: info.input_name_bytes().map(|s| s.to_vec()),
+ }
+ }
+}
+
+impl From<ffi::cubeb_device> for Device {
+ fn from(info: ffi::cubeb_device) -> Self {
+ Self {
+ output_name: dup_str(info.output_name),
+ input_name: dup_str(info.input_name),
+ }
+ }
+}
+
+impl From<Device> for ffi::cubeb_device {
+ fn from(info: Device) -> Self {
+ Self {
+ output_name: opt_str(info.output_name),
+ input_name: opt_str(info.input_name),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct DeviceInfo {
+ pub devid: usize,
+ pub device_id: Option<Vec<u8>>,
+ pub friendly_name: Option<Vec<u8>>,
+ pub group_id: Option<Vec<u8>>,
+ pub vendor_name: Option<Vec<u8>>,
+
+ pub device_type: ffi::cubeb_device_type,
+ pub state: ffi::cubeb_device_state,
+ pub preferred: ffi::cubeb_device_pref,
+
+ pub format: ffi::cubeb_device_fmt,
+ pub default_format: ffi::cubeb_device_fmt,
+ pub max_channels: u32,
+ pub default_rate: u32,
+ pub max_rate: u32,
+ pub min_rate: u32,
+
+ pub latency_lo: u32,
+ pub latency_hi: u32,
+}
+
+impl<'a> From<&'a cubeb::DeviceInfoRef> for DeviceInfo {
+ fn from(info: &'a cubeb::DeviceInfoRef) -> Self {
+ let info = unsafe { &*info.as_ptr() };
+ DeviceInfo {
+ devid: info.devid as _,
+ device_id: dup_str(info.device_id),
+ friendly_name: dup_str(info.friendly_name),
+ group_id: dup_str(info.group_id),
+ vendor_name: dup_str(info.vendor_name),
+
+ device_type: info.device_type,
+ state: info.state,
+ preferred: info.preferred,
+
+ format: info.format,
+ default_format: info.default_format,
+ max_channels: info.max_channels,
+ default_rate: info.default_rate,
+ max_rate: info.max_rate,
+ min_rate: info.min_rate,
+
+ latency_lo: info.latency_lo,
+ latency_hi: info.latency_hi,
+ }
+ }
+}
+
+impl From<DeviceInfo> for ffi::cubeb_device_info {
+ fn from(info: DeviceInfo) -> Self {
+ ffi::cubeb_device_info {
+ devid: info.devid as _,
+ device_id: opt_str(info.device_id),
+ friendly_name: opt_str(info.friendly_name),
+ group_id: opt_str(info.group_id),
+ vendor_name: opt_str(info.vendor_name),
+
+ device_type: info.device_type,
+ state: info.state,
+ preferred: info.preferred,
+
+ format: info.format,
+ default_format: info.default_format,
+ max_channels: info.max_channels,
+ default_rate: info.default_rate,
+ max_rate: info.max_rate,
+ min_rate: info.min_rate,
+
+ latency_lo: info.latency_lo,
+ latency_hi: info.latency_hi,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+pub struct StreamParams {
+ pub format: ffi::cubeb_sample_format,
+ pub rate: c_uint,
+ pub channels: c_uint,
+ pub layout: ffi::cubeb_channel_layout,
+ pub prefs: ffi::cubeb_stream_prefs,
+}
+
+impl<'a> From<&'a cubeb::StreamParamsRef> for StreamParams {
+ fn from(x: &cubeb::StreamParamsRef) -> StreamParams {
+ unsafe { *(x.as_ptr() as *mut StreamParams) }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StreamCreateParams {
+ pub input_stream_params: Option<StreamParams>,
+ pub output_stream_params: Option<StreamParams>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StreamInitParams {
+ pub stream_name: Option<Vec<u8>>,
+ pub input_device: usize,
+ pub input_stream_params: Option<StreamParams>,
+ pub output_device: usize,
+ pub output_stream_params: Option<StreamParams>,
+ pub latency_frames: u32,
+}
+
+fn dup_str(s: *const c_char) -> Option<Vec<u8>> {
+ if s.is_null() {
+ None
+ } else {
+ let vec: Vec<u8> = unsafe { CStr::from_ptr(s) }.to_bytes().to_vec();
+ Some(vec)
+ }
+}
+
+fn opt_str(v: Option<Vec<u8>>) -> *mut c_char {
+ match v {
+ Some(v) => match CString::new(v) {
+ Ok(s) => s.into_raw(),
+ Err(_) => {
+ debug!("Failed to convert bytes to CString");
+ ptr::null_mut()
+ }
+ },
+ None => ptr::null_mut(),
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StreamCreate {
+ pub token: usize,
+ pub platform_handle: SerializableHandle,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct RegisterDeviceCollectionChanged {
+ pub platform_handle: SerializableHandle,
+}
+
+// Client -> Server messages.
+// TODO: Callbacks should be different messages types so
+// ServerConn::process_msg doesn't have a catch-all case.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum ServerMessage {
+ ClientConnect(u32),
+ ClientDisconnect,
+
+ ContextGetBackendId,
+ ContextGetMaxChannelCount,
+ ContextGetMinLatency(StreamParams),
+ ContextGetPreferredSampleRate,
+ ContextGetDeviceEnumeration(ffi::cubeb_device_type),
+ ContextSetupDeviceCollectionCallback,
+ ContextRegisterDeviceCollectionChanged(ffi::cubeb_device_type, bool),
+
+ StreamCreate(StreamCreateParams),
+ StreamInit(usize, StreamInitParams),
+ StreamDestroy(usize),
+
+ StreamStart(usize),
+ StreamStop(usize),
+ StreamGetPosition(usize),
+ StreamGetLatency(usize),
+ StreamGetInputLatency(usize),
+ StreamSetVolume(usize, f32),
+ StreamSetName(usize, CString),
+ StreamGetCurrentDevice(usize),
+ StreamRegisterDeviceChangeCallback(usize, bool),
+
+ #[cfg(target_os = "linux")]
+ PromoteThreadToRealTime([u8; std::mem::size_of::<RtPriorityThreadInfo>()]),
+}
+
+// Server -> Client messages.
+// TODO: Streams need id.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum ClientMessage {
+ ClientConnected,
+ ClientDisconnected,
+
+ ContextBackendId(String),
+ ContextMaxChannelCount(u32),
+ ContextMinLatency(u32),
+ ContextPreferredSampleRate(u32),
+ ContextEnumeratedDevices(Vec<DeviceInfo>),
+ ContextSetupDeviceCollectionCallback(RegisterDeviceCollectionChanged),
+ ContextRegisteredDeviceCollectionChanged,
+
+ StreamCreated(StreamCreate),
+ StreamInitialized,
+ StreamDestroyed,
+
+ StreamStarted,
+ StreamStopped,
+ StreamPosition(u64),
+ StreamLatency(u32),
+ StreamInputLatency(u32),
+ StreamVolumeSet,
+ StreamNameSet,
+ StreamCurrentDevice(Device),
+ StreamRegisterDeviceChangeCallback,
+
+ #[cfg(target_os = "linux")]
+ ThreadPromoted,
+
+ Error(c_int),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum CallbackReq {
+ Data {
+ nframes: isize,
+ input_frame_size: usize,
+ output_frame_size: usize,
+ },
+ State(ffi::cubeb_state),
+ DeviceChange,
+ SharedMem(SerializableHandle, usize),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum CallbackResp {
+ Data(isize),
+ State,
+ DeviceChange,
+ SharedMem,
+ Error(c_int),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum DeviceCollectionReq {
+ DeviceChange(ffi::cubeb_device_type),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum DeviceCollectionResp {
+ DeviceChange,
+}
+
+// Represents a handle in various transitional states during serialization and remoting.
+// The process of serializing and remoting handles and the ownership during various states differs
+// between Windows and Unix. SerializableHandle changes during IPC is as follows:
+//
+// 1. The initial state `Owned`, with a valid `target_pid`.
+// 2. Ownership is transferred out for processing during IPC send, becoming `Empty` temporarily.
+// See `AssocRawPlatformHandle::take_handle_for_send`.
+// 3. Message containing `SerializableHandleValue` is serialized and sent via IPC.
+// - Windows: DuplicateHandle transfers the handle to the remote process.
+// This produces a new value in the local process representing the remote handle.
+// This value must be sent to the remote, so is recorded as `SerializableValue`.
+// - Unix: Handle value (and ownership) is encoded into cmsg buffer via `cmsg::builder`.
+// The handle is converted to a `SerializableValue` for convenience, but is otherwise unused.
+// 4. Message received and deserialized in target process.
+// - Windows: Deserialization converts the `SerializableValue` into `Owned`, ready for use.
+// - Unix: Handle (with a new value in the target process) is received out-of-band via `recvmsg`
+// and converted to `Owned` via `AssocRawPlatformHandle::set_owned_handle`.
+#[derive(Debug)]
+pub enum SerializableHandle {
+ // Owned handle, with optional target_pid on sending side.
+ Owned(PlatformHandle, Option<u32>),
+ // Transitional IPC states.
+ SerializableValue(PlatformHandleType),
+ Empty,
+}
+
+// PlatformHandle is non-Send and containers a pointer (HANDLE) on Windows.
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for SerializableHandle {}
+
+impl SerializableHandle {
+ pub fn new(handle: PlatformHandle, target_pid: u32) -> SerializableHandle {
+ SerializableHandle::Owned(handle, Some(target_pid))
+ }
+
+ pub fn take_handle(&mut self) -> PlatformHandle {
+ match std::mem::replace(self, SerializableHandle::Empty) {
+ SerializableHandle::Owned(handle, target_pid) => {
+ assert!(target_pid.is_none());
+ handle
+ }
+ _ => panic!("take_handle called in invalid state"),
+ }
+ }
+
+ unsafe fn take_handle_for_send(&mut self) -> (PlatformHandleType, u32) {
+ match std::mem::replace(self, SerializableHandle::Empty) {
+ SerializableHandle::Owned(handle, target_pid) => (
+ handle.into_raw(),
+ target_pid.expect("need valid target_pid"),
+ ),
+ _ => panic!("take_handle_with_target called in invalid state"),
+ }
+ }
+
+ fn new_owned(handle: PlatformHandleType) -> SerializableHandle {
+ SerializableHandle::Owned(PlatformHandle::new(handle), None)
+ }
+
+ fn new_serializable_value(handle: PlatformHandleType) -> SerializableHandle {
+ SerializableHandle::SerializableValue(handle)
+ }
+
+ fn get_serializable_value(&self) -> PlatformHandleType {
+ match *self {
+ SerializableHandle::SerializableValue(handle) => handle,
+ _ => panic!("get_remote_handle called in invalid state"),
+ }
+ }
+}
+
+// Raw handle values are serialized as i64. Additional handling external to (de)serialization is required during IPC
+// send/receive to convert these raw values into valid handles.
+impl serde::Serialize for SerializableHandle {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let handle = self.get_serializable_value();
+ serializer.serialize_i64(handle as i64)
+ }
+}
+
+impl<'de> serde::Deserialize<'de> for SerializableHandle {
+ fn deserialize<D>(deserializer: D) -> Result<SerializableHandle, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ deserializer.deserialize_i64(SerializableHandleVisitor)
+ }
+}
+
+struct SerializableHandleVisitor;
+impl<'de> serde::de::Visitor<'de> for SerializableHandleVisitor {
+ type Value = SerializableHandle;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ formatter.write_str("an integer between -2^63 and 2^63")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ let value = value as PlatformHandleType;
+ Ok(if cfg!(windows) {
+ SerializableHandle::new_owned(value)
+ } else {
+ // On Unix, SerializableHandle becomes owned once `set_owned_handle` is called
+ // with the new local handle value during `recvmsg`.
+ SerializableHandle::new_serializable_value(value)
+ })
+ }
+}
+
+pub trait AssocRawPlatformHandle {
+ // Transfer ownership of handle (if any) to caller.
+ // The caller may then replace the handle using the platform-specific methods below.
+ fn take_handle_for_send(&mut self) -> Option<(PlatformHandleType, u32)> {
+ None
+ }
+
+ // Update the item's handle to reflect the new remote handle value.
+ // Called on the sending side prior to serialization.
+ fn set_remote_handle_value<F>(&mut self, f: F)
+ where
+ F: FnOnce() -> Option<PlatformHandleType>,
+ {
+ assert!(f().is_none());
+ }
+
+ // Update the item's handle with the received value, making it a valid owned handle.
+ // Called on the receiving side after deserialization.
+ #[cfg(unix)]
+ fn set_owned_handle<F>(&mut self, f: F)
+ where
+ F: FnOnce() -> Option<PlatformHandleType>,
+ {
+ assert!(f().is_none());
+ }
+}
+
+impl AssocRawPlatformHandle for ServerMessage {}
+
+impl AssocRawPlatformHandle for ClientMessage {
+ fn take_handle_for_send(&mut self) -> Option<(PlatformHandleType, u32)> {
+ unsafe {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => {
+ Some(data.platform_handle.take_handle_for_send())
+ }
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ Some(data.platform_handle.take_handle_for_send())
+ }
+ _ => None,
+ }
+ }
+ }
+
+ fn set_remote_handle_value<F>(&mut self, f: F)
+ where
+ F: FnOnce() -> Option<PlatformHandleType>,
+ {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => {
+ let handle =
+ f().expect("platform_handle must be available when processing StreamCreated");
+ data.platform_handle = SerializableHandle::new_serializable_value(handle);
+ }
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ let handle = f().expect("platform_handle must be available when processing ContextSetupDeviceCollectionCallback");
+ data.platform_handle = SerializableHandle::new_serializable_value(handle);
+ }
+ _ => {}
+ }
+ }
+
+ #[cfg(unix)]
+ fn set_owned_handle<F>(&mut self, f: F)
+ where
+ F: FnOnce() -> Option<PlatformHandleType>,
+ {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => {
+ let handle =
+ f().expect("platform_handle must be available when processing StreamCreated");
+ data.platform_handle = SerializableHandle::new_owned(handle);
+ }
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ let handle = f().expect("platform_handle must be available when processing ContextSetupDeviceCollectionCallback");
+ data.platform_handle = SerializableHandle::new_owned(handle);
+ }
+ _ => {}
+ }
+ }
+}
+
+impl AssocRawPlatformHandle for DeviceCollectionReq {}
+impl AssocRawPlatformHandle for DeviceCollectionResp {}
+
+impl AssocRawPlatformHandle for CallbackReq {
+ fn take_handle_for_send(&mut self) -> Option<(PlatformHandleType, u32)> {
+ unsafe {
+ if let CallbackReq::SharedMem(ref mut data, _) = *self {
+ Some(data.take_handle_for_send())
+ } else {
+ None
+ }
+ }
+ }
+
+ fn set_remote_handle_value<F>(&mut self, f: F)
+ where
+ F: FnOnce() -> Option<PlatformHandleType>,
+ {
+ if let CallbackReq::SharedMem(ref mut data, _) = *self {
+ let handle = f().expect("platform_handle must be available when processing SharedMem");
+ *data = SerializableHandle::new_serializable_value(handle);
+ }
+ }
+
+ #[cfg(unix)]
+ fn set_owned_handle<F>(&mut self, f: F)
+ where
+ F: FnOnce() -> Option<PlatformHandleType>,
+ {
+ if let CallbackReq::SharedMem(ref mut data, _) = *self {
+ let handle = f().expect("platform_handle must be available when processing SharedMem");
+ *data = SerializableHandle::new_owned(handle);
+ }
+ }
+}
+
+impl AssocRawPlatformHandle for CallbackResp {}
+
+#[cfg(test)]
+mod test {
+ use super::StreamParams;
+ use cubeb::ffi;
+ use std::mem;
+
+ #[test]
+ fn stream_params_size_check() {
+ assert_eq!(
+ mem::size_of::<StreamParams>(),
+ mem::size_of::<ffi::cubeb_stream_params>()
+ )
+ }
+
+ #[test]
+ fn stream_params_from() {
+ let raw = ffi::cubeb_stream_params {
+ format: ffi::CUBEB_SAMPLE_FLOAT32BE,
+ rate: 96_000,
+ channels: 32,
+ layout: ffi::CUBEB_LAYOUT_3F1_LFE,
+ prefs: ffi::CUBEB_STREAM_PREF_LOOPBACK,
+ };
+ let wrapped = ::cubeb::StreamParams::from(raw);
+ let params = StreamParams::from(wrapped.as_ref());
+ assert_eq!(params.format, raw.format);
+ assert_eq!(params.rate, raw.rate);
+ assert_eq!(params.channels, raw.channels);
+ assert_eq!(params.layout, raw.layout);
+ assert_eq!(params.prefs, raw.prefs);
+ }
+}
diff --git a/third_party/rust/audioipc/src/messagestream_unix.rs b/third_party/rust/audioipc/src/messagestream_unix.rs
new file mode 100644
index 0000000000..61c60056a0
--- /dev/null
+++ b/third_party/rust/audioipc/src/messagestream_unix.rs
@@ -0,0 +1,106 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use super::tokio_uds_stream as tokio_uds;
+use futures::Poll;
+use mio::Ready;
+use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use std::os::unix::net;
+use tokio_io::{AsyncRead, AsyncWrite};
+
+#[derive(Debug)]
+pub struct MessageStream(net::UnixStream);
+pub struct AsyncMessageStream(tokio_uds::UnixStream);
+
+impl MessageStream {
+ fn new(stream: net::UnixStream) -> MessageStream {
+ MessageStream(stream)
+ }
+
+ pub fn anonymous_ipc_pair(
+ ) -> std::result::Result<(MessageStream, MessageStream), std::io::Error> {
+ let pair = net::UnixStream::pair()?;
+ Ok((MessageStream::new(pair.0), MessageStream::new(pair.1)))
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn from_raw_handle(raw: super::PlatformHandleType) -> MessageStream {
+ MessageStream::new(net::UnixStream::from_raw_fd(raw))
+ }
+
+ pub fn into_tokio_ipc(
+ self,
+ handle: &tokio::reactor::Handle,
+ ) -> std::result::Result<AsyncMessageStream, std::io::Error> {
+ Ok(AsyncMessageStream::new(tokio_uds::UnixStream::from_std(
+ self.0, handle,
+ )?))
+ }
+}
+
+impl IntoRawFd for MessageStream {
+ fn into_raw_fd(self) -> RawFd {
+ self.0.into_raw_fd()
+ }
+}
+
+impl AsyncMessageStream {
+ fn new(stream: tokio_uds::UnixStream) -> AsyncMessageStream {
+ AsyncMessageStream(stream)
+ }
+
+ pub fn poll_read_ready(&self, ready: Ready) -> Poll<Ready, std::io::Error> {
+ self.0.poll_read_ready(ready)
+ }
+
+ pub fn clear_read_ready(&self, ready: Ready) -> Result<(), std::io::Error> {
+ self.0.clear_read_ready(ready)
+ }
+
+ pub fn poll_write_ready(&self) -> Poll<Ready, std::io::Error> {
+ self.0.poll_write_ready()
+ }
+
+ pub fn clear_write_ready(&self) -> Result<(), std::io::Error> {
+ self.0.clear_write_ready()
+ }
+}
+
+impl std::io::Read for AsyncMessageStream {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ self.0.read(buf)
+ }
+}
+
+impl std::io::Write for AsyncMessageStream {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.0.write(buf)
+ }
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.0.flush()
+ }
+}
+
+impl AsyncRead for AsyncMessageStream {
+ fn read_buf<B: bytes::BufMut>(&mut self, buf: &mut B) -> futures::Poll<usize, std::io::Error> {
+ <&tokio_uds::UnixStream>::read_buf(&mut &self.0, buf)
+ }
+}
+
+impl AsyncWrite for AsyncMessageStream {
+ fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> {
+ <&tokio_uds::UnixStream>::shutdown(&mut &self.0)
+ }
+
+ fn write_buf<B: bytes::Buf>(&mut self, buf: &mut B) -> futures::Poll<usize, std::io::Error> {
+ <&tokio_uds::UnixStream>::write_buf(&mut &self.0, buf)
+ }
+}
+
+impl AsRawFd for AsyncMessageStream {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
diff --git a/third_party/rust/audioipc/src/messagestream_win.rs b/third_party/rust/audioipc/src/messagestream_win.rs
new file mode 100644
index 0000000000..708bbbfb8b
--- /dev/null
+++ b/third_party/rust/audioipc/src/messagestream_win.rs
@@ -0,0 +1,111 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use super::tokio_named_pipes;
+use std::os::windows::fs::*;
+use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use tokio_io::{AsyncRead, AsyncWrite};
+use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
+
+#[derive(Debug)]
+pub struct MessageStream(miow::pipe::NamedPipe);
+pub struct AsyncMessageStream(tokio_named_pipes::NamedPipe);
+
+impl MessageStream {
+ fn new(stream: miow::pipe::NamedPipe) -> MessageStream {
+ MessageStream(stream)
+ }
+
+ pub fn anonymous_ipc_pair(
+ ) -> std::result::Result<(MessageStream, MessageStream), std::io::Error> {
+ let pipe_name = get_pipe_name();
+ let pipe_server = miow::pipe::NamedPipe::new(&pipe_name)?;
+ let pipe_client = {
+ let mut opts = std::fs::OpenOptions::new();
+ opts.read(true)
+ .write(true)
+ .custom_flags(FILE_FLAG_OVERLAPPED);
+ let file = opts.open(&pipe_name)?;
+ unsafe { miow::pipe::NamedPipe::from_raw_handle(file.into_raw_handle()) }
+ };
+ Ok((
+ MessageStream::new(pipe_server),
+ MessageStream::new(pipe_client),
+ ))
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn from_raw_handle(raw: super::PlatformHandleType) -> MessageStream {
+ MessageStream::new(miow::pipe::NamedPipe::from_raw_handle(raw))
+ }
+
+ pub fn into_tokio_ipc(
+ self,
+ handle: &tokio::reactor::Handle,
+ ) -> std::result::Result<AsyncMessageStream, std::io::Error> {
+ let pipe = unsafe { mio_named_pipes::NamedPipe::from_raw_handle(self.into_raw_handle()) };
+ Ok(AsyncMessageStream::new(
+ tokio_named_pipes::NamedPipe::from_pipe(pipe, handle)?,
+ ))
+ }
+}
+
+impl IntoRawHandle for MessageStream {
+ fn into_raw_handle(self) -> RawHandle {
+ self.0.into_raw_handle()
+ }
+}
+
+impl AsyncMessageStream {
+ fn new(stream: tokio_named_pipes::NamedPipe) -> AsyncMessageStream {
+ AsyncMessageStream(stream)
+ }
+}
+
+impl std::io::Read for AsyncMessageStream {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ self.0.read(buf)
+ }
+}
+
+impl std::io::Write for AsyncMessageStream {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.0.write(buf)
+ }
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.0.flush()
+ }
+}
+
+impl AsyncRead for AsyncMessageStream {
+ fn read_buf<B: bytes::BufMut>(&mut self, buf: &mut B) -> futures::Poll<usize, std::io::Error> {
+ <tokio_named_pipes::NamedPipe>::read_buf(&mut self.0, buf)
+ }
+}
+
+impl AsyncWrite for AsyncMessageStream {
+ fn shutdown(&mut self) -> futures::Poll<(), std::io::Error> {
+ <tokio_named_pipes::NamedPipe>::shutdown(&mut self.0)
+ }
+
+ fn write_buf<B: bytes::Buf>(&mut self, buf: &mut B) -> futures::Poll<usize, std::io::Error> {
+ <tokio_named_pipes::NamedPipe>::write_buf(&mut self.0, buf)
+ }
+}
+
+impl AsRawHandle for AsyncMessageStream {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.0.as_raw_handle()
+ }
+}
+
+static PIPE_ID: AtomicUsize = AtomicUsize::new(0);
+
+fn get_pipe_name() -> String {
+ let pid = std::process::id();
+ let pipe_id = PIPE_ID.fetch_add(1, Ordering::SeqCst);
+ format!("\\\\.\\pipe\\cubeb-pipe-{}-{}", pid, pipe_id)
+}
diff --git a/third_party/rust/audioipc/src/msg.rs b/third_party/rust/audioipc/src/msg.rs
new file mode 100644
index 0000000000..e50734d3e4
--- /dev/null
+++ b/third_party/rust/audioipc/src/msg.rs
@@ -0,0 +1,115 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use iovec::unix;
+use iovec::IoVec;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::{cmp, io, mem, ptr};
+
+// Extend sys::os::unix::net::UnixStream to support sending and receiving a single file desc.
+// We can extend UnixStream by using traits, eliminating the need to introduce a new wrapped
+// UnixStream type.
+pub trait RecvMsg {
+ fn recv_msg(
+ &mut self,
+ iov: &mut [&mut IoVec],
+ cmsg: &mut [u8],
+ ) -> io::Result<(usize, usize, i32)>;
+}
+
+pub trait SendMsg {
+ fn send_msg(&mut self, iov: &[&IoVec], cmsg: &[u8]) -> io::Result<usize>;
+}
+
+impl<T: AsRawFd> RecvMsg for T {
+ fn recv_msg(
+ &mut self,
+ iov: &mut [&mut IoVec],
+ cmsg: &mut [u8],
+ ) -> io::Result<(usize, usize, i32)> {
+ #[cfg(target_os = "linux")]
+ let flags = libc::MSG_CMSG_CLOEXEC;
+ #[cfg(not(target_os = "linux"))]
+ let flags = 0;
+ recv_msg_with_flags(self.as_raw_fd(), iov, cmsg, flags)
+ }
+}
+
+impl<T: AsRawFd> SendMsg for T {
+ fn send_msg(&mut self, iov: &[&IoVec], cmsg: &[u8]) -> io::Result<usize> {
+ send_msg_with_flags(self.as_raw_fd(), iov, cmsg, 0)
+ }
+}
+
+fn cvt(r: libc::ssize_t) -> io::Result<usize> {
+ if r == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(r as usize)
+ }
+}
+
+// Convert return of -1 into error message, handling retry on EINTR
+fn cvt_r<F: FnMut() -> libc::ssize_t>(mut f: F) -> io::Result<usize> {
+ loop {
+ match cvt(f()) {
+ Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
+ other => return other,
+ }
+ }
+}
+
+pub fn recv_msg_with_flags(
+ socket: RawFd,
+ bufs: &mut [&mut IoVec],
+ cmsg: &mut [u8],
+ flags: libc::c_int,
+) -> io::Result<(usize, usize, libc::c_int)> {
+ let slice = unix::as_os_slice_mut(bufs);
+ let len = cmp::min(<libc::c_int>::max_value() as usize, slice.len());
+ let (control, controllen) = if cmsg.is_empty() {
+ (ptr::null_mut(), 0)
+ } else {
+ (cmsg.as_ptr() as *mut _, cmsg.len())
+ };
+
+ let mut msghdr: libc::msghdr = unsafe { mem::zeroed() };
+ msghdr.msg_name = ptr::null_mut();
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = slice.as_mut_ptr();
+ msghdr.msg_iovlen = len as _;
+ msghdr.msg_control = control;
+ msghdr.msg_controllen = controllen as _;
+
+ let n = cvt_r(|| unsafe { libc::recvmsg(socket, &mut msghdr as *mut _, flags) })?;
+
+ let controllen = msghdr.msg_controllen as usize;
+ Ok((n, controllen, msghdr.msg_flags))
+}
+
+pub fn send_msg_with_flags(
+ socket: RawFd,
+ bufs: &[&IoVec],
+ cmsg: &[u8],
+ flags: libc::c_int,
+) -> io::Result<usize> {
+ let slice = unix::as_os_slice(bufs);
+ let len = cmp::min(<libc::c_int>::max_value() as usize, slice.len());
+ let (control, controllen) = if cmsg.is_empty() {
+ (ptr::null_mut(), 0)
+ } else {
+ (cmsg.as_ptr() as *mut _, cmsg.len())
+ };
+
+ let mut msghdr: libc::msghdr = unsafe { mem::zeroed() };
+ msghdr.msg_name = ptr::null_mut();
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = slice.as_ptr() as *mut _;
+ msghdr.msg_iovlen = len as _;
+ msghdr.msg_control = control;
+ msghdr.msg_controllen = controllen as _;
+
+ cvt_r(|| unsafe { libc::sendmsg(socket, &msghdr as *const _, flags) })
+}
diff --git a/third_party/rust/audioipc/src/rpc/client/mod.rs b/third_party/rust/audioipc/src/rpc/client/mod.rs
new file mode 100644
index 0000000000..0fb8aed565
--- /dev/null
+++ b/third_party/rust/audioipc/src/rpc/client/mod.rs
@@ -0,0 +1,162 @@
+// This is a derived version of simple/pipeline/client.rs from
+// tokio_proto crate used under MIT license.
+//
+// Original version of client.rs:
+// https://github.com/tokio-rs/tokio-proto/commit/8fb8e482dcd55cf02ceee165f8e08eee799c96d3
+//
+// The following modifications were made:
+// * Simplify the code to implement RPC for pipeline requests that
+// contain simple request/response messages:
+// * Remove `Error` types,
+// * Remove `bind_transport` fn & `BindTransport` type,
+// * Remove all "Lift"ing functionality.
+// * Remove `Service` trait since audioipc doesn't use `tokio_service`
+// crate.
+//
+// Copyright (c) 2016 Tokio contributors
+//
+// 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.
+
+use crate::rpc::driver::Driver;
+use crate::rpc::Handler;
+use futures::sync::oneshot;
+use futures::{Async, Future, Poll, Sink, Stream};
+use std::collections::VecDeque;
+use std::io;
+use tokio::runtime::current_thread;
+
+mod proxy;
+
+pub use self::proxy::{ClientProxy, Response};
+
+pub fn bind_client<C>(transport: C::Transport) -> proxy::ClientProxy<C::Request, C::Response>
+where
+ C: Client,
+{
+ let (tx, rx) = proxy::channel();
+
+ let fut = {
+ let handler = ClientHandler::<C> {
+ transport,
+ requests: rx,
+ in_flight: VecDeque::with_capacity(32),
+ };
+ Driver::new(handler)
+ };
+
+ // Spawn the RPC driver into task
+ current_thread::spawn(fut.map_err(|_| ()));
+
+ tx
+}
+
+pub trait Client: 'static {
+ /// Request
+ type Request: 'static;
+
+ /// Response
+ type Response: 'static;
+
+ /// The message transport, which works with async I/O objects of type `A`
+ type Transport: 'static
+ + Stream<Item = Self::Response, Error = io::Error>
+ + Sink<SinkItem = Self::Request, SinkError = io::Error>;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ClientHandler<C>
+where
+ C: Client,
+{
+ transport: C::Transport,
+ requests: proxy::Receiver<C::Request, C::Response>,
+ in_flight: VecDeque<oneshot::Sender<C::Response>>,
+}
+
+impl<C> Handler for ClientHandler<C>
+where
+ C: Client,
+{
+ type In = C::Response;
+ type Out = C::Request;
+ type Transport = C::Transport;
+
+ fn transport(&mut self) -> &mut Self::Transport {
+ &mut self.transport
+ }
+
+ fn consume(&mut self, response: Self::In) -> io::Result<()> {
+ trace!("ClientHandler::consume");
+ if let Some(complete) = self.in_flight.pop_front() {
+ drop(complete.send(response));
+ } else {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "request / response mismatch",
+ ));
+ }
+
+ Ok(())
+ }
+
+ /// Produce a message
+ fn produce(&mut self) -> Poll<Option<Self::Out>, io::Error> {
+ trace!("ClientHandler::produce");
+
+ // Try to get a new request
+ match self.requests.poll() {
+ Ok(Async::Ready(Some((request, complete)))) => {
+ trace!(" --> received request");
+
+ // Track complete handle
+ self.in_flight.push_back(complete);
+
+ Ok(Some(request).into())
+ }
+ Ok(Async::Ready(None)) => {
+ trace!(" --> client dropped");
+ Ok(None.into())
+ }
+ Ok(Async::NotReady) => {
+ trace!(" --> not ready");
+ Ok(Async::NotReady)
+ }
+ Err(_) => unreachable!(),
+ }
+ }
+
+ /// RPC currently in flight
+ fn has_in_flight(&self) -> bool {
+ !self.in_flight.is_empty()
+ }
+}
+
+impl<C: Client> Drop for ClientHandler<C> {
+ fn drop(&mut self) {
+ let _ = self.transport.close();
+ self.in_flight.clear();
+ }
+}
diff --git a/third_party/rust/audioipc/src/rpc/client/proxy.rs b/third_party/rust/audioipc/src/rpc/client/proxy.rs
new file mode 100644
index 0000000000..bd44110d59
--- /dev/null
+++ b/third_party/rust/audioipc/src/rpc/client/proxy.rs
@@ -0,0 +1,136 @@
+// This is a derived version of client_proxy.rs from
+// tokio_proto crate used under MIT license.
+//
+// Original version of client_proxy.rs:
+// https://github.com/tokio-rs/tokio-proto/commit/8fb8e482dcd55cf02ceee165f8e08eee799c96d3
+//
+// The following modifications were made:
+// * Remove `Service` trait since audioipc doesn't use `tokio_service`
+// crate.
+// * Remove `RefCell` from `ClientProxy` since cubeb is called from
+// multiple threads. `mpsc::UnboundedSender` is thread safe.
+// * Simplify the interface to just request (`R`) and response (`Q`)
+// removing error (`E`).
+// * Remove the `Envelope` type.
+// * Renamed `pair` to `channel` to represent that an `rpc::channel`
+// is being created.
+//
+// Original License:
+//
+// Copyright (c) 2016 Tokio contributors
+//
+// 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.
+
+use futures::sync::{mpsc, oneshot};
+use futures::{Async, Future, Poll};
+use std::fmt;
+use std::io;
+
+/// Message used to dispatch requests to the task managing the
+/// client connection.
+pub type Request<R, Q> = (R, oneshot::Sender<Q>);
+
+/// Receive requests submitted to the client
+pub type Receiver<R, Q> = mpsc::UnboundedReceiver<Request<R, Q>>;
+
+/// Response future returned from a client
+pub struct Response<Q> {
+ inner: oneshot::Receiver<Q>,
+}
+
+pub struct ClientProxy<R, Q> {
+ tx: mpsc::UnboundedSender<Request<R, Q>>,
+}
+
+impl<R, Q> Clone for ClientProxy<R, Q> {
+ fn clone(&self) -> Self {
+ ClientProxy {
+ tx: self.tx.clone(),
+ }
+ }
+}
+
+pub fn channel<R, Q>() -> (ClientProxy<R, Q>, Receiver<R, Q>) {
+ // Create a channel to send the requests to client-side of rpc.
+ let (tx, rx) = mpsc::unbounded();
+
+ // Wrap the `tx` part in ClientProxy so the rpc call interface
+ // can be implemented.
+ let client = ClientProxy { tx };
+
+ (client, rx)
+}
+
+impl<R, Q> ClientProxy<R, Q> {
+ pub fn call(&self, request: R) -> Response<Q> {
+ // The response to returned from the rpc client task over a
+ // oneshot channel.
+ let (tx, rx) = oneshot::channel();
+
+ // If send returns an Err, its because the other side has been dropped.
+ // By ignoring it, we are just dropping the `tx`, which will mean the
+ // rx will return Canceled when polled. In turn, that is translated
+ // into a BrokenPipe, which conveys the proper error.
+ let _ = self.tx.unbounded_send((request, tx));
+
+ Response { inner: rx }
+ }
+}
+
+impl<R, Q> fmt::Debug for ClientProxy<R, Q>
+where
+ R: fmt::Debug,
+ Q: fmt::Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "ClientProxy {{ ... }}")
+ }
+}
+
+impl<Q> Future for Response<Q> {
+ type Item = Q;
+ type Error = io::Error;
+
+ fn poll(&mut self) -> Poll<Q, io::Error> {
+ match self.inner.poll() {
+ Ok(Async::Ready(res)) => Ok(Async::Ready(res)),
+ Ok(Async::NotReady) => Ok(Async::NotReady),
+ // Convert oneshot::Canceled into io::Error
+ Err(_) => {
+ let e = io::Error::new(io::ErrorKind::BrokenPipe, "broken pipe");
+ Err(e)
+ }
+ }
+ }
+}
+
+impl<Q> fmt::Debug for Response<Q>
+where
+ Q: fmt::Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Response {{ ... }}")
+ }
+}
diff --git a/third_party/rust/audioipc/src/rpc/driver.rs b/third_party/rust/audioipc/src/rpc/driver.rs
new file mode 100644
index 0000000000..0890fd138e
--- /dev/null
+++ b/third_party/rust/audioipc/src/rpc/driver.rs
@@ -0,0 +1,171 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::rpc::Handler;
+use futures::{Async, AsyncSink, Future, Poll, Sink, Stream};
+use std::fmt;
+use std::io;
+
+pub struct Driver<T>
+where
+ T: Handler,
+{
+ // Glue
+ handler: T,
+
+ // True as long as the connection has more request frames to read.
+ run: bool,
+
+ // True when the transport is fully flushed
+ is_flushed: bool,
+}
+
+impl<T> Driver<T>
+where
+ T: Handler,
+{
+ /// Create a new rpc driver with the given service and transport.
+ pub fn new(handler: T) -> Driver<T> {
+ Driver {
+ handler,
+ run: true,
+ is_flushed: true,
+ }
+ }
+
+ /// Returns true if the driver has nothing left to do
+ fn is_done(&self) -> bool {
+ !self.run && self.is_flushed && !self.has_in_flight()
+ }
+
+ /// Process incoming messages off the transport.
+ fn receive_incoming(&mut self) -> io::Result<()> {
+ while self.run {
+ if let Async::Ready(req) = self.handler.transport().poll()? {
+ self.process_incoming(req);
+ } else {
+ break;
+ }
+ }
+ Ok(())
+ }
+
+ /// Process an incoming message
+ fn process_incoming(&mut self, req: Option<T::In>) {
+ trace!("process_incoming");
+ // At this point, the service & transport are ready to process the
+ // request, no matter what it is.
+ match req {
+ Some(message) => {
+ trace!("received message");
+
+ if let Err(e) = self.handler.consume(message) {
+ // TODO: Should handler be infalliable?
+ panic!("unimplemented error handling: {:?}", e);
+ }
+ }
+ None => {
+ trace!("received None");
+ // At this point, we just return. This works
+ // because poll with be called again and go
+ // through the receive-cycle again.
+ self.run = false;
+ }
+ }
+ }
+
+ /// Send outgoing messages to the transport.
+ fn send_outgoing(&mut self) -> io::Result<()> {
+ trace!("send_responses");
+ loop {
+ match self.handler.produce()? {
+ Async::Ready(Some(message)) => {
+ trace!(" --> got message");
+ self.process_outgoing(message)?;
+ }
+ Async::Ready(None) => {
+ trace!(" --> got None");
+ // The service is done with the connection.
+ self.run = false;
+ break;
+ }
+ // Nothing to dispatch
+ Async::NotReady => break,
+ }
+ }
+
+ Ok(())
+ }
+
+ fn process_outgoing(&mut self, message: T::Out) -> io::Result<()> {
+ trace!("process_outgoing");
+ assert_send(&mut self.handler.transport(), message)?;
+
+ Ok(())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.is_flushed = self.handler.transport().poll_complete()?.is_ready();
+
+ // TODO:
+ Ok(())
+ }
+
+ fn has_in_flight(&self) -> bool {
+ self.handler.has_in_flight()
+ }
+}
+
+impl<T> Future for Driver<T>
+where
+ T: Handler,
+{
+ type Item = ();
+ type Error = io::Error;
+
+ fn poll(&mut self) -> Poll<(), Self::Error> {
+ trace!("rpc::Driver::tick");
+
+ // First read off data from the socket
+ self.receive_incoming()?;
+
+ // Handle completed responses
+ self.send_outgoing()?;
+
+ // Try flushing buffered writes
+ self.flush()?;
+
+ if self.is_done() {
+ trace!(" --> is done.");
+ return Ok(().into());
+ }
+
+ // Tick again later
+ Ok(Async::NotReady)
+ }
+}
+
+fn assert_send<S: Sink>(s: &mut S, item: S::SinkItem) -> Result<(), S::SinkError> {
+ match s.start_send(item)? {
+ AsyncSink::Ready => Ok(()),
+ AsyncSink::NotReady(_) => panic!(
+ "sink reported itself as ready after `poll_ready` but was \
+ then unable to accept a message"
+ ),
+ }
+}
+
+impl<T> fmt::Debug for Driver<T>
+where
+ T: Handler + fmt::Debug,
+{
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("rpc::Handler")
+ .field("handler", &self.handler)
+ .field("run", &self.run)
+ .field("is_flushed", &self.is_flushed)
+ .finish()
+ }
+}
diff --git a/third_party/rust/audioipc/src/rpc/mod.rs b/third_party/rust/audioipc/src/rpc/mod.rs
new file mode 100644
index 0000000000..8d54fc6e00
--- /dev/null
+++ b/third_party/rust/audioipc/src/rpc/mod.rs
@@ -0,0 +1,36 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use futures::{Poll, Sink, Stream};
+use std::io;
+
+mod client;
+mod driver;
+mod server;
+
+pub use self::client::{bind_client, Client, ClientProxy, Response};
+pub use self::server::{bind_server, Server};
+
+pub trait Handler {
+ /// Message type read from transport
+ type In;
+ /// Message type written to transport
+ type Out;
+ type Transport: 'static
+ + Stream<Item = Self::In, Error = io::Error>
+ + Sink<SinkItem = Self::Out, SinkError = io::Error>;
+
+ /// Mutable reference to the transport
+ fn transport(&mut self) -> &mut Self::Transport;
+
+ /// Consume a request
+ fn consume(&mut self, message: Self::In) -> io::Result<()>;
+
+ /// Produce a response
+ fn produce(&mut self) -> Poll<Option<Self::Out>, io::Error>;
+
+ /// RPC currently in flight
+ fn has_in_flight(&self) -> bool;
+}
diff --git a/third_party/rust/audioipc/src/rpc/server.rs b/third_party/rust/audioipc/src/rpc/server.rs
new file mode 100644
index 0000000000..1cb037bd8a
--- /dev/null
+++ b/third_party/rust/audioipc/src/rpc/server.rs
@@ -0,0 +1,184 @@
+// This is a derived version of simple/pipeline/server.rs from
+// tokio_proto crate used under MIT license.
+//
+// Original version of server.rs:
+// https://github.com/tokio-rs/tokio-proto/commit/8fb8e482dcd55cf02ceee165f8e08eee799c96d3
+//
+// The following modifications were made:
+// * Simplify the code to implement RPC for pipeline requests that
+// contain simple request/response messages:
+// * Remove `Error` types,
+// * Remove `bind_transport` fn & `BindTransport` type,
+// * Remove all "Lift"ing functionality.
+// * Remove `Service` trait since audioipc doesn't use `tokio_service`
+// crate.
+//
+// Copyright (c) 2016 Tokio contributors
+//
+// 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.
+
+use crate::rpc::driver::Driver;
+use crate::rpc::Handler;
+use futures::{Async, Future, Poll, Sink, Stream};
+use std::collections::VecDeque;
+use std::io;
+use tokio::runtime::current_thread;
+
+/// Bind an async I/O object `io` to the `server`.
+pub fn bind_server<S>(transport: S::Transport, server: S)
+where
+ S: Server,
+{
+ let fut = {
+ let handler = ServerHandler {
+ server,
+ transport,
+ in_flight: VecDeque::with_capacity(32),
+ };
+ Driver::new(handler)
+ };
+
+ // Spawn the RPC driver into task
+ current_thread::spawn(fut.map_err(|_| ()))
+}
+
+pub trait Server: 'static {
+ /// Request
+ type Request: 'static;
+
+ /// Response
+ type Response: 'static;
+
+ /// Future
+ type Future: Future<Item = Self::Response, Error = ()>;
+
+ /// The message transport, which works with async I/O objects of
+ /// type `A`.
+ type Transport: 'static
+ + Stream<Item = Self::Request, Error = io::Error>
+ + Sink<SinkItem = Self::Response, SinkError = io::Error>;
+
+ /// Process the request and return the response asynchronously.
+ fn process(&mut self, req: Self::Request) -> Self::Future;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+struct ServerHandler<S>
+where
+ S: Server,
+{
+ // The service handling the connection
+ server: S,
+ // The transport responsible for sending/receving messages over the wire
+ transport: S::Transport,
+ // FIFO of "in flight" responses to requests.
+ in_flight: VecDeque<InFlight<S::Future>>,
+}
+
+impl<S> Handler for ServerHandler<S>
+where
+ S: Server,
+{
+ type In = S::Request;
+ type Out = S::Response;
+ type Transport = S::Transport;
+
+ /// Mutable reference to the transport
+ fn transport(&mut self) -> &mut Self::Transport {
+ &mut self.transport
+ }
+
+ /// Consume a message
+ fn consume(&mut self, request: Self::In) -> io::Result<()> {
+ trace!("ServerHandler::consume");
+ let response = self.server.process(request);
+ self.in_flight.push_back(InFlight::Active(response));
+
+ // TODO: Should the error be handled differently?
+ Ok(())
+ }
+
+ /// Produce a message
+ fn produce(&mut self) -> Poll<Option<Self::Out>, io::Error> {
+ trace!("ServerHandler::produce");
+
+ // Make progress on pending responses
+ for pending in &mut self.in_flight {
+ pending.poll();
+ }
+
+ // Is the head of the queue ready?
+ match self.in_flight.front() {
+ Some(&InFlight::Done(_)) => {}
+ _ => {
+ trace!(" --> not ready");
+ return Ok(Async::NotReady);
+ }
+ }
+
+ // Return the ready response
+ match self.in_flight.pop_front() {
+ Some(InFlight::Done(res)) => {
+ trace!(" --> received response");
+ Ok(Async::Ready(Some(res)))
+ }
+ _ => panic!(),
+ }
+ }
+
+ /// RPC currently in flight
+ fn has_in_flight(&self) -> bool {
+ !self.in_flight.is_empty()
+ }
+}
+
+impl<S: Server> Drop for ServerHandler<S> {
+ fn drop(&mut self) {
+ let _ = self.transport.close();
+ self.in_flight.clear();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+enum InFlight<F: Future<Error = ()>> {
+ Active(F),
+ Done(F::Item),
+}
+
+impl<F: Future<Error = ()>> InFlight<F> {
+ fn poll(&mut self) {
+ let res = match *self {
+ InFlight::Active(ref mut f) => match f.poll() {
+ Ok(Async::Ready(e)) => e,
+ Err(_) => unreachable!(),
+ Ok(Async::NotReady) => return,
+ },
+ _ => return,
+ };
+ *self = InFlight::Done(res);
+ }
+}
diff --git a/third_party/rust/audioipc/src/shm.rs b/third_party/rust/audioipc/src/shm.rs
new file mode 100644
index 0000000000..ddcd14afe3
--- /dev/null
+++ b/third_party/rust/audioipc/src/shm.rs
@@ -0,0 +1,334 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+#![allow(clippy::missing_safety_doc)]
+
+use crate::errors::*;
+use crate::PlatformHandle;
+use std::{convert::TryInto, ffi::c_void, slice};
+
+#[cfg(unix)]
+pub use unix::SharedMem;
+#[cfg(windows)]
+pub use windows::SharedMem;
+
+#[derive(Copy, Clone)]
+pub struct SharedMemView {
+ ptr: *mut c_void,
+ size: usize,
+}
+
+unsafe impl Send for SharedMemView {}
+
+impl SharedMemView {
+ pub unsafe fn get_slice(&self, size: usize) -> Result<&[u8]> {
+ let map = slice::from_raw_parts(self.ptr as _, self.size);
+ if size <= self.size {
+ Ok(&map[..size])
+ } else {
+ bail!("mmap size");
+ }
+ }
+
+ pub unsafe fn get_mut_slice(&mut self, size: usize) -> Result<&mut [u8]> {
+ let map = slice::from_raw_parts_mut(self.ptr as _, self.size);
+ if size <= self.size {
+ Ok(&mut map[..size])
+ } else {
+ bail!("mmap size")
+ }
+ }
+}
+
+#[cfg(unix)]
+mod unix {
+ use super::*;
+ use memmap2::{MmapMut, MmapOptions};
+ use std::fs::File;
+ use std::os::unix::io::{AsRawFd, FromRawFd};
+
+ #[cfg(target_os = "android")]
+ fn open_shm_file(_id: &str, size: usize) -> Result<File> {
+ unsafe {
+ let fd = ashmem::ASharedMemory_create(std::ptr::null(), size);
+ if fd >= 0 {
+ // Drop PROT_EXEC
+ let r = ashmem::ASharedMemory_setProt(fd, libc::PROT_READ | libc::PROT_WRITE);
+ assert_eq!(r, 0);
+ return Ok(File::from_raw_fd(fd.try_into().unwrap()));
+ }
+ Err(std::io::Error::last_os_error().into())
+ }
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn open_shm_file(id: &str, size: usize) -> Result<File> {
+ let file = open_shm_file_impl(id)?;
+ allocate_file(&file, size)?;
+ Ok(file)
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn open_shm_file_impl(id: &str) -> Result<File> {
+ use std::env::temp_dir;
+ use std::fs::{remove_file, OpenOptions};
+
+ let id_cstring = std::ffi::CString::new(id).unwrap();
+
+ #[cfg(target_os = "linux")]
+ {
+ unsafe {
+ let r = libc::syscall(libc::SYS_memfd_create, id_cstring.as_ptr(), 0);
+ if r >= 0 {
+ return Ok(File::from_raw_fd(r.try_into().unwrap()));
+ }
+ }
+
+ let mut path = std::path::PathBuf::from("/dev/shm");
+ path.push(id);
+
+ if let Ok(file) = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create_new(true)
+ .open(&path)
+ {
+ let _ = remove_file(&path);
+ return Ok(file);
+ }
+ }
+
+ unsafe {
+ let fd = libc::shm_open(
+ id_cstring.as_ptr(),
+ libc::O_RDWR | libc::O_CREAT | libc::O_EXCL,
+ 0o600,
+ );
+ if fd >= 0 {
+ libc::shm_unlink(id_cstring.as_ptr());
+ return Ok(File::from_raw_fd(fd));
+ }
+ }
+
+ let mut path = temp_dir();
+ path.push(id);
+
+ let file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create_new(true)
+ .open(&path)?;
+
+ let _ = remove_file(&path);
+ Ok(file)
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn handle_enospc(s: &str) -> Result<()> {
+ let err = std::io::Error::last_os_error();
+ let errno = err.raw_os_error().unwrap_or(0);
+ assert_ne!(errno, 0);
+ debug!("allocate_file: {} failed errno={}", s, errno);
+ if errno == libc::ENOSPC {
+ return Err(err.into());
+ }
+ Ok(())
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn allocate_file(file: &File, size: usize) -> Result<()> {
+ // First, set the file size. This may create a sparse file on
+ // many systems, which can fail with SIGBUS when accessed via a
+ // mapping and the lazy backing allocation fails due to low disk
+ // space. To avoid this, try to force the entire file to be
+ // preallocated before mapping using OS-specific approaches below.
+
+ file.set_len(size.try_into().unwrap())?;
+
+ let fd = file.as_raw_fd();
+ let size: libc::off_t = size.try_into().unwrap();
+
+ // Try Linux-specific fallocate.
+ #[cfg(target_os = "linux")]
+ {
+ if unsafe { libc::fallocate(fd, 0, 0, size) } == 0 {
+ return Ok(());
+ }
+ handle_enospc("fallocate()")?;
+ }
+
+ // Try macOS-specific fcntl.
+ #[cfg(target_os = "macos")]
+ {
+ let params = libc::fstore_t {
+ fst_flags: libc::F_ALLOCATEALL,
+ fst_posmode: libc::F_PEOFPOSMODE,
+ fst_offset: 0,
+ fst_length: size,
+ fst_bytesalloc: 0,
+ };
+ if unsafe { libc::fcntl(fd, libc::F_PREALLOCATE, &params) } == 0 {
+ return Ok(());
+ }
+ handle_enospc("fcntl(F_PREALLOCATE)")?;
+ }
+
+ // Fall back to portable version, where available.
+ #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly"))]
+ {
+ if unsafe { libc::posix_fallocate(fd, 0, size) } == 0 {
+ return Ok(());
+ }
+ handle_enospc("posix_fallocate()")?;
+ }
+
+ Ok(())
+ }
+
+ pub struct SharedMem {
+ file: File,
+ _mmap: MmapMut,
+ view: SharedMemView,
+ }
+
+ impl SharedMem {
+ pub fn new(id: &str, size: usize) -> Result<SharedMem> {
+ let file = open_shm_file(id, size)?;
+ let mut mmap = unsafe { MmapOptions::new().map_mut(&file)? };
+ assert_eq!(mmap.len(), size);
+ let view = SharedMemView {
+ ptr: mmap.as_mut_ptr() as _,
+ size,
+ };
+ Ok(SharedMem {
+ file,
+ _mmap: mmap,
+ view,
+ })
+ }
+
+ pub unsafe fn make_handle(&self) -> Result<PlatformHandle> {
+ PlatformHandle::duplicate(self.file.as_raw_fd()).map_err(|e| e.into())
+ }
+
+ pub unsafe fn from(handle: PlatformHandle, size: usize) -> Result<SharedMem> {
+ let file = File::from_raw_fd(handle.into_raw());
+ let mut mmap = MmapOptions::new().map_mut(&file)?;
+ assert_eq!(mmap.len(), size);
+ let view = SharedMemView {
+ ptr: mmap.as_mut_ptr() as _,
+ size,
+ };
+ Ok(SharedMem {
+ file,
+ _mmap: mmap,
+ view,
+ })
+ }
+
+ pub unsafe fn unsafe_view(&self) -> SharedMemView {
+ self.view
+ }
+
+ pub unsafe fn get_slice(&self, size: usize) -> Result<&[u8]> {
+ self.view.get_slice(size)
+ }
+
+ pub unsafe fn get_mut_slice(&mut self, size: usize) -> Result<&mut [u8]> {
+ self.view.get_mut_slice(size)
+ }
+ }
+}
+
+#[cfg(windows)]
+mod windows {
+ use super::*;
+ use std::ptr;
+ use winapi::{
+ shared::{minwindef::DWORD, ntdef::HANDLE},
+ um::{
+ handleapi::CloseHandle,
+ memoryapi::{MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS},
+ winbase::CreateFileMappingA,
+ winnt::PAGE_READWRITE,
+ },
+ };
+
+ use crate::INVALID_HANDLE_VALUE;
+
+ pub struct SharedMem {
+ handle: HANDLE,
+ view: SharedMemView,
+ }
+
+ unsafe impl Send for SharedMem {}
+
+ impl Drop for SharedMem {
+ fn drop(&mut self) {
+ unsafe {
+ let ok = UnmapViewOfFile(self.view.ptr);
+ assert_ne!(ok, 0);
+ let ok = CloseHandle(self.handle);
+ assert_ne!(ok, 0);
+ }
+ }
+ }
+
+ impl SharedMem {
+ pub fn new(_id: &str, size: usize) -> Result<SharedMem> {
+ unsafe {
+ let handle = CreateFileMappingA(
+ INVALID_HANDLE_VALUE,
+ ptr::null_mut(),
+ PAGE_READWRITE,
+ (size as u64 >> 32).try_into().unwrap(),
+ (size as u64 & (DWORD::MAX as u64)).try_into().unwrap(),
+ ptr::null(),
+ );
+ if handle.is_null() {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ let ptr = MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
+ if ptr.is_null() {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ Ok(SharedMem {
+ handle,
+ view: SharedMemView { ptr, size },
+ })
+ }
+ }
+
+ pub unsafe fn make_handle(&self) -> Result<PlatformHandle> {
+ PlatformHandle::duplicate(self.handle).map_err(|e| e.into())
+ }
+
+ pub unsafe fn from(handle: PlatformHandle, size: usize) -> Result<SharedMem> {
+ let handle = handle.into_raw();
+ let ptr = MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
+ if ptr.is_null() {
+ return Err(std::io::Error::last_os_error().into());
+ }
+ Ok(SharedMem {
+ handle,
+ view: SharedMemView { ptr, size },
+ })
+ }
+
+ pub unsafe fn unsafe_view(&self) -> SharedMemView {
+ self.view
+ }
+
+ pub unsafe fn get_slice(&self, size: usize) -> Result<&[u8]> {
+ self.view.get_slice(size)
+ }
+
+ pub unsafe fn get_mut_slice(&mut self, size: usize) -> Result<&mut [u8]> {
+ self.view.get_mut_slice(size)
+ }
+ }
+}
diff --git a/third_party/rust/audioipc/src/tokio_named_pipes.rs b/third_party/rust/audioipc/src/tokio_named_pipes.rs
new file mode 100644
index 0000000000..ed4343d759
--- /dev/null
+++ b/third_party/rust/audioipc/src/tokio_named_pipes.rs
@@ -0,0 +1,155 @@
+// Copied from tokio-named-pipes/src/lib.rs revision 49ec1ba8bbc94ab6fc9636af2a00dfb3204080c8 (tokio-named-pipes 0.2.0)
+// This file is dual licensed under the MIT and Apache-2.0 per upstream: https://github.com/NikVolf/tokio-named-pipes/blob/stable/LICENSE-MIT and https://github.com/NikVolf/tokio-named-pipes/blob/stable/LICENSE-APACHE
+// - Implement AsyncWrite::shutdown
+
+#![cfg(windows)]
+
+use std::ffi::OsStr;
+use std::fmt;
+use std::io::{Read, Write};
+use std::os::windows::io::*;
+
+use bytes::{Buf, BufMut};
+use futures::{Async, Poll};
+use mio::Ready;
+use tokio::io::{AsyncRead, AsyncWrite};
+use tokio::reactor::{Handle, PollEvented2};
+
+pub struct NamedPipe {
+ io: PollEvented2<mio_named_pipes::NamedPipe>,
+}
+
+impl NamedPipe {
+ pub fn new<P: AsRef<OsStr>>(p: P, handle: &Handle) -> std::io::Result<NamedPipe> {
+ NamedPipe::_new(p.as_ref(), handle)
+ }
+
+ fn _new(p: &OsStr, handle: &Handle) -> std::io::Result<NamedPipe> {
+ let inner = mio_named_pipes::NamedPipe::new(p)?;
+ NamedPipe::from_pipe(inner, handle)
+ }
+
+ pub fn from_pipe(
+ pipe: mio_named_pipes::NamedPipe,
+ handle: &Handle,
+ ) -> std::io::Result<NamedPipe> {
+ Ok(NamedPipe {
+ io: PollEvented2::new_with_handle(pipe, handle)?,
+ })
+ }
+
+ pub fn connect(&self) -> std::io::Result<()> {
+ self.io.get_ref().connect()
+ }
+
+ pub fn disconnect(&self) -> std::io::Result<()> {
+ self.io.get_ref().disconnect()
+ }
+
+ pub fn poll_read_ready_readable(&mut self) -> tokio::io::Result<Async<Ready>> {
+ self.io.poll_read_ready(Ready::readable())
+ }
+
+ pub fn poll_write_ready(&mut self) -> tokio::io::Result<Async<Ready>> {
+ self.io.poll_write_ready()
+ }
+
+ fn io_mut(&mut self) -> &mut PollEvented2<mio_named_pipes::NamedPipe> {
+ &mut self.io
+ }
+}
+
+impl Read for NamedPipe {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ self.io.read(buf)
+ }
+}
+
+impl Write for NamedPipe {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.io.write(buf)
+ }
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.io.flush()
+ }
+}
+
+impl<'a> Read for &'a NamedPipe {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ (&self.io).read(buf)
+ }
+}
+
+impl<'a> Write for &'a NamedPipe {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ (&self.io).write(buf)
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ (&self.io).flush()
+ }
+}
+
+impl AsyncRead for NamedPipe {
+ unsafe fn prepare_uninitialized_buffer(&self, _: &mut [u8]) -> bool {
+ false
+ }
+
+ fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, std::io::Error> {
+ if let Async::NotReady = self.io.poll_read_ready(Ready::readable())? {
+ return Ok(Async::NotReady);
+ }
+
+ let mut stack_buf = [0u8; 1024];
+ let bytes_read = self.io_mut().read(&mut stack_buf);
+ match bytes_read {
+ Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
+ self.io_mut().clear_read_ready(Ready::readable())?;
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e),
+ Ok(bytes_read) => {
+ buf.put_slice(&stack_buf[0..bytes_read]);
+ Ok(Async::Ready(bytes_read))
+ }
+ }
+ }
+}
+
+impl AsyncWrite for NamedPipe {
+ fn shutdown(&mut self) -> Poll<(), std::io::Error> {
+ let _ = self.disconnect();
+ Ok(().into())
+ }
+
+ fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, std::io::Error> {
+ if let Async::NotReady = self.io.poll_write_ready()? {
+ return Ok(Async::NotReady);
+ }
+
+ let bytes_wrt = self.io_mut().write(buf.bytes());
+ match bytes_wrt {
+ Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
+ self.io_mut().clear_write_ready()?;
+ Ok(Async::NotReady)
+ }
+ Err(e) => Err(e),
+ Ok(bytes_wrt) => {
+ buf.advance(bytes_wrt);
+ Ok(Async::Ready(bytes_wrt))
+ }
+ }
+ }
+}
+
+impl fmt::Debug for NamedPipe {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.io.get_ref().fmt(f)
+ }
+}
+
+impl AsRawHandle for NamedPipe {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.io.get_ref().as_raw_handle()
+ }
+}
diff --git a/third_party/rust/audioipc/src/tokio_uds_stream.rs b/third_party/rust/audioipc/src/tokio_uds_stream.rs
new file mode 100644
index 0000000000..4453c3d8f7
--- /dev/null
+++ b/third_party/rust/audioipc/src/tokio_uds_stream.rs
@@ -0,0 +1,361 @@
+// Copied from tokio-uds/src/stream.rs revision 25e835c5b7e2cfeb9c22b1fd576844f6814a9477 (tokio-uds 0.2.5)
+// License MIT per upstream: https://github.com/tokio-rs/tokio/blob/master/tokio-uds/LICENSE
+// - Removed ucred for build simplicity
+// - Added clear_{read,write}_ready per: https://github.com/tokio-rs/tokio/pull/1294
+
+use tokio_io::{AsyncRead, AsyncWrite};
+use tokio_reactor::{Handle, PollEvented};
+
+use bytes::{Buf, BufMut};
+use futures::{Async, Future, Poll};
+use iovec::{self, IoVec};
+use mio::Ready;
+
+use std::fmt;
+use std::io::{self, Read, Write};
+use std::net::Shutdown;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::os::unix::net::{self, SocketAddr};
+use std::path::Path;
+
+/// A structure representing a connected Unix socket.
+///
+/// This socket can be connected directly with `UnixStream::connect` or accepted
+/// from a listener with `UnixListener::incoming`. Additionally, a pair of
+/// anonymous Unix sockets can be created with `UnixStream::pair`.
+pub struct UnixStream {
+ io: PollEvented<mio_uds::UnixStream>,
+}
+
+/// Future returned by `UnixStream::connect` which will resolve to a
+/// `UnixStream` when the stream is connected.
+#[derive(Debug)]
+pub struct ConnectFuture {
+ inner: State,
+}
+
+#[derive(Debug)]
+enum State {
+ Waiting(UnixStream),
+ Error(io::Error),
+ Empty,
+}
+
+impl UnixStream {
+ /// Connects to the socket named by `path`.
+ ///
+ /// This function will create a new Unix socket and connect to the path
+ /// specified, associating the returned stream with the default event loop's
+ /// handle.
+ pub fn connect<P>(path: P) -> ConnectFuture
+ where
+ P: AsRef<Path>,
+ {
+ let res = mio_uds::UnixStream::connect(path).map(UnixStream::new);
+
+ let inner = match res {
+ Ok(stream) => State::Waiting(stream),
+ Err(e) => State::Error(e),
+ };
+
+ ConnectFuture { inner }
+ }
+
+ /// Consumes a `UnixStream` in the standard library and returns a
+ /// nonblocking `UnixStream` from this crate.
+ ///
+ /// The returned stream will be associated with the given event loop
+ /// specified by `handle` and is ready to perform I/O.
+ pub fn from_std(stream: net::UnixStream, handle: &Handle) -> io::Result<UnixStream> {
+ let stream = mio_uds::UnixStream::from_stream(stream)?;
+ let io = PollEvented::new_with_handle(stream, handle)?;
+
+ Ok(UnixStream { io })
+ }
+
+ /// Creates an unnamed pair of connected sockets.
+ ///
+ /// This function will create a pair of interconnected Unix sockets for
+ /// communicating back and forth between one another. Each socket will
+ /// be associated with the default event loop's handle.
+ pub fn pair() -> io::Result<(UnixStream, UnixStream)> {
+ let (a, b) = mio_uds::UnixStream::pair()?;
+ let a = UnixStream::new(a);
+ let b = UnixStream::new(b);
+
+ Ok((a, b))
+ }
+
+ pub(crate) fn new(stream: mio_uds::UnixStream) -> UnixStream {
+ let io = PollEvented::new(stream);
+ UnixStream { io }
+ }
+
+ /// Test whether this socket is ready to be read or not.
+ pub fn poll_read_ready(&self, ready: Ready) -> Poll<Ready, io::Error> {
+ self.io.poll_read_ready(ready)
+ }
+
+ /// Clear read ready state.
+ pub fn clear_read_ready(&self, ready: mio::Ready) -> io::Result<()> {
+ self.io.clear_read_ready(ready)
+ }
+
+ /// Test whether this socket is ready to be written to or not.
+ pub fn poll_write_ready(&self) -> Poll<Ready, io::Error> {
+ self.io.poll_write_ready()
+ }
+
+ /// Clear write ready state.
+ pub fn clear_write_ready(&self) -> io::Result<()> {
+ self.io.clear_write_ready()
+ }
+
+ /// Returns the socket address of the local half of this connection.
+ pub fn local_addr(&self) -> io::Result<SocketAddr> {
+ self.io.get_ref().local_addr()
+ }
+
+ /// Returns the socket address of the remote half of this connection.
+ pub fn peer_addr(&self) -> io::Result<SocketAddr> {
+ self.io.get_ref().peer_addr()
+ }
+
+ /// Returns the value of the `SO_ERROR` option.
+ pub fn take_error(&self) -> io::Result<Option<io::Error>> {
+ self.io.get_ref().take_error()
+ }
+
+ /// Shuts down the read, write, or both halves of this connection.
+ ///
+ /// This function will cause all pending and future I/O calls on the
+ /// specified portions to immediately return with an appropriate value
+ /// (see the documentation of `Shutdown`).
+ pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
+ self.io.get_ref().shutdown(how)
+ }
+}
+
+impl Read for UnixStream {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.io.read(buf)
+ }
+}
+
+impl Write for UnixStream {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.io.write(buf)
+ }
+ fn flush(&mut self) -> io::Result<()> {
+ self.io.flush()
+ }
+}
+
+impl AsyncRead for UnixStream {
+ unsafe fn prepare_uninitialized_buffer(&self, _: &mut [u8]) -> bool {
+ false
+ }
+
+ fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
+ tokio_io::AsyncRead::read_buf(&mut &*self, buf)
+ }
+}
+
+impl AsyncWrite for UnixStream {
+ fn shutdown(&mut self) -> Poll<(), io::Error> {
+ <&UnixStream>::shutdown(&mut &*self)
+ }
+
+ fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
+ <&UnixStream>::write_buf(&mut &*self, buf)
+ }
+}
+
+impl<'a> Read for &'a UnixStream {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ (&self.io).read(buf)
+ }
+}
+
+impl<'a> Write for &'a UnixStream {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ (&self.io).write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ (&self.io).flush()
+ }
+}
+
+impl<'a> AsyncRead for &'a UnixStream {
+ unsafe fn prepare_uninitialized_buffer(&self, _: &mut [u8]) -> bool {
+ false
+ }
+
+ fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
+ if let Async::NotReady = <UnixStream>::poll_read_ready(self, Ready::readable())? {
+ return Ok(Async::NotReady);
+ }
+ unsafe {
+ let r = read_ready(buf, self.as_raw_fd());
+ if r == -1 {
+ let e = io::Error::last_os_error();
+ if e.kind() == io::ErrorKind::WouldBlock {
+ self.io.clear_read_ready(Ready::readable())?;
+ Ok(Async::NotReady)
+ } else {
+ Err(e)
+ }
+ } else {
+ let r = r as usize;
+ buf.advance_mut(r);
+ Ok(r.into())
+ }
+ }
+ }
+}
+
+impl<'a> AsyncWrite for &'a UnixStream {
+ fn shutdown(&mut self) -> Poll<(), io::Error> {
+ Ok(().into())
+ }
+
+ fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
+ if let Async::NotReady = <UnixStream>::poll_write_ready(self)? {
+ return Ok(Async::NotReady);
+ }
+ unsafe {
+ let r = write_ready(buf, self.as_raw_fd());
+ if r == -1 {
+ let e = io::Error::last_os_error();
+ if e.kind() == io::ErrorKind::WouldBlock {
+ self.io.clear_write_ready()?;
+ Ok(Async::NotReady)
+ } else {
+ Err(e)
+ }
+ } else {
+ let r = r as usize;
+ buf.advance(r);
+ Ok(r.into())
+ }
+ }
+ }
+}
+
+impl fmt::Debug for UnixStream {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ self.io.get_ref().fmt(f)
+ }
+}
+
+impl AsRawFd for UnixStream {
+ fn as_raw_fd(&self) -> RawFd {
+ self.io.get_ref().as_raw_fd()
+ }
+}
+
+impl Future for ConnectFuture {
+ type Item = UnixStream;
+ type Error = io::Error;
+
+ fn poll(&mut self) -> Poll<UnixStream, io::Error> {
+ use std::mem;
+
+ match self.inner {
+ State::Waiting(ref mut stream) => {
+ if let Async::NotReady = stream.io.poll_write_ready()? {
+ return Ok(Async::NotReady);
+ }
+
+ if let Some(e) = stream.io.get_ref().take_error()? {
+ return Err(e);
+ }
+ }
+ State::Error(_) => {
+ let e = match mem::replace(&mut self.inner, State::Empty) {
+ State::Error(e) => e,
+ _ => unreachable!(),
+ };
+
+ return Err(e);
+ }
+ State::Empty => panic!("can't poll stream twice"),
+ }
+
+ match mem::replace(&mut self.inner, State::Empty) {
+ State::Waiting(stream) => Ok(Async::Ready(stream)),
+ _ => unreachable!(),
+ }
+ }
+}
+
+unsafe fn read_ready<B: BufMut>(buf: &mut B, raw_fd: RawFd) -> isize {
+ // The `IoVec` type can't have a 0-length size, so we create a bunch
+ // of dummy versions on the stack with 1 length which we'll quickly
+ // overwrite.
+ let b1: &mut [u8] = &mut [0];
+ let b2: &mut [u8] = &mut [0];
+ let b3: &mut [u8] = &mut [0];
+ let b4: &mut [u8] = &mut [0];
+ let b5: &mut [u8] = &mut [0];
+ let b6: &mut [u8] = &mut [0];
+ let b7: &mut [u8] = &mut [0];
+ let b8: &mut [u8] = &mut [0];
+ let b9: &mut [u8] = &mut [0];
+ let b10: &mut [u8] = &mut [0];
+ let b11: &mut [u8] = &mut [0];
+ let b12: &mut [u8] = &mut [0];
+ let b13: &mut [u8] = &mut [0];
+ let b14: &mut [u8] = &mut [0];
+ let b15: &mut [u8] = &mut [0];
+ let b16: &mut [u8] = &mut [0];
+ let mut bufs: [&mut IoVec; 16] = [
+ b1.into(),
+ b2.into(),
+ b3.into(),
+ b4.into(),
+ b5.into(),
+ b6.into(),
+ b7.into(),
+ b8.into(),
+ b9.into(),
+ b10.into(),
+ b11.into(),
+ b12.into(),
+ b13.into(),
+ b14.into(),
+ b15.into(),
+ b16.into(),
+ ];
+
+ let n = buf.bytes_vec_mut(&mut bufs);
+ read_ready_vecs(&mut bufs[..n], raw_fd)
+}
+
+unsafe fn read_ready_vecs(bufs: &mut [&mut IoVec], raw_fd: RawFd) -> isize {
+ let iovecs = iovec::unix::as_os_slice_mut(bufs);
+
+ libc::readv(raw_fd, iovecs.as_ptr(), iovecs.len() as i32)
+}
+
+unsafe fn write_ready<B: Buf>(buf: &mut B, raw_fd: RawFd) -> isize {
+ // The `IoVec` type can't have a zero-length size, so create a dummy
+ // version from a 1-length slice which we'll overwrite with the
+ // `bytes_vec` method.
+ static DUMMY: &[u8] = &[0];
+ let iovec = <&IoVec>::from(DUMMY);
+ let mut bufs = [
+ iovec, iovec, iovec, iovec, iovec, iovec, iovec, iovec, iovec, iovec, iovec, iovec, iovec,
+ iovec, iovec, iovec,
+ ];
+
+ let n = buf.bytes_vec(&mut bufs);
+ write_ready_vecs(&bufs[..n], raw_fd)
+}
+
+unsafe fn write_ready_vecs(bufs: &[&IoVec], raw_fd: RawFd) -> isize {
+ let iovecs = iovec::unix::as_os_slice(bufs);
+
+ libc::writev(raw_fd, iovecs.as_ptr(), iovecs.len() as i32)
+}
diff --git a/third_party/rust/audioipc2-client/.cargo-checksum.json b/third_party/rust/audioipc2-client/.cargo-checksum.json
new file mode 100644
index 0000000000..ed34c2bc99
--- /dev/null
+++ b/third_party/rust/audioipc2-client/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"31dc34fae9951183eaed3511cffe3d830d52ba3c046257454f09a06156d0716b","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/context.rs":"45d640800e8b287ca0242d867618bf33396fb3a9c270b7fb58adca76e37c72cc","src/lib.rs":"c4a6797734489280f6b97dd72c9e51a7bd7be4104592eece3929e29d45cbca4a","src/send_recv.rs":"859abe75b521eb4297c84b30423814b5b87f3c7741ad16fe72189212e123e1ac","src/stream.rs":"46068bb0da752c9dee489f3620d4588967921000e234110ee066397595700ae0"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc2-client/Cargo.toml b/third_party/rust/audioipc2-client/Cargo.toml
new file mode 100644
index 0000000000..106fb300a8
--- /dev/null
+++ b/third_party/rust/audioipc2-client/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "audioipc2-client"
+version = "0.5.0"
+authors = [
+ "Matthew Gregan <kinetik@flim.org>",
+ "Dan Glastonbury <dan.glastonbury@gmail.com>"
+ ]
+license = "ISC"
+description = "Cubeb Backend for talking to remote cubeb server."
+edition = "2018"
+
+[dependencies]
+audioipc = { package = "audioipc2", path="../audioipc" }
+cubeb-backend = "0.10"
+log = "0.4"
+
+[dependencies.audio_thread_priority]
+version = "0.26.1"
+default-features = false
+features = ["winapi"]
diff --git a/third_party/rust/audioipc2-client/cbindgen.toml b/third_party/rust/audioipc2-client/cbindgen.toml
new file mode 100644
index 0000000000..b3267889c6
--- /dev/null
+++ b/third_party/rust/audioipc2-client/cbindgen.toml
@@ -0,0 +1,28 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "audioipc2"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[parse]
+parse_deps = true
+include = ["audioipc2"]
+
+[fn]
+args = "Vertical"
+rename_args = "GeckoCase"
+
+[struct]
+rename_fields = "GeckoCase"
+
+[defines]
+"windows" = "XP_WIN"
+"unix" = "XP_UNIX"
diff --git a/third_party/rust/audioipc2-client/src/context.rs b/third_party/rust/audioipc2-client/src/context.rs
new file mode 100644
index 0000000000..c255815ac8
--- /dev/null
+++ b/third_party/rust/audioipc2-client/src/context.rs
@@ -0,0 +1,385 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::stream;
+use crate::{assert_not_in_callback, run_in_callback};
+use crate::{ClientStream, AUDIOIPC_INIT_PARAMS};
+#[cfg(target_os = "linux")]
+use audio_thread_priority::get_current_thread_info;
+#[cfg(not(target_os = "linux"))]
+use audio_thread_priority::promote_current_thread_to_real_time;
+use audioipc::ipccore::EventLoopHandle;
+use audioipc::{ipccore, rpccore, sys, PlatformHandle};
+use audioipc::{
+ messages, messages::DeviceCollectionReq, messages::DeviceCollectionResp, ClientMessage,
+ ServerMessage,
+};
+use cubeb_backend::{
+ capi_new, ffi, Context, ContextOps, DeviceCollectionRef, DeviceId, DeviceType, Error, Ops,
+ Result, Stream, StreamParams, StreamParamsRef,
+};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_void;
+use std::sync::{Arc, Mutex};
+use std::thread;
+use std::{fmt, mem, ptr};
+
+struct CubebClient;
+
+impl rpccore::Client for CubebClient {
+ type ServerMessage = ServerMessage;
+ type ClientMessage = ClientMessage;
+}
+
+pub const CLIENT_OPS: Ops = capi_new!(ClientContext, ClientStream);
+
+// ClientContext's layout *must* match cubeb.c's `struct cubeb` for the
+// common fields.
+#[repr(C)]
+pub struct ClientContext {
+ _ops: *const Ops,
+ rpc: rpccore::Proxy<ServerMessage, ClientMessage>,
+ rpc_thread: ipccore::EventLoopThread,
+ callback_thread: ipccore::EventLoopThread,
+ backend_id: CString,
+ device_collection_rpc: bool,
+ input_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+ output_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+}
+
+impl ClientContext {
+ #[doc(hidden)]
+ pub fn rpc_handle(&self) -> &EventLoopHandle {
+ self.rpc_thread.handle()
+ }
+
+ #[doc(hidden)]
+ pub fn rpc(&self) -> Result<rpccore::Proxy<ServerMessage, ClientMessage>> {
+ self.rpc.try_clone().map_err(|_| Error::default())
+ }
+
+ #[doc(hidden)]
+ pub fn callback_handle(&self) -> &EventLoopHandle {
+ self.callback_thread.handle()
+ }
+}
+
+#[cfg(target_os = "linux")]
+fn promote_thread(rpc: &rpccore::Proxy<ServerMessage, ClientMessage>) {
+ match get_current_thread_info() {
+ Ok(info) => {
+ let bytes = info.serialize();
+ let _ = rpc.call(ServerMessage::PromoteThreadToRealTime(bytes));
+ }
+ Err(_) => {
+ warn!("Could not remotely promote thread to RT.");
+ }
+ }
+}
+
+#[cfg(not(target_os = "linux"))]
+fn promote_thread(_rpc: &rpccore::Proxy<ServerMessage, ClientMessage>) {
+ match promote_current_thread_to_real_time(256, 48000) {
+ Ok(_) => {
+ info!("Audio thread promoted to real-time.");
+ }
+ Err(_) => {
+ warn!("Could not promote thread to real-time.");
+ }
+ }
+}
+
+fn register_thread(callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>) {
+ if let Some(func) = callback {
+ let thr = thread::current();
+ let name = CString::new(thr.name().unwrap()).unwrap();
+ func(name.as_ptr());
+ }
+}
+
+fn unregister_thread(callback: Option<extern "C" fn()>) {
+ if let Some(func) = callback {
+ func();
+ }
+}
+
+fn promote_and_register_thread(
+ rpc: &rpccore::Proxy<ServerMessage, ClientMessage>,
+ callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+) {
+ promote_thread(rpc);
+ register_thread(callback);
+}
+
+#[derive(Default)]
+struct DeviceCollectionCallback {
+ cb: ffi::cubeb_device_collection_changed_callback,
+ user_ptr: usize,
+}
+
+struct DeviceCollectionServer {
+ input_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+ output_device_callback: Arc<Mutex<DeviceCollectionCallback>>,
+}
+
+impl rpccore::Server for DeviceCollectionServer {
+ type ServerMessage = DeviceCollectionReq;
+ type ClientMessage = DeviceCollectionResp;
+
+ fn process(&mut self, req: Self::ServerMessage) -> Self::ClientMessage {
+ match req {
+ DeviceCollectionReq::DeviceChange(device_type) => {
+ trace!(
+ "ctx_thread: DeviceChange Callback: device_type={}",
+ device_type
+ );
+
+ let devtype = cubeb_backend::DeviceType::from_bits_truncate(device_type);
+
+ let (input_cb, input_user_ptr) = {
+ let dcb = self.input_device_callback.lock().unwrap();
+ (dcb.cb, dcb.user_ptr)
+ };
+ let (output_cb, output_user_ptr) = {
+ let dcb = self.output_device_callback.lock().unwrap();
+ (dcb.cb, dcb.user_ptr)
+ };
+
+ run_in_callback(|| {
+ if devtype.contains(cubeb_backend::DeviceType::INPUT) {
+ unsafe { input_cb.unwrap()(ptr::null_mut(), input_user_ptr as *mut c_void) }
+ }
+ if devtype.contains(cubeb_backend::DeviceType::OUTPUT) {
+ unsafe {
+ output_cb.unwrap()(ptr::null_mut(), output_user_ptr as *mut c_void)
+ }
+ }
+ });
+
+ DeviceCollectionResp::DeviceChange
+ }
+ }
+ }
+}
+
+impl ContextOps for ClientContext {
+ fn init(_context_name: Option<&CStr>) -> Result<Context> {
+ assert_not_in_callback();
+
+ let params = AUDIOIPC_INIT_PARAMS.with(|p| p.replace(None).unwrap());
+ let thread_create_callback = params.thread_create_callback;
+ let thread_destroy_callback = params.thread_destroy_callback;
+
+ let server_connection =
+ unsafe { sys::Pipe::from_raw_handle(PlatformHandle::new(params.server_connection)) };
+
+ let rpc_thread = ipccore::EventLoopThread::new(
+ "AudioIPC Client RPC".to_string(),
+ None,
+ move || register_thread(thread_create_callback),
+ move || unregister_thread(thread_destroy_callback),
+ )
+ .map_err(|_| Error::default())?;
+ let rpc = rpc_thread
+ .handle()
+ .bind_client::<CubebClient>(server_connection)
+ .map_err(|_| Error::default())?;
+ let rpc2 = rpc.try_clone().map_err(|_| Error::default())?;
+
+ // Don't let errors bubble from here. Later calls against this context
+ // will return errors the caller expects to handle.
+ let _ = send_recv!(rpc, ClientConnect(std::process::id()) => ClientConnected);
+
+ let backend_id = send_recv!(rpc, ContextGetBackendId => ContextBackendId())
+ .unwrap_or_else(|_| "(remote error)".to_string());
+ let backend_id = CString::new(backend_id).expect("backend_id query failed");
+
+ // TODO: remove params.pool_size from init params.
+ let callback_thread = ipccore::EventLoopThread::new(
+ "AudioIPC Client Callback".to_string(),
+ Some(params.stack_size),
+ move || promote_and_register_thread(&rpc2, thread_create_callback),
+ move || unregister_thread(thread_destroy_callback),
+ )
+ .map_err(|_| Error::default())?;
+
+ let ctx = Box::new(ClientContext {
+ _ops: &CLIENT_OPS as *const _,
+ rpc,
+ rpc_thread,
+ callback_thread,
+ backend_id,
+ device_collection_rpc: false,
+ input_device_callback: Arc::new(Mutex::new(Default::default())),
+ output_device_callback: Arc::new(Mutex::new(Default::default())),
+ });
+ Ok(unsafe { Context::from_ptr(Box::into_raw(ctx) as *mut _) })
+ }
+
+ fn backend_id(&mut self) -> &CStr {
+ assert_not_in_callback();
+ self.backend_id.as_c_str()
+ }
+
+ fn max_channel_count(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ send_recv!(self.rpc()?, ContextGetMaxChannelCount => ContextMaxChannelCount())
+ }
+
+ fn min_latency(&mut self, params: StreamParams) -> Result<u32> {
+ assert_not_in_callback();
+ let params = messages::StreamParams::from(params.as_ref());
+ send_recv!(self.rpc()?, ContextGetMinLatency(params) => ContextMinLatency())
+ }
+
+ fn preferred_sample_rate(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ send_recv!(self.rpc()?, ContextGetPreferredSampleRate => ContextPreferredSampleRate())
+ }
+
+ fn enumerate_devices(
+ &mut self,
+ devtype: DeviceType,
+ collection: &DeviceCollectionRef,
+ ) -> Result<()> {
+ assert_not_in_callback();
+ let v: Vec<ffi::cubeb_device_info> = send_recv!(
+ self.rpc()?, ContextGetDeviceEnumeration(devtype.bits()) => ContextEnumeratedDevices())?
+ .into_iter()
+ .map(|i| i.into())
+ .collect();
+ let mut vs = v.into_boxed_slice();
+ let coll = unsafe { &mut *collection.as_ptr() };
+ coll.device = vs.as_mut_ptr();
+ coll.count = vs.len();
+ // Giving away the memory owned by vs. Don't free it!
+ // Reclaimed in `device_collection_destroy`.
+ mem::forget(vs);
+ Ok(())
+ }
+
+ fn device_collection_destroy(&mut self, collection: &mut DeviceCollectionRef) -> Result<()> {
+ assert_not_in_callback();
+ unsafe {
+ let coll = &mut *collection.as_ptr();
+ let mut devices = Vec::from_raw_parts(
+ coll.device as *mut ffi::cubeb_device_info,
+ coll.count,
+ coll.count,
+ );
+ for dev in &mut devices {
+ if !dev.device_id.is_null() {
+ let _ = CString::from_raw(dev.device_id as *mut _);
+ }
+ if !dev.group_id.is_null() {
+ let _ = CString::from_raw(dev.group_id as *mut _);
+ }
+ if !dev.vendor_name.is_null() {
+ let _ = CString::from_raw(dev.vendor_name as *mut _);
+ }
+ if !dev.friendly_name.is_null() {
+ let _ = CString::from_raw(dev.friendly_name as *mut _);
+ }
+ }
+ coll.device = ptr::null_mut();
+ coll.count = 0;
+ Ok(())
+ }
+ }
+
+ fn stream_init(
+ &mut self,
+ stream_name: Option<&CStr>,
+ input_device: DeviceId,
+ input_stream_params: Option<&StreamParamsRef>,
+ output_device: DeviceId,
+ output_stream_params: Option<&StreamParamsRef>,
+ latency_frames: u32,
+ // These params aren't sent to the server
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+ ) -> Result<Stream> {
+ assert_not_in_callback();
+
+ let stream_name = stream_name.map(|name| name.to_bytes_with_nul().to_vec());
+
+ let input_stream_params = input_stream_params.map(messages::StreamParams::from);
+ let output_stream_params = output_stream_params.map(messages::StreamParams::from);
+
+ let init_params = messages::StreamInitParams {
+ stream_name,
+ input_device: input_device as usize,
+ input_stream_params,
+ output_device: output_device as usize,
+ output_stream_params,
+ latency_frames,
+ };
+ stream::init(self, init_params, data_callback, state_callback, user_ptr)
+ }
+
+ fn register_device_collection_changed(
+ &mut self,
+ devtype: DeviceType,
+ collection_changed_callback: ffi::cubeb_device_collection_changed_callback,
+ user_ptr: *mut c_void,
+ ) -> Result<()> {
+ assert_not_in_callback();
+
+ if !self.device_collection_rpc {
+ let mut fd = send_recv!(self.rpc()?,
+ ContextSetupDeviceCollectionCallback =>
+ ContextSetupDeviceCollectionCallback())?;
+
+ let stream = unsafe { sys::Pipe::from_raw_handle(fd.platform_handle.take_handle()) };
+
+ let server = DeviceCollectionServer {
+ input_device_callback: self.input_device_callback.clone(),
+ output_device_callback: self.output_device_callback.clone(),
+ };
+
+ self.rpc_handle()
+ .bind_server(server, stream)
+ .map_err(|_| Error::default())?;
+ self.device_collection_rpc = true;
+ }
+
+ if devtype.contains(cubeb_backend::DeviceType::INPUT) {
+ let mut cb = self.input_device_callback.lock().unwrap();
+ cb.cb = collection_changed_callback;
+ cb.user_ptr = user_ptr as usize;
+ }
+ if devtype.contains(cubeb_backend::DeviceType::OUTPUT) {
+ let mut cb = self.output_device_callback.lock().unwrap();
+ cb.cb = collection_changed_callback;
+ cb.user_ptr = user_ptr as usize;
+ }
+
+ let enable = collection_changed_callback.is_some();
+ send_recv!(self.rpc()?,
+ ContextRegisterDeviceCollectionChanged(devtype.bits(), enable) =>
+ ContextRegisteredDeviceCollectionChanged)
+ }
+}
+
+impl Drop for ClientContext {
+ fn drop(&mut self) {
+ debug!("ClientContext dropped...");
+ let _ = self
+ .rpc()
+ .and_then(|rpc| send_recv!(rpc, ClientDisconnect => ClientDisconnected));
+ }
+}
+
+impl fmt::Debug for ClientContext {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("ClientContext")
+ .field("_ops", &self._ops)
+ .field("rpc", &self.rpc)
+ .field("core", &self.rpc_thread)
+ .field("cpu_pool", &"...")
+ .finish()
+ }
+}
diff --git a/third_party/rust/audioipc2-client/src/lib.rs b/third_party/rust/audioipc2-client/src/lib.rs
new file mode 100644
index 0000000000..51a68af709
--- /dev/null
+++ b/third_party/rust/audioipc2-client/src/lib.rs
@@ -0,0 +1,81 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+#![warn(unused_extern_crates)]
+
+#[macro_use]
+extern crate log;
+
+#[macro_use]
+mod send_recv;
+mod context;
+mod stream;
+
+use crate::context::ClientContext;
+use crate::stream::ClientStream;
+use audioipc::PlatformHandleType;
+use cubeb_backend::{capi, ffi};
+use std::os::raw::{c_char, c_int};
+
+thread_local!(static IN_CALLBACK: std::cell::RefCell<bool> = std::cell::RefCell::new(false));
+thread_local!(static AUDIOIPC_INIT_PARAMS: std::cell::RefCell<Option<AudioIpcInitParams>> = std::cell::RefCell::new(None));
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct AudioIpcInitParams {
+ // Fields only need to be public for ipctest.
+ pub server_connection: PlatformHandleType,
+ pub pool_size: usize,
+ pub stack_size: usize,
+ pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+ pub thread_destroy_callback: Option<extern "C" fn()>,
+}
+
+unsafe impl Send for AudioIpcInitParams {}
+
+fn set_in_callback(in_callback: bool) {
+ IN_CALLBACK.with(|b| {
+ assert_eq!(*b.borrow(), !in_callback);
+ *b.borrow_mut() = in_callback;
+ });
+}
+
+fn run_in_callback<F, R>(f: F) -> R
+where
+ F: FnOnce() -> R,
+{
+ set_in_callback(true);
+
+ let r = f();
+
+ set_in_callback(false);
+
+ r
+}
+
+fn assert_not_in_callback() {
+ IN_CALLBACK.with(|b| {
+ assert!(!*b.borrow());
+ });
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+/// Entry point from C code.
+pub unsafe extern "C" fn audioipc2_client_init(
+ c: *mut *mut ffi::cubeb,
+ context_name: *const c_char,
+ init_params: *const AudioIpcInitParams,
+) -> c_int {
+ if init_params.is_null() {
+ return cubeb_backend::ffi::CUBEB_ERROR;
+ }
+
+ let init_params = &*init_params;
+
+ AUDIOIPC_INIT_PARAMS.with(|p| {
+ *p.borrow_mut() = Some(*init_params);
+ });
+ capi::capi_init::<ClientContext>(c, context_name)
+}
diff --git a/third_party/rust/audioipc2-client/src/send_recv.rs b/third_party/rust/audioipc2-client/src/send_recv.rs
new file mode 100644
index 0000000000..1134c99a49
--- /dev/null
+++ b/third_party/rust/audioipc2-client/src/send_recv.rs
@@ -0,0 +1,73 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use cubeb_backend::Error;
+use std::os::raw::c_int;
+
+#[doc(hidden)]
+pub fn _err<E>(e: E) -> Error
+where
+ E: Into<Option<c_int>>,
+{
+ match e.into() {
+ Some(e) => Error::from_raw(e),
+ None => Error::error(),
+ }
+}
+
+#[macro_export]
+macro_rules! send_recv {
+ ($rpc:expr, $smsg:ident => $rmsg:ident) => {{
+ let resp = send_recv!(__send $rpc, $smsg);
+ send_recv!(__recv resp, $rmsg)
+ }};
+ ($rpc:expr, $smsg:ident => $rmsg:ident()) => {{
+ let resp = send_recv!(__send $rpc, $smsg);
+ send_recv!(__recv resp, $rmsg __result)
+ }};
+ ($rpc:expr, $smsg:ident($($a:expr),*) => $rmsg:ident) => {{
+ let resp = send_recv!(__send $rpc, $smsg, $($a),*);
+ send_recv!(__recv resp, $rmsg)
+ }};
+ ($rpc:expr, $smsg:ident($($a:expr),*) => $rmsg:ident()) => {{
+ let resp = send_recv!(__send $rpc, $smsg, $($a),*);
+ send_recv!(__recv resp, $rmsg __result)
+ }};
+ //
+ (__send $rpc:expr, $smsg:ident) => ({
+ $rpc.call(ServerMessage::$smsg)
+ });
+ (__send $rpc:expr, $smsg:ident, $($a:expr),*) => ({
+ $rpc.call(ServerMessage::$smsg($($a),*))
+ });
+ (__recv $resp:expr, $rmsg:ident) => ({
+ match $resp {
+ Ok(ClientMessage::$rmsg) => Ok(()),
+ Ok(ClientMessage::Error(e)) => Err($crate::send_recv::_err(e)),
+ Ok(m) => {
+ debug!("received wrong message - got={:?}", m);
+ Err($crate::send_recv::_err(None))
+ },
+ Err(e) => {
+ debug!("received error from rpc - got={:?}", e);
+ Err($crate::send_recv::_err(None))
+ },
+ }
+ });
+ (__recv $resp:expr, $rmsg:ident __result) => ({
+ match $resp {
+ Ok(ClientMessage::$rmsg(v)) => Ok(v),
+ Ok(ClientMessage::Error(e)) => Err($crate::send_recv::_err(e)),
+ Ok(m) => {
+ debug!("received wrong message - got={:?}", m);
+ Err($crate::send_recv::_err(None))
+ },
+ Err(e) => {
+ debug!("received error - got={:?}", e);
+ Err($crate::send_recv::_err(None))
+ },
+ }
+ })
+}
diff --git a/third_party/rust/audioipc2-client/src/stream.rs b/third_party/rust/audioipc2-client/src/stream.rs
new file mode 100644
index 0000000000..315efd7906
--- /dev/null
+++ b/third_party/rust/audioipc2-client/src/stream.rs
@@ -0,0 +1,342 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::ClientContext;
+use crate::{assert_not_in_callback, run_in_callback};
+use audioipc::messages::StreamCreateParams;
+use audioipc::messages::{self, CallbackReq, CallbackResp, ClientMessage, ServerMessage};
+use audioipc::shm::SharedMem;
+use audioipc::{rpccore, sys};
+use cubeb_backend::{ffi, DeviceRef, Error, Result, Stream, StreamOps};
+use std::ffi::{CStr, CString};
+use std::os::raw::c_void;
+use std::ptr;
+use std::sync::mpsc;
+use std::sync::{Arc, Mutex};
+
+pub struct Device(ffi::cubeb_device);
+
+impl Drop for Device {
+ fn drop(&mut self) {
+ unsafe {
+ if !self.0.input_name.is_null() {
+ let _ = CString::from_raw(self.0.input_name as *mut _);
+ }
+ if !self.0.output_name.is_null() {
+ let _ = CString::from_raw(self.0.output_name as *mut _);
+ }
+ }
+ }
+}
+
+// ClientStream's layout *must* match cubeb.c's `struct cubeb_stream` for the
+// common fields.
+#[repr(C)]
+#[derive(Debug)]
+pub struct ClientStream<'ctx> {
+ // This must be a reference to Context for cubeb, cubeb accesses
+ // stream methods via stream->context->ops
+ context: &'ctx ClientContext,
+ user_ptr: *mut c_void,
+ token: usize,
+ device_change_cb: Arc<Mutex<ffi::cubeb_device_changed_callback>>,
+ // Signals ClientStream that CallbackServer has dropped.
+ shutdown_rx: mpsc::Receiver<()>,
+}
+
+struct CallbackServer {
+ shm: SharedMem,
+ duplex_input: Option<Vec<u8>>,
+ data_cb: ffi::cubeb_data_callback,
+ state_cb: ffi::cubeb_state_callback,
+ user_ptr: usize,
+ device_change_cb: Arc<Mutex<ffi::cubeb_device_changed_callback>>,
+ // Signals ClientStream that CallbackServer has dropped.
+ _shutdown_tx: mpsc::Sender<()>,
+}
+
+impl rpccore::Server for CallbackServer {
+ type ServerMessage = CallbackReq;
+ type ClientMessage = CallbackResp;
+
+ fn process(&mut self, req: Self::ServerMessage) -> Self::ClientMessage {
+ match req {
+ CallbackReq::Data {
+ nframes,
+ input_frame_size,
+ output_frame_size,
+ } => {
+ trace!(
+ "stream_thread: Data Callback: nframes={} input_fs={} output_fs={}",
+ nframes,
+ input_frame_size,
+ output_frame_size,
+ );
+
+ let input_nbytes = nframes as usize * input_frame_size;
+ let output_nbytes = nframes as usize * output_frame_size;
+
+ // Input and output reuse the same shmem backing. Unfortunately, cubeb's data_callback isn't
+ // specified in such a way that would require the callee to consume all of the input before
+ // writing to the output (i.e., it is passed as two pointers that aren't expected to alias).
+ // That means we need to copy the input here.
+ if let Some(buf) = &mut self.duplex_input {
+ assert!(input_nbytes > 0);
+ assert!(buf.capacity() >= input_nbytes);
+ unsafe {
+ let input = self.shm.get_slice(input_nbytes).unwrap();
+ ptr::copy_nonoverlapping(input.as_ptr(), buf.as_mut_ptr(), input.len());
+ }
+ }
+
+ run_in_callback(|| {
+ let nframes = unsafe {
+ let input_ptr = if input_frame_size > 0 {
+ if let Some(buf) = &mut self.duplex_input {
+ buf.as_ptr()
+ } else {
+ self.shm.get_slice(input_nbytes).unwrap().as_ptr()
+ }
+ } else {
+ ptr::null()
+ };
+ let output_ptr = if output_frame_size > 0 {
+ self.shm.get_mut_slice(output_nbytes).unwrap().as_mut_ptr()
+ } else {
+ ptr::null_mut()
+ };
+
+ self.data_cb.unwrap()(
+ ptr::null_mut(), // https://github.com/kinetiknz/cubeb/issues/518
+ self.user_ptr as *mut c_void,
+ input_ptr as *const _,
+ output_ptr as *mut _,
+ nframes as _,
+ )
+ };
+
+ CallbackResp::Data(nframes as isize)
+ })
+ }
+ CallbackReq::State(state) => {
+ trace!("stream_thread: State Callback: {:?}", state);
+ run_in_callback(|| unsafe {
+ self.state_cb.unwrap()(ptr::null_mut(), self.user_ptr as *mut _, state);
+ });
+
+ CallbackResp::State
+ }
+ CallbackReq::DeviceChange => {
+ run_in_callback(|| {
+ let cb = *self.device_change_cb.lock().unwrap();
+ if let Some(cb) = cb {
+ unsafe {
+ cb(self.user_ptr as *mut _);
+ }
+ } else {
+ warn!("DeviceChange received with null callback");
+ }
+ });
+
+ CallbackResp::DeviceChange
+ }
+ }
+ }
+}
+
+impl<'ctx> ClientStream<'ctx> {
+ fn init(
+ ctx: &'ctx ClientContext,
+ init_params: messages::StreamInitParams,
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+ ) -> Result<Stream> {
+ assert_not_in_callback();
+
+ let rpc = ctx.rpc()?;
+ let create_params = StreamCreateParams {
+ input_stream_params: init_params.input_stream_params,
+ output_stream_params: init_params.output_stream_params,
+ };
+ let mut data = send_recv!(rpc, StreamCreate(create_params) => StreamCreated())?;
+
+ debug!(
+ "token = {}, handle = {:?} area_size = {:?}",
+ data.token, data.shm_handle, data.shm_area_size
+ );
+
+ let shm =
+ match unsafe { SharedMem::from(data.shm_handle.take_handle(), data.shm_area_size) } {
+ Ok(shm) => shm,
+ Err(e) => {
+ warn!(
+ "SharedMem client mapping failed (size={}, err={:?})",
+ data.shm_area_size, e
+ );
+ return Err(Error::default());
+ }
+ };
+
+ let duplex_input = if let (Some(_), Some(_)) = (
+ init_params.input_stream_params,
+ init_params.output_stream_params,
+ ) {
+ let mut duplex_input = Vec::new();
+ match duplex_input.try_reserve_exact(data.shm_area_size) {
+ Ok(()) => Some(duplex_input),
+ Err(e) => {
+ warn!(
+ "duplex_input allocation failed (size={}, err={:?})",
+ data.shm_area_size, e
+ );
+ return Err(Error::default());
+ }
+ }
+ } else {
+ None
+ };
+
+ let mut stream =
+ send_recv!(rpc, StreamInit(data.token, init_params) => StreamInitialized())?;
+ let stream = unsafe { sys::Pipe::from_raw_handle(stream.take_handle()) };
+
+ let user_data = user_ptr as usize;
+
+ let null_cb: ffi::cubeb_device_changed_callback = None;
+ let device_change_cb = Arc::new(Mutex::new(null_cb));
+
+ let (_shutdown_tx, shutdown_rx) = mpsc::channel();
+
+ let server = CallbackServer {
+ shm,
+ duplex_input,
+ data_cb: data_callback,
+ state_cb: state_callback,
+ user_ptr: user_data,
+ device_change_cb: device_change_cb.clone(),
+ _shutdown_tx,
+ };
+
+ ctx.callback_handle()
+ .bind_server(server, stream)
+ .map_err(|_| Error::default())?;
+
+ let stream = Box::into_raw(Box::new(ClientStream {
+ context: ctx,
+ user_ptr,
+ token: data.token,
+ device_change_cb,
+ shutdown_rx,
+ }));
+ Ok(unsafe { Stream::from_ptr(stream as *mut _) })
+ }
+}
+
+impl Drop for ClientStream<'_> {
+ fn drop(&mut self) {
+ debug!("ClientStream drop");
+ let _ = self
+ .context
+ .rpc()
+ .and_then(|rpc| send_recv!(rpc, StreamDestroy(self.token) => StreamDestroyed));
+ debug!("ClientStream drop - stream destroyed");
+ // Wait for CallbackServer to shutdown. The remote server drops the RPC
+ // connection during StreamDestroy, which will cause CallbackServer to drop
+ // once the connection close is detected. Dropping CallbackServer will
+ // cause the shutdown channel to error on recv, which we rely on to
+ // synchronize with CallbackServer dropping.
+ let _ = self.shutdown_rx.recv();
+ debug!("ClientStream dropped");
+ }
+}
+
+impl StreamOps for ClientStream<'_> {
+ fn start(&mut self) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamStart(self.token) => StreamStarted)
+ }
+
+ fn stop(&mut self) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamStop(self.token) => StreamStopped)
+ }
+
+ fn position(&mut self) -> Result<u64> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamGetPosition(self.token) => StreamPosition())
+ }
+
+ fn latency(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamGetLatency(self.token) => StreamLatency())
+ }
+
+ fn input_latency(&mut self) -> Result<u32> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamGetInputLatency(self.token) => StreamInputLatency())
+ }
+
+ fn set_volume(&mut self, volume: f32) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamSetVolume(self.token, volume) => StreamVolumeSet)
+ }
+
+ fn set_name(&mut self, name: &CStr) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ send_recv!(rpc, StreamSetName(self.token, name.to_owned()) => StreamNameSet)
+ }
+
+ fn current_device(&mut self) -> Result<&DeviceRef> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ match send_recv!(rpc, StreamGetCurrentDevice(self.token) => StreamCurrentDevice()) {
+ Ok(d) => Ok(unsafe { DeviceRef::from_ptr(Box::into_raw(Box::new(d.into()))) }),
+ Err(e) => Err(e),
+ }
+ }
+
+ fn device_destroy(&mut self, device: &DeviceRef) -> Result<()> {
+ assert_not_in_callback();
+ if device.as_ptr().is_null() {
+ Err(Error::error())
+ } else {
+ unsafe {
+ let _: Box<Device> = Box::from_raw(device.as_ptr() as *mut _);
+ }
+ Ok(())
+ }
+ }
+
+ fn register_device_changed_callback(
+ &mut self,
+ device_changed_callback: ffi::cubeb_device_changed_callback,
+ ) -> Result<()> {
+ assert_not_in_callback();
+ let rpc = self.context.rpc()?;
+ let enable = device_changed_callback.is_some();
+ *self.device_change_cb.lock().unwrap() = device_changed_callback;
+ send_recv!(rpc, StreamRegisterDeviceChangeCallback(self.token, enable) => StreamRegisterDeviceChangeCallback)
+ }
+}
+
+pub fn init(
+ ctx: &ClientContext,
+ init_params: messages::StreamInitParams,
+ data_callback: ffi::cubeb_data_callback,
+ state_callback: ffi::cubeb_state_callback,
+ user_ptr: *mut c_void,
+) -> Result<Stream> {
+ let stm = ClientStream::init(ctx, init_params, data_callback, state_callback, user_ptr)?;
+ debug_assert_eq!(stm.user_ptr(), user_ptr);
+ Ok(stm)
+}
diff --git a/third_party/rust/audioipc2-server/.cargo-checksum.json b/third_party/rust/audioipc2-server/.cargo-checksum.json
new file mode 100644
index 0000000000..7dcab92007
--- /dev/null
+++ b/third_party/rust/audioipc2-server/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"7feb495b23148ecc83ec7f480aefe19c9804a8900cdb4ceb005c049cdce82428","cbindgen.toml":"fb6abe1671497f432a06e40b1db7ed7cd2cceecbd9a2382193ad7534e8855e34","src/lib.rs":"d9cc7ca311cceb70acbc63b2190d6205094152e582faaad1b4a6061019f5803f","src/server.rs":"744f655486469daa4973d9101bd76a124ab6169b46b540f1c955932f6139e89f"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc2-server/Cargo.toml b/third_party/rust/audioipc2-server/Cargo.toml
new file mode 100644
index 0000000000..46f221ec92
--- /dev/null
+++ b/third_party/rust/audioipc2-server/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "audioipc2-server"
+version = "0.5.0"
+authors = [
+ "Matthew Gregan <kinetik@flim.org>",
+ "Dan Glastonbury <dan.glastonbury@gmail.com>"
+ ]
+license = "ISC"
+description = "Remote cubeb server"
+edition = "2018"
+
+[dependencies]
+audioipc = { package = "audioipc2", path = "../audioipc" }
+cubeb-core = "0.10.0"
+once_cell = "1.2.0"
+log = "0.4"
+slab = "0.4"
+
+[dependencies.error-chain]
+version = "0.12.0"
+default-features = false
+
+[dependencies.audio_thread_priority]
+version = "0.26.1"
+default-features = false
+features = ["winapi"]
diff --git a/third_party/rust/audioipc2-server/cbindgen.toml b/third_party/rust/audioipc2-server/cbindgen.toml
new file mode 100644
index 0000000000..b3267889c6
--- /dev/null
+++ b/third_party/rust/audioipc2-server/cbindgen.toml
@@ -0,0 +1,28 @@
+header = """/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+namespaces = ["mozilla", "audioipc2"]
+
+[export]
+item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions", "constants"]
+
+[parse]
+parse_deps = true
+include = ["audioipc2"]
+
+[fn]
+args = "Vertical"
+rename_args = "GeckoCase"
+
+[struct]
+rename_fields = "GeckoCase"
+
+[defines]
+"windows" = "XP_WIN"
+"unix" = "XP_UNIX"
diff --git a/third_party/rust/audioipc2-server/src/lib.rs b/third_party/rust/audioipc2-server/src/lib.rs
new file mode 100644
index 0000000000..9a5974d968
--- /dev/null
+++ b/third_party/rust/audioipc2-server/src/lib.rs
@@ -0,0 +1,221 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+#![warn(unused_extern_crates)]
+
+#[macro_use]
+extern crate error_chain;
+#[macro_use]
+extern crate log;
+
+use audio_thread_priority::promote_current_thread_to_real_time;
+use audioipc::ipccore;
+use audioipc::sys;
+use audioipc::PlatformHandleType;
+use once_cell::sync::Lazy;
+use std::ffi::{CStr, CString};
+use std::os::raw::c_void;
+use std::ptr;
+use std::sync::Mutex;
+use std::thread;
+
+mod server;
+
+struct CubebContextParams {
+ context_name: CString,
+ backend_name: Option<CString>,
+}
+
+static G_CUBEB_CONTEXT_PARAMS: Lazy<Mutex<CubebContextParams>> = Lazy::new(|| {
+ Mutex::new(CubebContextParams {
+ context_name: CString::new("AudioIPC Server").unwrap(),
+ backend_name: None,
+ })
+});
+
+#[allow(deprecated)]
+pub mod errors {
+ #![allow(clippy::upper_case_acronyms)]
+ error_chain! {
+ links {
+ AudioIPC(::audioipc::errors::Error, ::audioipc::errors::ErrorKind);
+ }
+ foreign_links {
+ Cubeb(cubeb_core::Error);
+ Io(::std::io::Error);
+ }
+ }
+}
+
+use crate::errors::*;
+
+struct ServerWrapper {
+ rpc_thread: ipccore::EventLoopThread,
+ callback_thread: ipccore::EventLoopThread,
+ device_collection_thread: ipccore::EventLoopThread,
+}
+
+fn register_thread(callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>) {
+ if let Some(func) = callback {
+ let name = CString::new(thread::current().name().unwrap()).unwrap();
+ func(name.as_ptr());
+ }
+}
+
+fn unregister_thread(callback: Option<extern "C" fn()>) {
+ if let Some(func) = callback {
+ func();
+ }
+}
+
+fn init_threads(
+ thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+ thread_destroy_callback: Option<extern "C" fn()>,
+) -> Result<ServerWrapper> {
+ let rpc_name = "AudioIPC Server RPC";
+ let rpc_thread = ipccore::EventLoopThread::new(
+ rpc_name.to_string(),
+ None,
+ move || {
+ trace!("Starting {} thread", rpc_name);
+ register_thread(thread_create_callback);
+ audioipc::server_platform_init();
+ },
+ move || {
+ unregister_thread(thread_destroy_callback);
+ trace!("Stopping {} thread", rpc_name);
+ },
+ )
+ .map_err(|e| {
+ debug!("Failed to start {} thread: {:?}", rpc_name, e);
+ e
+ })?;
+
+ let callback_name = "AudioIPC Server Callback";
+ let callback_thread = ipccore::EventLoopThread::new(
+ callback_name.to_string(),
+ None,
+ move || {
+ trace!("Starting {} thread", callback_name);
+ if let Err(e) = promote_current_thread_to_real_time(256, 48000) {
+ debug!(
+ "Failed to promote {} thread to real-time: {:?}",
+ callback_name, e
+ );
+ }
+ register_thread(thread_create_callback);
+ },
+ move || {
+ unregister_thread(thread_destroy_callback);
+ trace!("Stopping {} thread", callback_name);
+ },
+ )
+ .map_err(|e| {
+ debug!("Failed to start {} thread: {:?}", callback_name, e);
+ e
+ })?;
+
+ let device_collection_name = "AudioIPC DeviceCollection RPC";
+ let device_collection_thread = ipccore::EventLoopThread::new(
+ device_collection_name.to_string(),
+ None,
+ move || {
+ trace!("Starting {} thread", device_collection_name);
+ register_thread(thread_create_callback);
+ },
+ move || {
+ unregister_thread(thread_destroy_callback);
+ trace!("Stopping {} thread", device_collection_name);
+ },
+ )
+ .map_err(|e| {
+ debug!("Failed to start {} thread: {:?}", device_collection_name, e);
+ e
+ })?;
+
+ Ok(ServerWrapper {
+ rpc_thread,
+ callback_thread,
+ device_collection_thread,
+ })
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug)]
+pub struct AudioIpcServerInitParams {
+ // Fields only need to be public for ipctest.
+ pub thread_create_callback: Option<extern "C" fn(*const ::std::os::raw::c_char)>,
+ pub thread_destroy_callback: Option<extern "C" fn()>,
+}
+
+#[allow(clippy::missing_safety_doc)]
+#[no_mangle]
+pub unsafe extern "C" fn audioipc2_server_start(
+ context_name: *const std::os::raw::c_char,
+ backend_name: *const std::os::raw::c_char,
+ init_params: *const AudioIpcServerInitParams,
+) -> *mut c_void {
+ assert!(!init_params.is_null());
+ let mut params = G_CUBEB_CONTEXT_PARAMS.lock().unwrap();
+ if !context_name.is_null() {
+ params.context_name = CStr::from_ptr(context_name).to_owned();
+ }
+ if !backend_name.is_null() {
+ let backend_string = CStr::from_ptr(backend_name).to_owned();
+ params.backend_name = Some(backend_string);
+ }
+ match init_threads(
+ (*init_params).thread_create_callback,
+ (*init_params).thread_destroy_callback,
+ ) {
+ Ok(server) => Box::into_raw(Box::new(server)) as *mut _,
+ Err(_) => ptr::null_mut() as *mut _,
+ }
+}
+
+// A `shm_area_size` of 0 allows the server to calculate an appropriate shm size for each stream.
+// A non-zero `shm_area_size` forces all allocations to the specified size.
+#[no_mangle]
+pub extern "C" fn audioipc2_server_new_client(
+ p: *mut c_void,
+ shm_area_size: usize,
+) -> PlatformHandleType {
+ let wrapper: &ServerWrapper = unsafe { &*(p as *mut _) };
+
+ // We create a connected pair of anonymous IPC endpoints. One side
+ // is registered with the event loop core, the other side is returned
+ // to the caller to be remoted to the client to complete setup.
+ let (server_pipe, client_pipe) = match sys::make_pipe_pair() {
+ Ok((server_pipe, client_pipe)) => (server_pipe, client_pipe),
+ Err(e) => {
+ error!(
+ "audioipc_server_new_client - make_pipe_pair failed: {:?}",
+ e
+ );
+ return audioipc::INVALID_HANDLE_VALUE;
+ }
+ };
+
+ let rpc_thread = wrapper.rpc_thread.handle();
+ let callback_thread = wrapper.callback_thread.handle();
+ let device_collection_thread = wrapper.device_collection_thread.handle();
+
+ let server = server::CubebServer::new(
+ callback_thread.clone(),
+ device_collection_thread.clone(),
+ shm_area_size,
+ );
+ if let Err(e) = rpc_thread.bind_server(server, server_pipe) {
+ error!("audioipc_server_new_client - bind_server failed: {:?}", e);
+ return audioipc::INVALID_HANDLE_VALUE;
+ }
+
+ unsafe { client_pipe.into_raw() }
+}
+
+#[no_mangle]
+pub extern "C" fn audioipc2_server_stop(p: *mut c_void) {
+ let wrapper = unsafe { Box::<ServerWrapper>::from_raw(p as *mut _) };
+ drop(wrapper);
+}
diff --git a/third_party/rust/audioipc2-server/src/server.rs b/third_party/rust/audioipc2-server/src/server.rs
new file mode 100644
index 0000000000..ff85712847
--- /dev/null
+++ b/third_party/rust/audioipc2-server/src/server.rs
@@ -0,0 +1,902 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+#[cfg(target_os = "linux")]
+use audio_thread_priority::{promote_thread_to_real_time, RtPriorityThreadInfo};
+use audioipc::messages::SerializableHandle;
+use audioipc::messages::{
+ CallbackReq, CallbackResp, ClientMessage, Device, DeviceCollectionReq, DeviceCollectionResp,
+ DeviceInfo, RegisterDeviceCollectionChanged, ServerMessage, StreamCreate, StreamCreateParams,
+ StreamInitParams, StreamParams,
+};
+use audioipc::shm::SharedMem;
+use audioipc::{ipccore, rpccore, sys, PlatformHandle};
+use cubeb_core as cubeb;
+use cubeb_core::ffi;
+use std::convert::{From, TryInto};
+use std::ffi::CStr;
+use std::mem::size_of;
+use std::os::raw::{c_long, c_void};
+use std::rc::Rc;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::{cell::RefCell, sync::Mutex};
+use std::{panic, slice};
+
+use crate::errors::*;
+
+fn error(error: cubeb::Error) -> ClientMessage {
+ ClientMessage::Error(error.raw_code())
+}
+
+struct CubebDeviceCollectionManager {
+ servers: Mutex<Vec<(Rc<DeviceCollectionChangeCallback>, cubeb::DeviceType)>>,
+}
+
+impl CubebDeviceCollectionManager {
+ fn new() -> CubebDeviceCollectionManager {
+ CubebDeviceCollectionManager {
+ servers: Mutex::new(Vec::new()),
+ }
+ }
+
+ fn register(
+ &self,
+ context: &cubeb::Context,
+ server: &Rc<DeviceCollectionChangeCallback>,
+ devtype: cubeb::DeviceType,
+ ) -> cubeb::Result<()> {
+ let mut servers = self.servers.lock().unwrap();
+ if servers.is_empty() {
+ self.internal_register(context, true)?;
+ }
+ servers.push((server.clone(), devtype));
+ Ok(())
+ }
+
+ fn unregister(
+ &self,
+ context: &cubeb::Context,
+ server: &Rc<DeviceCollectionChangeCallback>,
+ devtype: cubeb::DeviceType,
+ ) -> cubeb::Result<()> {
+ let mut servers = self.servers.lock().unwrap();
+ servers.retain(|(s, d)| !Rc::ptr_eq(s, server) || d != &devtype);
+ if servers.is_empty() {
+ self.internal_register(context, false)?;
+ }
+ Ok(())
+ }
+
+ fn internal_register(&self, context: &cubeb::Context, enable: bool) -> cubeb::Result<()> {
+ for &(dir, cb) in &[
+ (
+ cubeb::DeviceType::INPUT,
+ device_collection_changed_input_cb_c as _,
+ ),
+ (
+ cubeb::DeviceType::OUTPUT,
+ device_collection_changed_output_cb_c as _,
+ ),
+ ] {
+ unsafe {
+ context.register_device_collection_changed(
+ dir,
+ if enable { Some(cb) } else { None },
+ if enable {
+ self as *const CubebDeviceCollectionManager as *mut c_void
+ } else {
+ std::ptr::null_mut()
+ },
+ )?;
+ }
+ }
+ Ok(())
+ }
+
+ unsafe fn device_collection_changed_callback(&self, device_type: ffi::cubeb_device_type) {
+ let servers = self.servers.lock().unwrap();
+ servers.iter().for_each(|(s, d)| {
+ if d.contains(cubeb::DeviceType::from_bits_truncate(device_type)) {
+ s.device_collection_changed_callback(device_type)
+ }
+ });
+ }
+}
+
+impl Drop for CubebDeviceCollectionManager {
+ fn drop(&mut self) {
+ assert!(self.servers.lock().unwrap().is_empty());
+ }
+}
+
+struct DevIdMap {
+ devices: Vec<usize>,
+}
+
+// A cubeb_devid is an opaque type which may be implemented with a stable
+// pointer in a cubeb backend. cubeb_devids received remotely must be
+// validated before use, so DevIdMap provides a simple 1:1 mapping between a
+// cubeb_devid and an IPC-transportable value suitable for use as a unique
+// handle.
+impl DevIdMap {
+ fn new() -> DevIdMap {
+ let mut d = DevIdMap {
+ devices: Vec::with_capacity(32),
+ };
+ // A null cubeb_devid is used for selecting the default device.
+ // Pre-populate the mapping with 0 -> 0 to handle nulls.
+ d.devices.push(0);
+ d
+ }
+
+ // Given a cubeb_devid, return a unique stable value suitable for use
+ // over IPC.
+ fn make_handle(&mut self, devid: usize) -> usize {
+ if let Some(i) = self.devices.iter().position(|&d| d == devid) {
+ return i;
+ }
+ self.devices.push(devid);
+ self.devices.len() - 1
+ }
+
+ // Given a handle produced by `make_handle`, return the associated
+ // cubeb_devid. Invalid handles result in a panic.
+ fn handle_to_id(&self, handle: usize) -> usize {
+ self.devices[handle]
+ }
+}
+
+struct CubebContextState {
+ // `manager` must be dropped before the `context` is destroyed.
+ manager: CubebDeviceCollectionManager,
+ context: cubeb::Result<cubeb::Context>,
+}
+
+thread_local!(static CONTEXT_KEY: RefCell<Option<CubebContextState>> = RefCell::new(None));
+
+fn cubeb_init_from_context_params() -> cubeb::Result<cubeb::Context> {
+ let params = super::G_CUBEB_CONTEXT_PARAMS.lock().unwrap();
+ let context_name = Some(params.context_name.as_c_str());
+ let backend_name = params.backend_name.as_deref();
+ let r = cubeb::Context::init(context_name, backend_name);
+ r.map_err(|e| {
+ info!("cubeb::Context::init failed r={:?}", e);
+ e
+ })
+}
+
+fn with_local_context<T, F>(f: F) -> T
+where
+ F: FnOnce(&cubeb::Result<cubeb::Context>, &mut CubebDeviceCollectionManager) -> T,
+{
+ CONTEXT_KEY.with(|k| {
+ let mut state = k.borrow_mut();
+ if state.is_none() {
+ *state = Some(CubebContextState {
+ manager: CubebDeviceCollectionManager::new(),
+ context: cubeb_init_from_context_params(),
+ });
+ }
+ let CubebContextState { manager, context } = state.as_mut().unwrap();
+ // Always reattempt to initialize cubeb, OS config may have changed.
+ if context.is_err() {
+ *context = cubeb_init_from_context_params();
+ }
+ f(context, manager)
+ })
+}
+
+struct DeviceCollectionClient;
+
+impl rpccore::Client for DeviceCollectionClient {
+ type ServerMessage = DeviceCollectionReq;
+ type ClientMessage = DeviceCollectionResp;
+}
+
+struct CallbackClient;
+
+impl rpccore::Client for CallbackClient {
+ type ServerMessage = CallbackReq;
+ type ClientMessage = CallbackResp;
+}
+
+struct ServerStreamCallbacks {
+ /// Size of input frame in bytes
+ input_frame_size: u16,
+ /// Size of output frame in bytes
+ output_frame_size: u16,
+ /// Shared memory buffer for transporting audio data to/from client
+ shm: SharedMem,
+ /// RPC interface for data_callback (on OS audio thread) to server callback thread
+ data_callback_rpc: rpccore::Proxy<CallbackReq, CallbackResp>,
+ /// RPC interface for state_callback (on any thread) to server callback thread
+ state_callback_rpc: rpccore::Proxy<CallbackReq, CallbackResp>,
+ /// RPC interface for device_change_callback (on any thread) to server callback thread
+ device_change_callback_rpc: rpccore::Proxy<CallbackReq, CallbackResp>,
+}
+
+impl ServerStreamCallbacks {
+ fn data_callback(&mut self, input: &[u8], output: &mut [u8], nframes: isize) -> isize {
+ trace!(
+ "Stream data callback: {} {} {}",
+ nframes,
+ input.len(),
+ output.len()
+ );
+
+ if self.input_frame_size != 0 {
+ if input.len() > self.shm.get_size() {
+ debug!(
+ "bad input size: input={} shm={}",
+ input.len(),
+ self.shm.get_size()
+ );
+ return cubeb::ffi::CUBEB_ERROR.try_into().unwrap();
+ }
+ unsafe {
+ self.shm
+ .get_mut_slice(input.len())
+ .unwrap()
+ .copy_from_slice(input);
+ }
+ }
+
+ if self.output_frame_size != 0 && output.len() > self.shm.get_size() {
+ debug!(
+ "bad output size: output={} shm={}",
+ output.len(),
+ self.shm.get_size()
+ );
+ return cubeb::ffi::CUBEB_ERROR.try_into().unwrap();
+ }
+
+ let r = self.data_callback_rpc.call(CallbackReq::Data {
+ nframes,
+ input_frame_size: self.input_frame_size as usize,
+ output_frame_size: self.output_frame_size as usize,
+ });
+
+ match r {
+ Ok(CallbackResp::Data(frames)) => {
+ if frames >= 0 && self.output_frame_size != 0 {
+ let nbytes = frames as usize * self.output_frame_size as usize;
+ unsafe {
+ output[..nbytes].copy_from_slice(self.shm.get_slice(nbytes).unwrap());
+ }
+ }
+ frames
+ }
+ _ => {
+ debug!("Unexpected message {:?} during data_callback", r);
+ cubeb::ffi::CUBEB_ERROR.try_into().unwrap()
+ }
+ }
+ }
+
+ fn state_callback(&self, state: cubeb::State) {
+ trace!("Stream state callback: {:?}", state);
+ let r = self
+ .state_callback_rpc
+ .call(CallbackReq::State(state.into()));
+ match r {
+ Ok(CallbackResp::State) => {}
+ _ => {
+ debug!("Unexpected message {:?} during state callback", r);
+ }
+ }
+ }
+
+ fn device_change_callback(&self) {
+ trace!("Stream device change callback");
+ let r = self
+ .device_change_callback_rpc
+ .call(CallbackReq::DeviceChange);
+ match r {
+ Ok(CallbackResp::DeviceChange) => {}
+ _ => {
+ debug!("Unexpected message {:?} during device change callback", r);
+ }
+ }
+ }
+}
+
+static SHM_ID: AtomicUsize = AtomicUsize::new(0);
+
+// Generate a temporary shm_id fragment that is unique to the process. This
+// path is used temporarily to create a shm segment, which is then
+// immediately deleted from the filesystem while retaining handles to the
+// shm to be shared between the server and client.
+fn get_shm_id() -> String {
+ format!(
+ "cubeb-shm-{}-{}",
+ std::process::id(),
+ SHM_ID.fetch_add(1, Ordering::SeqCst)
+ )
+}
+
+struct ServerStream {
+ stream: Option<cubeb::Stream>,
+ cbs: Box<ServerStreamCallbacks>,
+ client_pipe: Option<PlatformHandle>,
+}
+
+impl Drop for ServerStream {
+ fn drop(&mut self) {
+ // `stream` *must* be dropped before `cbs`.
+ drop(self.stream.take());
+ }
+}
+
+struct DeviceCollectionChangeCallback {
+ rpc: rpccore::Proxy<DeviceCollectionReq, DeviceCollectionResp>,
+}
+
+impl DeviceCollectionChangeCallback {
+ fn device_collection_changed_callback(&self, device_type: ffi::cubeb_device_type) {
+ // TODO: Assert device_type is in devtype.
+ debug!(
+ "Sending device collection ({:?}) changed event",
+ device_type
+ );
+ let _ = self
+ .rpc
+ .call(DeviceCollectionReq::DeviceChange(device_type));
+ }
+}
+
+pub struct CubebServer {
+ callback_thread: ipccore::EventLoopHandle,
+ device_collection_thread: ipccore::EventLoopHandle,
+ streams: slab::Slab<ServerStream>,
+ remote_pid: Option<u32>,
+ device_collection_change_callbacks: Option<Rc<DeviceCollectionChangeCallback>>,
+ devidmap: DevIdMap,
+ shm_area_size: usize,
+}
+
+impl Drop for CubebServer {
+ fn drop(&mut self) {
+ if let Some(device_collection_change_callbacks) = &self.device_collection_change_callbacks {
+ debug!("CubebServer: dropped with device_collection_change_callbacks registered");
+ CONTEXT_KEY.with(|k| {
+ let mut state = k.borrow_mut();
+ if let Some(CubebContextState {
+ manager,
+ context: Ok(context),
+ }) = state.as_mut()
+ {
+ for devtype in [cubeb::DeviceType::INPUT, cubeb::DeviceType::OUTPUT] {
+ let r = manager.unregister(
+ context,
+ device_collection_change_callbacks,
+ devtype,
+ );
+ if r.is_err() {
+ debug!("CubebServer: unregister failed: {:?}", r);
+ }
+ }
+ }
+ })
+ }
+ }
+}
+
+#[allow(unknown_lints)] // non_send_fields_in_send_ty is Nightly-only as of 2021-11-29.
+#[allow(clippy::non_send_fields_in_send_ty)]
+// XXX: required for server setup, verify this is safe.
+unsafe impl Send for CubebServer {}
+
+impl rpccore::Server for CubebServer {
+ type ServerMessage = ServerMessage;
+ type ClientMessage = ClientMessage;
+
+ fn process(&mut self, req: Self::ServerMessage) -> Self::ClientMessage {
+ if let ServerMessage::ClientConnect(pid) = req {
+ self.remote_pid = Some(pid);
+ }
+ with_local_context(|context, manager| match *context {
+ Err(_) => error(cubeb::Error::error()),
+ Ok(ref context) => self.process_msg(context, manager, &req),
+ })
+ }
+}
+
+// Debugging for BMO 1594216/1612044.
+macro_rules! try_stream {
+ ($self:expr, $stm_tok:expr) => {
+ if $self.streams.contains($stm_tok) {
+ $self.streams[$stm_tok]
+ .stream
+ .as_mut()
+ .expect("uninitialized stream")
+ } else {
+ error!(
+ "{}:{}:{} - Stream({}): invalid token",
+ file!(),
+ line!(),
+ column!(),
+ $stm_tok
+ );
+ return error(cubeb::Error::invalid_parameter());
+ }
+ };
+}
+
+impl CubebServer {
+ pub fn new(
+ callback_thread: ipccore::EventLoopHandle,
+ device_collection_thread: ipccore::EventLoopHandle,
+ shm_area_size: usize,
+ ) -> Self {
+ CubebServer {
+ callback_thread,
+ device_collection_thread,
+ streams: slab::Slab::<ServerStream>::new(),
+ remote_pid: None,
+ device_collection_change_callbacks: None,
+ devidmap: DevIdMap::new(),
+ shm_area_size,
+ }
+ }
+
+ // Process a request coming from the client.
+ fn process_msg(
+ &mut self,
+ context: &cubeb::Context,
+ manager: &mut CubebDeviceCollectionManager,
+ msg: &ServerMessage,
+ ) -> ClientMessage {
+ let resp: ClientMessage = match *msg {
+ ServerMessage::ClientConnect(_) => {
+ // remote_pid is set before cubeb initialization, just verify here.
+ assert!(self.remote_pid.is_some());
+ ClientMessage::ClientConnected
+ }
+
+ ServerMessage::ClientDisconnect => {
+ // TODO:
+ //self.connection.client_disconnect();
+ ClientMessage::ClientDisconnected
+ }
+
+ ServerMessage::ContextGetBackendId => {
+ ClientMessage::ContextBackendId(context.backend_id().to_string())
+ }
+
+ ServerMessage::ContextGetMaxChannelCount => context
+ .max_channel_count()
+ .map(ClientMessage::ContextMaxChannelCount)
+ .unwrap_or_else(error),
+
+ ServerMessage::ContextGetMinLatency(ref params) => {
+ let format = cubeb::SampleFormat::from(params.format);
+ let layout = cubeb::ChannelLayout::from(params.layout);
+
+ let params = cubeb::StreamParamsBuilder::new()
+ .format(format)
+ .rate(params.rate)
+ .channels(params.channels)
+ .layout(layout)
+ .take();
+
+ context
+ .min_latency(&params)
+ .map(ClientMessage::ContextMinLatency)
+ .unwrap_or_else(error)
+ }
+
+ ServerMessage::ContextGetPreferredSampleRate => context
+ .preferred_sample_rate()
+ .map(ClientMessage::ContextPreferredSampleRate)
+ .unwrap_or_else(error),
+
+ ServerMessage::ContextGetDeviceEnumeration(device_type) => context
+ .enumerate_devices(cubeb::DeviceType::from_bits_truncate(device_type))
+ .map(|devices| {
+ let v: Vec<DeviceInfo> = devices
+ .iter()
+ .map(|i| {
+ let mut tmp: DeviceInfo = i.as_ref().into();
+ // Replace each cubeb_devid with a unique handle suitable for IPC.
+ tmp.devid = self.devidmap.make_handle(tmp.devid);
+ tmp
+ })
+ .collect();
+ ClientMessage::ContextEnumeratedDevices(v)
+ })
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamCreate(ref params) => self
+ .process_stream_create(params)
+ .unwrap_or_else(|_| error(cubeb::Error::error())),
+
+ ServerMessage::StreamInit(stm_tok, ref params) => self
+ .process_stream_init(context, stm_tok, params)
+ .unwrap_or_else(|_| error(cubeb::Error::error())),
+
+ ServerMessage::StreamDestroy(stm_tok) => {
+ if self.streams.contains(stm_tok) {
+ debug!("Unregistering stream {:?}", stm_tok);
+ self.streams.remove(stm_tok);
+ } else {
+ // Debugging for BMO 1594216/1612044.
+ error!("StreamDestroy({}): invalid token", stm_tok);
+ return error(cubeb::Error::invalid_parameter());
+ }
+ ClientMessage::StreamDestroyed
+ }
+
+ ServerMessage::StreamStart(stm_tok) => try_stream!(self, stm_tok)
+ .start()
+ .map(|_| ClientMessage::StreamStarted)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamStop(stm_tok) => try_stream!(self, stm_tok)
+ .stop()
+ .map(|_| ClientMessage::StreamStopped)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetPosition(stm_tok) => try_stream!(self, stm_tok)
+ .position()
+ .map(ClientMessage::StreamPosition)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetLatency(stm_tok) => try_stream!(self, stm_tok)
+ .latency()
+ .map(ClientMessage::StreamLatency)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetInputLatency(stm_tok) => try_stream!(self, stm_tok)
+ .input_latency()
+ .map(ClientMessage::StreamInputLatency)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamSetVolume(stm_tok, volume) => try_stream!(self, stm_tok)
+ .set_volume(volume)
+ .map(|_| ClientMessage::StreamVolumeSet)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamSetName(stm_tok, ref name) => try_stream!(self, stm_tok)
+ .set_name(name)
+ .map(|_| ClientMessage::StreamNameSet)
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamGetCurrentDevice(stm_tok) => try_stream!(self, stm_tok)
+ .current_device()
+ .map(|device| ClientMessage::StreamCurrentDevice(Device::from(device)))
+ .unwrap_or_else(error),
+
+ ServerMessage::StreamRegisterDeviceChangeCallback(stm_tok, enable) => {
+ try_stream!(self, stm_tok)
+ .register_device_changed_callback(if enable {
+ Some(device_change_cb_c)
+ } else {
+ None
+ })
+ .map(|_| ClientMessage::StreamRegisterDeviceChangeCallback)
+ .unwrap_or_else(error)
+ }
+
+ ServerMessage::ContextSetupDeviceCollectionCallback => {
+ let (server_pipe, client_pipe) = match sys::make_pipe_pair() {
+ Ok((server_pipe, client_pipe)) => (server_pipe, client_pipe),
+ Err(e) => {
+ debug!(
+ "ContextSetupDeviceCollectionCallback - make_pipe_pair failed: {:?}",
+ e
+ );
+ return error(cubeb::Error::error());
+ }
+ };
+
+ // TODO: this should bind the client_pipe and send the server_pipe to the remote, but
+ // additional work is required as it's not possible to convert a Windows sys::Pipe into a raw handle.
+ // TODO: Use the rpc_thread instead of an extra device_collection_thread, but a reentrant bind_client
+ // is required to support that.
+ let rpc = match self
+ .device_collection_thread
+ .bind_client::<DeviceCollectionClient>(server_pipe)
+ {
+ Ok(rpc) => rpc,
+ Err(e) => {
+ debug!(
+ "ContextSetupDeviceCollectionCallback - bind_client: {:?}",
+ e
+ );
+ return error(cubeb::Error::error());
+ }
+ };
+
+ self.device_collection_change_callbacks =
+ Some(Rc::new(DeviceCollectionChangeCallback { rpc }));
+ let fd = RegisterDeviceCollectionChanged {
+ platform_handle: SerializableHandle::new(client_pipe, self.remote_pid.unwrap()),
+ };
+
+ ClientMessage::ContextSetupDeviceCollectionCallback(fd)
+ }
+
+ ServerMessage::ContextRegisterDeviceCollectionChanged(device_type, enable) => self
+ .process_register_device_collection_changed(
+ context,
+ manager,
+ cubeb::DeviceType::from_bits_truncate(device_type),
+ enable,
+ )
+ .unwrap_or_else(error),
+
+ #[cfg(target_os = "linux")]
+ ServerMessage::PromoteThreadToRealTime(thread_info) => {
+ let info = RtPriorityThreadInfo::deserialize(thread_info);
+ match promote_thread_to_real_time(info, 256, 48000) {
+ Ok(_) => {
+ info!("Promotion of content process thread to real-time OK");
+ }
+ Err(_) => {
+ warn!("Promotion of content process thread to real-time error");
+ }
+ }
+ ClientMessage::ThreadPromoted
+ }
+ };
+
+ trace!("process_msg: req={:?}, resp={:?}", msg, resp);
+
+ resp
+ }
+
+ fn process_register_device_collection_changed(
+ &mut self,
+ context: &cubeb::Context,
+ manager: &mut CubebDeviceCollectionManager,
+ devtype: cubeb::DeviceType,
+ enable: bool,
+ ) -> cubeb::Result<ClientMessage> {
+ if devtype == cubeb::DeviceType::UNKNOWN {
+ return Err(cubeb::Error::invalid_parameter());
+ }
+
+ assert!(self.device_collection_change_callbacks.is_some());
+ let cbs = self.device_collection_change_callbacks.as_ref().unwrap();
+
+ if enable {
+ manager.register(context, cbs, devtype)
+ } else {
+ manager.unregister(context, cbs, devtype)
+ }
+ .map(|_| ClientMessage::ContextRegisteredDeviceCollectionChanged)
+ }
+
+ // Stream create is special, so it's been separated from process_msg.
+ fn process_stream_create(&mut self, params: &StreamCreateParams) -> Result<ClientMessage> {
+ fn frame_size_in_bytes(params: Option<&StreamParams>) -> u16 {
+ params
+ .map(|p| {
+ let format = p.format.into();
+ let sample_size = match format {
+ cubeb::SampleFormat::S16LE
+ | cubeb::SampleFormat::S16BE
+ | cubeb::SampleFormat::S16NE => 2,
+ cubeb::SampleFormat::Float32LE
+ | cubeb::SampleFormat::Float32BE
+ | cubeb::SampleFormat::Float32NE => 4,
+ };
+ let channel_count = p.channels as u16;
+ sample_size * channel_count
+ })
+ .unwrap_or(0u16)
+ }
+
+ // Create the callback handling struct which is attached the cubeb stream.
+ let input_frame_size = frame_size_in_bytes(params.input_stream_params.as_ref());
+ let output_frame_size = frame_size_in_bytes(params.output_stream_params.as_ref());
+
+ // Estimate a safe shmem size for this stream configuration. If the server was configured with a fixed
+ // shm_area_size override, use that instead.
+ // TODO: Add a new cubeb API to query the precise buffer size required for a given stream config.
+ // https://github.com/mozilla/audioipc-2/issues/124
+ let shm_area_size = if self.shm_area_size == 0 {
+ let frame_size = output_frame_size.max(input_frame_size) as u32;
+ let in_rate = params.input_stream_params.map(|p| p.rate).unwrap_or(0);
+ let out_rate = params.output_stream_params.map(|p| p.rate).unwrap_or(0);
+ let rate = out_rate.max(in_rate);
+ // 1s of audio, rounded up to the nearest 64kB.
+ // Stream latency is capped at 1s in process_stream_init.
+ (((rate * frame_size) + 0xffff) & !0xffff) as usize
+ } else {
+ self.shm_area_size
+ };
+ debug!("shm_area_size = {}", shm_area_size);
+
+ let shm = SharedMem::new(&get_shm_id(), shm_area_size)?;
+ let shm_handle = unsafe { shm.make_handle()? };
+
+ let (server_pipe, client_pipe) = sys::make_pipe_pair()?;
+ // TODO: this should bind the client_pipe and send the server_pipe to the remote, but
+ // additional work is required as it's not possible to convert a Windows sys::Pipe into a raw handle.
+ let rpc = self
+ .callback_thread
+ .bind_client::<CallbackClient>(server_pipe)?;
+
+ let cbs = Box::new(ServerStreamCallbacks {
+ input_frame_size,
+ output_frame_size,
+ shm,
+ state_callback_rpc: rpc.try_clone()?,
+ device_change_callback_rpc: rpc.try_clone()?,
+ data_callback_rpc: rpc,
+ });
+
+ let entry = self.streams.vacant_entry();
+ let key = entry.key();
+ debug!("Registering stream {:?}", key);
+
+ entry.insert(ServerStream {
+ stream: None,
+ cbs,
+ client_pipe: Some(client_pipe),
+ });
+
+ Ok(ClientMessage::StreamCreated(StreamCreate {
+ token: key,
+ shm_handle: SerializableHandle::new(shm_handle, self.remote_pid.unwrap()),
+ shm_area_size,
+ }))
+ }
+
+ // Stream init is special, so it's been separated from process_msg.
+ fn process_stream_init(
+ &mut self,
+ context: &cubeb::Context,
+ stm_tok: usize,
+ params: &StreamInitParams,
+ ) -> Result<ClientMessage> {
+ // Create cubeb stream from params
+ let stream_name = params
+ .stream_name
+ .as_ref()
+ .and_then(|name| CStr::from_bytes_with_nul(name).ok());
+
+ // Map IPC handle back to cubeb_devid.
+ let input_device = self.devidmap.handle_to_id(params.input_device) as *const _;
+ let input_stream_params = params.input_stream_params.as_ref().map(|isp| unsafe {
+ cubeb::StreamParamsRef::from_ptr(isp as *const StreamParams as *mut _)
+ });
+
+ // Map IPC handle back to cubeb_devid.
+ let output_device = self.devidmap.handle_to_id(params.output_device) as *const _;
+ let output_stream_params = params.output_stream_params.as_ref().map(|osp| unsafe {
+ cubeb::StreamParamsRef::from_ptr(osp as *const StreamParams as *mut _)
+ });
+
+ // TODO: Manage stream latency requests with respect to the RT deadlines the callback_thread was configured for.
+ fn round_up_pow2(v: u32) -> u32 {
+ debug_assert!(v >= 1);
+ 1 << (32 - (v - 1).leading_zeros())
+ }
+ let rate = params
+ .output_stream_params
+ .map(|p| p.rate)
+ .unwrap_or_else(|| params.input_stream_params.map(|p| p.rate).unwrap());
+ // Note: minimum latency supported by AudioIPC is currently ~5ms. This restriction may be reduced by later IPC improvements.
+ let min_latency = round_up_pow2(5 * rate / 1000);
+ // Note: maximum latency is restricted by the SharedMem size.
+ let max_latency = rate;
+ let latency = params.latency_frames.max(min_latency).min(max_latency);
+ trace!(
+ "stream rate={} latency requested={} calculated={}",
+ rate,
+ params.latency_frames,
+ latency
+ );
+
+ let server_stream = &mut self.streams[stm_tok];
+ assert!(size_of::<Box<ServerStreamCallbacks>>() == size_of::<usize>());
+ let user_ptr = server_stream.cbs.as_ref() as *const ServerStreamCallbacks as *mut c_void;
+
+ let stream = unsafe {
+ let stream = context.stream_init(
+ stream_name,
+ input_device,
+ input_stream_params,
+ output_device,
+ output_stream_params,
+ latency,
+ Some(data_cb_c),
+ Some(state_cb_c),
+ user_ptr,
+ );
+ match stream {
+ Ok(stream) => stream,
+ Err(e) => {
+ debug!("Unregistering stream {:?} (stream error {:?})", stm_tok, e);
+ self.streams.remove(stm_tok);
+ return Err(e.into());
+ }
+ }
+ };
+
+ server_stream.stream = Some(stream);
+
+ let client_pipe = server_stream
+ .client_pipe
+ .take()
+ .expect("invalid state after StreamCreated");
+ Ok(ClientMessage::StreamInitialized(SerializableHandle::new(
+ client_pipe,
+ self.remote_pid.unwrap(),
+ )))
+ }
+}
+
+// C callable callbacks
+unsafe extern "C" fn data_cb_c(
+ _: *mut ffi::cubeb_stream,
+ user_ptr: *mut c_void,
+ input_buffer: *const c_void,
+ output_buffer: *mut c_void,
+ nframes: c_long,
+) -> c_long {
+ let ok = panic::catch_unwind(|| {
+ let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
+ let input = if input_buffer.is_null() {
+ &[]
+ } else {
+ let nbytes = nframes * c_long::from(cbs.input_frame_size);
+ slice::from_raw_parts(input_buffer as *const u8, nbytes as usize)
+ };
+ let output: &mut [u8] = if output_buffer.is_null() {
+ &mut []
+ } else {
+ let nbytes = nframes * c_long::from(cbs.output_frame_size);
+ slice::from_raw_parts_mut(output_buffer as *mut u8, nbytes as usize)
+ };
+ cbs.data_callback(input, output, nframes as isize) as c_long
+ });
+ ok.unwrap_or(cubeb::ffi::CUBEB_ERROR as c_long)
+}
+
+unsafe extern "C" fn state_cb_c(
+ _: *mut ffi::cubeb_stream,
+ user_ptr: *mut c_void,
+ state: ffi::cubeb_state,
+) {
+ let ok = panic::catch_unwind(|| {
+ let state = cubeb::State::from(state);
+ let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
+ cbs.state_callback(state);
+ });
+ ok.expect("State callback panicked");
+}
+
+unsafe extern "C" fn device_change_cb_c(user_ptr: *mut c_void) {
+ let ok = panic::catch_unwind(|| {
+ let cbs = &mut *(user_ptr as *mut ServerStreamCallbacks);
+ cbs.device_change_callback();
+ });
+ ok.expect("Device change callback panicked");
+}
+
+unsafe extern "C" fn device_collection_changed_input_cb_c(
+ _: *mut ffi::cubeb,
+ user_ptr: *mut c_void,
+) {
+ let ok = panic::catch_unwind(|| {
+ let manager = &mut *(user_ptr as *mut CubebDeviceCollectionManager);
+ manager.device_collection_changed_callback(ffi::CUBEB_DEVICE_TYPE_INPUT);
+ });
+ ok.expect("Collection changed (input) callback panicked");
+}
+
+unsafe extern "C" fn device_collection_changed_output_cb_c(
+ _: *mut ffi::cubeb,
+ user_ptr: *mut c_void,
+) {
+ let ok = panic::catch_unwind(|| {
+ let manager = &mut *(user_ptr as *mut CubebDeviceCollectionManager);
+ manager.device_collection_changed_callback(ffi::CUBEB_DEVICE_TYPE_OUTPUT);
+ });
+ ok.expect("Collection changed (output) callback panicked");
+}
diff --git a/third_party/rust/audioipc2/.cargo-checksum.json b/third_party/rust/audioipc2/.cargo-checksum.json
new file mode 100644
index 0000000000..2847888023
--- /dev/null
+++ b/third_party/rust/audioipc2/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"8b2d3abbe023360a24d37b306dbec9e8bd0162025d38ca106ebcc8d7abab4039","benches/serialization.rs":"d56855d868dab6aa22c8b03a61084535351b76c94b68d8b1d20764e352fe473f","build.rs":"65df9a97c6cdaa3faf72581f04ac289197b0b1797d69d22c1796e957ff1089e2","src/codec.rs":"4e029396765db803201249e90bcf724eb56deed3b2e455822d6673f40550a3e1","src/errors.rs":"67a4a994d0724397657581cde153bdfc05ce86e7efc467f23fafc8f64df80fa4","src/ipccore.rs":"be36c9d927a4ea35869163f2a30be77366acfffadb7f67f550b3b9c53b23a201","src/lib.rs":"9b107cb52081eeea3fa742d30361db70f7138baa423dfe21d37dcf5087afc338","src/messages.rs":"452362da2cace9a0f2e3134c190ecb6a9997f8be4036cde06643e17c6c238240","src/rpccore.rs":"9b7199c0e31941c399934c4442e2169fb85c6067810c37879d1c9614ce00bdf5","src/shm.rs":"1d88f19606899e3e477865d6b84bbe3e272f51618a1c2d57b6dab03a4787cde3","src/sys/mod.rs":"e6fa1d260abf093e1f7b50185195e2d3aee0eb8c9774c6f253953b5896d838f3","src/sys/unix/cmsg.rs":"8a27a20383c333c5d033e58a546a530e26b964942a4615793d1ca078c65efb75","src/sys/unix/cmsghdr.c":"d7344b3dc15cdce410c68669b848bb81f7fe36362cd3699668cb613fa05180f8","src/sys/unix/mod.rs":"59835f0d5509940078b1820a54f49fc5514adeb3e45e7d21e3ab917431da2e74","src/sys/unix/msg.rs":"c0103cc058aeb890ab7aa023fcd6d3b9a0135d6b28fdecdec446650957210508","src/sys/windows/mod.rs":"7b1288e42b3ce34c7004b9fe3eeb6d9822c55e2688d3c2a40e55db46a2ca5d76"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/audioipc2/Cargo.toml b/third_party/rust/audioipc2/Cargo.toml
new file mode 100644
index 0000000000..17ef5b94e8
--- /dev/null
+++ b/third_party/rust/audioipc2/Cargo.toml
@@ -0,0 +1,55 @@
+[package]
+name = "audioipc2"
+version = "0.5.0"
+authors = [
+ "Matthew Gregan <kinetik@flim.org>",
+ "Dan Glastonbury <dan.glastonbury@gmail.com>"
+ ]
+license = "ISC"
+description = "Remote Cubeb IPC"
+edition = "2018"
+
+[dependencies]
+bincode = "1.3"
+byteorder = "1"
+bytes = "1"
+cubeb = "0.10"
+log = "0.4"
+serde = "1"
+serde_derive = "1"
+serde_bytes = "0.11"
+mio = { version = "0.8", features = ["os-poll", "net", "os-ext"] }
+slab = "0.4"
+scopeguard = "1.1.0"
+crossbeam-channel = "0.5"
+
+[target.'cfg(unix)'.dependencies]
+iovec = "0.1"
+libc = "0.2"
+memmap2 = "0.5"
+arrayvec = "0.7"
+
+[target.'cfg(target_os = "linux")'.dependencies.audio_thread_priority]
+version = "0.26.1"
+default-features = false
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "0.3", features = ["combaseapi", "handleapi", "memoryapi", "objbase"] }
+
+[target.'cfg(target_os = "android")'.dependencies]
+ashmem = "0.1.2"
+
+[dependencies.error-chain]
+version = "0.12.0"
+default-features = false
+
+[build-dependencies]
+cc = "1.0"
+
+[dev-dependencies]
+env_logger = "0.9"
+criterion = { version = "0.3", features = ["html_reports"] }
+
+[[bench]]
+name = "serialization"
+harness = false
diff --git a/third_party/rust/audioipc2/benches/serialization.rs b/third_party/rust/audioipc2/benches/serialization.rs
new file mode 100644
index 0000000000..39f770a939
--- /dev/null
+++ b/third_party/rust/audioipc2/benches/serialization.rs
@@ -0,0 +1,93 @@
+use audioipc::codec::{Codec, LengthDelimitedCodec};
+use audioipc::messages::DeviceInfo;
+use audioipc::ClientMessage;
+use audioipc2 as audioipc;
+use bytes::BytesMut;
+use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
+
+fn bench(c: &mut Criterion, name: &str, msg: impl Fn() -> ClientMessage) {
+ let mut codec: LengthDelimitedCodec<ClientMessage, ClientMessage> =
+ LengthDelimitedCodec::default();
+ let mut buf = BytesMut::with_capacity(8192);
+ c.bench_function(&format!("encode/{}", name), |b| {
+ b.iter_batched(
+ || msg(),
+ |msg| {
+ codec.encode(msg, &mut buf).unwrap();
+ buf.clear();
+ },
+ BatchSize::SmallInput,
+ )
+ });
+
+ let mut codec: LengthDelimitedCodec<ClientMessage, ClientMessage> =
+ LengthDelimitedCodec::default();
+ let mut buf = BytesMut::with_capacity(8192);
+ codec.encode(msg(), &mut buf).unwrap();
+ c.bench_function(&format!("decode/{}", name), |b| {
+ b.iter_batched_ref(
+ || buf.clone(),
+ |buf| {
+ codec.decode(buf).unwrap().unwrap();
+ },
+ BatchSize::SmallInput,
+ )
+ });
+
+ let mut codec: LengthDelimitedCodec<ClientMessage, ClientMessage> =
+ LengthDelimitedCodec::default();
+ let mut buf = BytesMut::with_capacity(8192);
+ c.bench_function(&format!("roundtrip/{}", name), |b| {
+ b.iter_batched(
+ || msg(),
+ |msg| {
+ codec.encode(msg, &mut buf).unwrap();
+ codec.decode(&mut buf).unwrap().unwrap();
+ },
+ BatchSize::SmallInput,
+ )
+ });
+}
+
+pub fn criterion_benchmark(c: &mut Criterion) {
+ bench(c, "tiny", || ClientMessage::ClientConnected);
+ bench(c, "small", || ClientMessage::StreamPosition(0));
+ bench(c, "medium", || {
+ ClientMessage::ContextEnumeratedDevices(make_device_vec(2))
+ });
+ bench(c, "large", || {
+ ClientMessage::ContextEnumeratedDevices(make_device_vec(20))
+ });
+ bench(c, "huge", || {
+ ClientMessage::ContextEnumeratedDevices(make_device_vec(128))
+ });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
+
+fn make_device_vec(n: usize) -> Vec<DeviceInfo> {
+ let mut devices = Vec::with_capacity(n);
+ for i in 0..n {
+ let device = DeviceInfo {
+ devid: i,
+ device_id: Some(vec![0u8; 64]),
+ friendly_name: Some(vec![0u8; 64]),
+ group_id: Some(vec![0u8; 64]),
+ vendor_name: Some(vec![0u8; 64]),
+ device_type: 0,
+ state: 0,
+ preferred: 0,
+ format: 0,
+ default_format: 0,
+ max_channels: 0,
+ default_rate: 0,
+ max_rate: 0,
+ min_rate: 0,
+ latency_lo: 0,
+ latency_hi: 0,
+ };
+ devices.push(device);
+ }
+ devices
+}
diff --git a/third_party/rust/audioipc2/build.rs b/third_party/rust/audioipc2/build.rs
new file mode 100644
index 0000000000..453dafad96
--- /dev/null
+++ b/third_party/rust/audioipc2/build.rs
@@ -0,0 +1,7 @@
+fn main() {
+ if std::env::var_os("CARGO_CFG_UNIX").is_some() {
+ cc::Build::new()
+ .file("src/sys/unix/cmsghdr.c")
+ .compile("cmsghdr");
+ }
+}
diff --git a/third_party/rust/audioipc2/src/codec.rs b/third_party/rust/audioipc2/src/codec.rs
new file mode 100644
index 0000000000..ada79a7632
--- /dev/null
+++ b/third_party/rust/audioipc2/src/codec.rs
@@ -0,0 +1,198 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+//! `Encoder`s and `Decoder`s from items to/from `BytesMut` buffers.
+
+use bincode::{self, Options};
+use byteorder::{ByteOrder, LittleEndian};
+use bytes::{Buf, BufMut, BytesMut};
+use serde::de::DeserializeOwned;
+use serde::ser::Serialize;
+use std::convert::TryInto;
+use std::fmt::Debug;
+use std::io;
+use std::marker::PhantomData;
+use std::mem::size_of;
+
+////////////////////////////////////////////////////////////////////////////////
+// Split buffer into size delimited frames - This appears more complicated than
+// might be necessary due to handling the possibility of messages being split
+// across reads.
+
+pub trait Codec {
+ /// The type of items to be encoded into byte buffer
+ type In;
+
+ /// The type of items to be returned by decoding from byte buffer
+ type Out;
+
+ /// Attempts to decode a frame from the provided buffer of bytes.
+ fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Out>>;
+
+ /// A default method available to be called when there are no more bytes
+ /// available to be read from the I/O.
+ fn decode_eof(&mut self, buf: &mut BytesMut) -> io::Result<Self::Out> {
+ match self.decode(buf)? {
+ Some(frame) => Ok(frame),
+ None => Err(io::Error::new(
+ io::ErrorKind::Other,
+ "bytes remaining on stream",
+ )),
+ }
+ }
+
+ /// Encodes a frame into the buffer provided.
+ fn encode(&mut self, msg: Self::In, buf: &mut BytesMut) -> io::Result<()>;
+}
+
+/// Codec based upon bincode serialization
+///
+/// Messages that have been serialized using bincode are prefixed with
+/// the length of the message to aid in deserialization, so that it's
+/// known if enough data has been received to decode a complete
+/// message.
+pub struct LengthDelimitedCodec<In, Out> {
+ state: State,
+ encode_buf: Vec<u8>,
+ __in: PhantomData<In>,
+ __out: PhantomData<Out>,
+}
+
+enum State {
+ Length,
+ Data(u32),
+}
+
+const MAX_MESSAGE_LEN: u32 = 1024 * 1024;
+const MAGIC: u64 = 0xa4d1_019c_c910_1d4a;
+const HEADER_LEN: usize = size_of::<u32>() + size_of::<u64>();
+
+impl<In, Out> Default for LengthDelimitedCodec<In, Out> {
+ fn default() -> Self {
+ Self {
+ state: State::Length,
+ encode_buf: Vec::with_capacity(crate::ipccore::IPC_CLIENT_BUFFER_SIZE),
+ __in: PhantomData,
+ __out: PhantomData,
+ }
+ }
+}
+
+impl<In, Out> LengthDelimitedCodec<In, Out> {
+ // Lengths are encoded as little endian u32
+ fn decode_length(buf: &mut BytesMut) -> Option<u32> {
+ if buf.len() < HEADER_LEN {
+ // Not enough data
+ return None;
+ }
+
+ let magic = LittleEndian::read_u64(&buf[0..8]);
+ assert_eq!(magic, MAGIC);
+
+ // Consume the length field
+ let n = LittleEndian::read_u32(&buf[8..12]);
+ buf.advance(HEADER_LEN);
+ Some(n)
+ }
+
+ fn decode_data(buf: &mut BytesMut, n: u32) -> io::Result<Option<Out>>
+ where
+ Out: DeserializeOwned + Debug,
+ {
+ let n = n.try_into().unwrap();
+
+ // At this point, the buffer has already had the required capacity
+ // reserved. All there is to do is read.
+ if buf.len() < n {
+ return Ok(None);
+ }
+
+ trace!("Attempting to decode");
+ let msg = bincode::options()
+ .with_limit(MAX_MESSAGE_LEN as u64)
+ .deserialize::<Out>(&buf[..n])
+ .map_err(|e| match *e {
+ bincode::ErrorKind::Io(e) => e,
+ _ => io::Error::new(io::ErrorKind::Other, *e),
+ })?;
+ buf.advance(n);
+
+ trace!("... Decoded {:?}", msg);
+ Ok(Some(msg))
+ }
+}
+
+impl<In, Out> Codec for LengthDelimitedCodec<In, Out>
+where
+ In: Serialize + Debug,
+ Out: DeserializeOwned + Debug,
+{
+ type In = In;
+ type Out = Out;
+
+ fn decode(&mut self, buf: &mut BytesMut) -> io::Result<Option<Self::Out>> {
+ let n = match self.state {
+ State::Length => {
+ match Self::decode_length(buf) {
+ Some(n) => {
+ assert!(
+ n <= MAX_MESSAGE_LEN,
+ "assertion failed: {} <= {}",
+ n,
+ MAX_MESSAGE_LEN
+ );
+ self.state = State::Data(n);
+
+ // Ensure that the buffer has enough space to read the
+ // incoming payload
+ buf.reserve(n.try_into().unwrap());
+
+ n
+ }
+ None => return Ok(None),
+ }
+ }
+ State::Data(n) => n,
+ };
+
+ match Self::decode_data(buf, n)? {
+ Some(data) => {
+ // Update the decode state
+ self.state = State::Length;
+
+ // Make sure the buffer has enough space to read the next length header.
+ buf.reserve(HEADER_LEN);
+
+ Ok(Some(data))
+ }
+ None => Ok(None),
+ }
+ }
+
+ fn encode(&mut self, item: Self::In, buf: &mut BytesMut) -> io::Result<()> {
+ trace!("Attempting to encode");
+
+ self.encode_buf.clear();
+ if let Err(e) = bincode::options()
+ .with_limit(MAX_MESSAGE_LEN as u64)
+ .serialize_into::<_, Self::In>(&mut self.encode_buf, &item)
+ {
+ trace!("message encode failed: {:?}", *e);
+ match *e {
+ bincode::ErrorKind::Io(e) => return Err(e),
+ _ => return Err(io::Error::new(io::ErrorKind::Other, *e)),
+ }
+ }
+
+ let encoded_len = self.encode_buf.len();
+ assert!(encoded_len <= MAX_MESSAGE_LEN as usize);
+ buf.reserve(encoded_len + HEADER_LEN);
+ buf.put_u64_le(MAGIC);
+ buf.put_u32_le(encoded_len.try_into().unwrap());
+ buf.extend_from_slice(&self.encode_buf);
+
+ Ok(())
+ }
+}
diff --git a/third_party/rust/audioipc2/src/errors.rs b/third_party/rust/audioipc2/src/errors.rs
new file mode 100644
index 0000000000..f4f3e32f5f
--- /dev/null
+++ b/third_party/rust/audioipc2/src/errors.rs
@@ -0,0 +1,18 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+error_chain! {
+ // Maybe replace with chain_err to improve the error info.
+ foreign_links {
+ Bincode(bincode::Error);
+ Io(std::io::Error);
+ Cubeb(cubeb::Error);
+ }
+
+ // Replace bail!(str) with explicit errors.
+ errors {
+ Disconnected
+ }
+}
diff --git a/third_party/rust/audioipc2/src/ipccore.rs b/third_party/rust/audioipc2/src/ipccore.rs
new file mode 100644
index 0000000000..3f31faa021
--- /dev/null
+++ b/third_party/rust/audioipc2/src/ipccore.rs
@@ -0,0 +1,917 @@
+// Copyright © 2021 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use std::io::{self, Result};
+use std::sync::{mpsc, Arc};
+use std::thread;
+
+use crossbeam_channel::{self, Receiver, Sender};
+use mio::{event::Event, Events, Interest, Poll, Registry, Token, Waker};
+use slab::Slab;
+
+use crate::messages::AssociateHandleForMessage;
+use crate::rpccore::{make_client, make_server, Client, Handler, Proxy, Server};
+use crate::{
+ codec::Codec,
+ codec::LengthDelimitedCodec,
+ sys::{self, RecvMsg, SendMsg},
+};
+
+use serde::{de::DeserializeOwned, Serialize};
+use std::fmt::Debug;
+
+const WAKE_TOKEN: Token = Token(!0);
+
+thread_local!(static IN_EVENTLOOP: std::cell::RefCell<Option<thread::ThreadId>> = std::cell::RefCell::new(None));
+
+fn assert_not_in_event_loop_thread() {
+ IN_EVENTLOOP.with(|b| {
+ assert_ne!(*b.borrow(), Some(thread::current().id()));
+ });
+}
+
+// Requests sent by an EventLoopHandle to be serviced by
+// the handle's associated EventLoop.
+enum Request {
+ // See EventLoop::add_connection
+ AddConnection(
+ sys::Pipe,
+ Box<dyn Driver + Send>,
+ mpsc::Sender<Result<Token>>,
+ ),
+ // See EventLoop::shutdown
+ Shutdown,
+ // See EventLoop::wake_connection
+ WakeConnection(Token),
+}
+
+// EventLoopHandle is a cloneable external reference
+// to a running EventLoop, allowing registration of
+// new client and server connections, in addition to
+// requesting the EventLoop shut down cleanly.
+#[derive(Clone, Debug)]
+pub struct EventLoopHandle {
+ waker: Arc<Waker>,
+ requests_tx: Sender<Request>,
+}
+
+impl EventLoopHandle {
+ pub fn bind_client<C: Client + 'static>(
+ &self,
+ connection: sys::Pipe,
+ ) -> Result<Proxy<<C as Client>::ServerMessage, <C as Client>::ClientMessage>>
+ where
+ <C as Client>::ServerMessage: Serialize + Debug + AssociateHandleForMessage + Send,
+ <C as Client>::ClientMessage: DeserializeOwned + Debug + AssociateHandleForMessage + Send,
+ {
+ let (handler, mut proxy) = make_client::<C>()?;
+ let driver = Box::new(FramedDriver::new(handler));
+ let r = self.add_connection(connection, driver);
+ trace!("EventLoop::bind_client {:?}", r);
+ r.map(|token| {
+ proxy.connect_event_loop(self.clone(), token);
+ proxy
+ })
+ }
+
+ pub fn bind_server<S: Server + Send + 'static>(
+ &self,
+ server: S,
+ connection: sys::Pipe,
+ ) -> Result<()>
+ where
+ <S as Server>::ServerMessage: DeserializeOwned + Debug + AssociateHandleForMessage + Send,
+ <S as Server>::ClientMessage: Serialize + Debug + AssociateHandleForMessage + Send,
+ {
+ let handler = make_server::<S>(server);
+ let driver = Box::new(FramedDriver::new(handler));
+ let r = self.add_connection(connection, driver);
+ trace!("EventLoop::bind_server {:?}", r);
+ r.map(|_| ())
+ }
+
+ // Register a new connection with associated driver on the EventLoop.
+ // TODO: Since this is called from a Gecko main thread, make this non-blocking wrt. the EventLoop.
+ fn add_connection(
+ &self,
+ connection: sys::Pipe,
+ driver: Box<dyn Driver + Send>,
+ ) -> Result<Token> {
+ assert_not_in_event_loop_thread();
+ let (tx, rx) = mpsc::channel();
+ self.requests_tx
+ .send(Request::AddConnection(connection, driver, tx))
+ .map_err(|_| {
+ debug!("EventLoopHandle::add_connection send failed");
+ io::ErrorKind::ConnectionAborted
+ })?;
+ self.waker.wake()?;
+ rx.recv().map_err(|_| {
+ debug!("EventLoopHandle::add_connection recv failed");
+ io::ErrorKind::ConnectionAborted
+ })?
+ }
+
+ // Signal EventLoop to shutdown. Causes EventLoop::poll to return Ok(false).
+ fn shutdown(&self) -> Result<()> {
+ self.requests_tx.send(Request::Shutdown).map_err(|_| {
+ debug!("EventLoopHandle::shutdown send failed");
+ io::ErrorKind::ConnectionAborted
+ })?;
+ self.waker.wake()
+ }
+
+ // Signal EventLoop to wake connection specified by `token` for processing.
+ pub(crate) fn wake_connection(&self, token: Token) {
+ match self.requests_tx.send(Request::WakeConnection(token)) {
+ Ok(_) => self.waker.wake().expect("wake failed"),
+ Err(e) => debug!("EventLoopHandle::wake_connection failed: {:?}", e),
+ }
+ }
+}
+
+// EventLoop owns all registered connections, and is responsible for calling each connection's
+// `handle_event` or `handle_wake` any time a readiness or wake event associated with that connection is
+// produced.
+struct EventLoop {
+ poll: Poll,
+ events: Events,
+ waker: Arc<Waker>,
+ name: String,
+ connections: Slab<Connection>,
+ requests_rx: Receiver<Request>,
+ requests_tx: Sender<Request>,
+}
+
+const EVENT_LOOP_INITIAL_CLIENTS: usize = 64; // Initial client allocation, exceeding this will cause the connection slab to grow.
+const EVENT_LOOP_EVENTS_PER_ITERATION: usize = 256; // Number of events per poll() step, arbitrary limit.
+
+impl EventLoop {
+ fn new(name: String) -> Result<EventLoop> {
+ let poll = Poll::new()?;
+ let waker = Arc::new(Waker::new(poll.registry(), WAKE_TOKEN)?);
+ let (tx, rx) = crossbeam_channel::bounded(EVENT_LOOP_INITIAL_CLIENTS);
+ let eventloop = EventLoop {
+ poll,
+ events: Events::with_capacity(EVENT_LOOP_EVENTS_PER_ITERATION),
+ waker,
+ name,
+ connections: Slab::with_capacity(EVENT_LOOP_INITIAL_CLIENTS),
+ requests_rx: rx,
+ requests_tx: tx,
+ };
+
+ Ok(eventloop)
+ }
+
+ // Return a cloneable handle for controlling the EventLoop externally.
+ fn handle(&mut self) -> EventLoopHandle {
+ EventLoopHandle {
+ waker: self.waker.clone(),
+ requests_tx: self.requests_tx.clone(),
+ }
+ }
+
+ // Register a connection and driver.
+ fn add_connection(
+ &mut self,
+ connection: sys::Pipe,
+ driver: Box<dyn Driver + Send>,
+ ) -> Result<Token> {
+ if self.connections.len() == self.connections.capacity() {
+ trace!("{}: connection slab full, insert will allocate", self.name);
+ }
+ let entry = self.connections.vacant_entry();
+ let token = Token(entry.key());
+ assert_ne!(token, WAKE_TOKEN);
+ let connection = Connection::new(connection, token, driver, self.poll.registry())?;
+ debug!("{}: {:?}: new connection", self.name, token);
+ entry.insert(connection);
+ Ok(token)
+ }
+
+ // Step EventLoop once. Call this in a loop from a dedicated thread.
+ // Returns false if EventLoop is shutting down.
+ // Each step may call `handle_event` on any registered connection that
+ // has received readiness events from the poll wakeup.
+ fn poll(&mut self) -> Result<bool> {
+ loop {
+ let r = self.poll.poll(&mut self.events, None);
+ match r {
+ Ok(()) => break,
+ Err(ref e) if interrupted(e) => continue,
+ Err(e) => return Err(e),
+ }
+ }
+
+ for event in self.events.iter() {
+ match event.token() {
+ WAKE_TOKEN => {
+ debug!("{}: WAKE: wake event, will process requests", self.name);
+ }
+ token => {
+ debug!("{}: {:?}: connection event: {:?}", self.name, token, event);
+ let done = if let Some(connection) = self.connections.get_mut(token.0) {
+ match connection.handle_event(event, self.poll.registry()) {
+ Ok(done) => {
+ debug!("{}: connection {:?} done={}", self.name, token, done);
+ done
+ }
+ Err(e) => {
+ debug!("{}: {:?}: connection error: {:?}", self.name, token, e);
+ true
+ }
+ }
+ } else {
+ // Spurious event, log and ignore.
+ debug!(
+ "{}: {:?}: token not found in slab: {:?}",
+ self.name, token, event
+ );
+ false
+ };
+ if done {
+ debug!("{}: {:?}: done, removing", self.name, token);
+ let mut connection = self.connections.remove(token.0);
+ if let Err(e) = connection.shutdown(self.poll.registry()) {
+ debug!(
+ "{}: EventLoop drop - closing connection for {:?} failed: {:?}",
+ self.name, token, e
+ );
+ }
+ }
+ }
+ }
+ }
+
+ // If the waker was signalled there may be pending requests to process.
+ while let Ok(req) = self.requests_rx.try_recv() {
+ match req {
+ Request::AddConnection(pipe, driver, tx) => {
+ debug!("{}: EventLoop: handling add_connection", self.name);
+ let r = self.add_connection(pipe, driver);
+ tx.send(r).expect("EventLoop::add_connection");
+ }
+ Request::Shutdown => {
+ debug!("{}: EventLoop: handling shutdown", self.name);
+ return Ok(false);
+ }
+ Request::WakeConnection(token) => {
+ debug!(
+ "{}: EventLoop: handling wake_connection {:?}",
+ self.name, token
+ );
+ let done = if let Some(connection) = self.connections.get_mut(token.0) {
+ match connection.handle_wake(self.poll.registry()) {
+ Ok(done) => {
+ assert!(!done);
+ false
+ }
+ Err(e) => {
+ debug!("{}: {:?}: connection error: {:?}", self.name, token, e);
+ true
+ }
+ }
+ } else {
+ // Spurious wake, log and ignore.
+ debug!(
+ "{}: {:?}: token not found in slab: wake_connection",
+ self.name, token
+ );
+ false
+ };
+ if done {
+ debug!("{}: {:?}: done (wake), removing", self.name, token);
+ let mut connection = self.connections.remove(token.0);
+ if let Err(e) = connection.shutdown(self.poll.registry()) {
+ debug!(
+ "{}: EventLoop drop - closing connection for {:?} failed: {:?}",
+ self.name, token, e
+ );
+ }
+ }
+ }
+ }
+ }
+
+ Ok(true)
+ }
+}
+
+impl Drop for EventLoop {
+ fn drop(&mut self) {
+ debug!("{}: EventLoop drop", self.name);
+ for (token, connection) in &mut self.connections {
+ debug!(
+ "{}: EventLoop drop - closing connection for {:?}",
+ self.name, token
+ );
+ if let Err(e) = connection.shutdown(self.poll.registry()) {
+ debug!(
+ "{}: EventLoop drop - closing connection for {:?} failed: {:?}",
+ self.name, token, e
+ );
+ }
+ }
+ debug!("{}: EventLoop drop done", self.name);
+ }
+}
+
+// Connection wraps an interprocess connection (Pipe) and manages
+// receiving inbound and sending outbound buffers (and associated handles, if any).
+// The associated driver is responsible for message framing and serialization.
+struct Connection {
+ io: sys::Pipe,
+ token: Token,
+ interest: Option<Interest>,
+ inbound: sys::ConnectionBuffer,
+ outbound: sys::ConnectionBuffer,
+ driver: Box<dyn Driver + Send>,
+}
+
+pub(crate) const IPC_CLIENT_BUFFER_SIZE: usize = 16384;
+
+impl Connection {
+ fn new(
+ mut io: sys::Pipe,
+ token: Token,
+ driver: Box<dyn Driver + Send>,
+ registry: &Registry,
+ ) -> Result<Connection> {
+ let interest = Interest::READABLE;
+ registry.register(&mut io, token, interest)?;
+ Ok(Connection {
+ io,
+ token,
+ interest: Some(interest),
+ inbound: sys::ConnectionBuffer::with_capacity(IPC_CLIENT_BUFFER_SIZE),
+ outbound: sys::ConnectionBuffer::with_capacity(IPC_CLIENT_BUFFER_SIZE),
+ driver,
+ })
+ }
+
+ fn shutdown(&mut self, registry: &Registry) -> Result<()> {
+ trace!(
+ "{:?}: connection shutdown interest={:?}",
+ self.token,
+ self.interest
+ );
+ let r = self.io.shutdown();
+ trace!("{:?}: connection shutdown r={:?}", self.token, r);
+ self.interest = None;
+ registry.deregister(&mut self.io)
+ }
+
+ // Connections are always interested in READABLE. clear_readable is only
+ // called when the connection is in the process of shutting down.
+ fn clear_readable(&mut self, registry: &Registry) -> Result<()> {
+ self.update_registration(
+ registry,
+ self.interest.and_then(|i| i.remove(Interest::READABLE)),
+ )
+ }
+
+ // Connections toggle WRITABLE based on the state of the `outbound` buffer.
+ fn set_writable(&mut self, registry: &Registry) -> Result<()> {
+ self.update_registration(
+ registry,
+ Some(
+ self.interest
+ .map_or_else(|| Interest::WRITABLE, |i| i.add(Interest::WRITABLE)),
+ ),
+ )
+ }
+
+ fn clear_writable(&mut self, registry: &Registry) -> Result<()> {
+ self.update_registration(
+ registry,
+ self.interest.and_then(|i| i.remove(Interest::WRITABLE)),
+ )
+ }
+
+ // Update connection registration with the current readiness event interests.
+ fn update_registration(
+ &mut self,
+ registry: &Registry,
+ new_interest: Option<Interest>,
+ ) -> Result<()> {
+ // Note: Updating registration always triggers a writable event with NamedPipes, so
+ // it's important to skip updating registration when the set of interests hasn't changed.
+ if new_interest != self.interest {
+ trace!(
+ "{:?}: updating readiness registration old={:?} new={:?}",
+ self.token,
+ self.interest,
+ new_interest
+ );
+ self.interest = new_interest;
+ if let Some(interest) = self.interest {
+ registry.reregister(&mut self.io, self.token, interest)?;
+ } else {
+ registry.deregister(&mut self.io)?;
+ }
+ }
+ Ok(())
+ }
+
+ // Handle readiness event. Errors returned are fatal for this connection, resulting in removal from the EventLoop connection list.
+ // The EventLoop will call this for any connection that has received an event.
+ fn handle_event(&mut self, event: &Event, registry: &Registry) -> Result<bool> {
+ debug!("{:?}: handling event {:?}", self.token, event);
+ assert_eq!(self.token, event.token());
+ let done = if event.is_readable() {
+ self.recv_inbound()?
+ } else {
+ trace!("{:?}: not readable", self.token);
+ false
+ };
+ self.flush_outbound()?;
+ if self.send_outbound(registry)? {
+ // Hit EOF during send
+ return Ok(true);
+ }
+ debug!(
+ "{:?}: handling event done (recv done={}, outbound={})",
+ self.token,
+ done,
+ self.outbound.is_empty()
+ );
+ let done = done && self.outbound.is_empty();
+ // If driver is done and outbound is clear, unregister connection.
+ if done {
+ trace!("{:?}: driver done, clearing read interest", self.token);
+ self.clear_readable(registry)?;
+ }
+ Ok(done)
+ }
+
+ // Handle wake event. Errors returned are fatal for this connection, resulting in removal from the EventLoop connection list.
+ // The EventLoop will call this to clear the outbound buffer for any connection that has received a wake event.
+ fn handle_wake(&mut self, registry: &Registry) -> Result<bool> {
+ debug!("{:?}: handling wake", self.token);
+ self.flush_outbound()?;
+ if self.send_outbound(registry)? {
+ // Hit EOF during send
+ return Ok(true);
+ }
+ debug!("{:?}: handling wake done", self.token);
+ Ok(false)
+ }
+
+ fn recv_inbound(&mut self) -> Result<bool> {
+ // If the connection is readable, read into inbound and pass to driver for processing until all ready data
+ // has been consumed.
+ loop {
+ trace!("{:?}: pre-recv inbound: {:?}", self.token, self.inbound);
+ let r = self.io.recv_msg(&mut self.inbound);
+ match r {
+ Ok(0) => {
+ trace!("{:?}: recv EOF", self.token);
+ assert!(self.inbound.is_empty()); // Ensure no unprocessed messages queued.
+ return Ok(true);
+ }
+ Ok(n) => {
+ trace!("{:?}: recv bytes: {}, process_inbound", self.token, n);
+ let r = self.driver.process_inbound(&mut self.inbound);
+ trace!("{:?}: process_inbound done: {:?}", self.token, r);
+ match r {
+ Ok(done) => {
+ if done {
+ return Ok(true);
+ }
+ }
+ Err(e) => {
+ debug!("{:?}: process_inbound error: {:?}", self.token, e);
+ assert!(self.inbound.is_empty()); // Ensure no unprocessed messages queued.
+ return Err(e);
+ }
+ }
+ }
+ Err(ref e) if would_block(e) => {
+ trace!("{:?}: recv would_block: {:?}", self.token, e);
+ return Ok(false);
+ }
+ Err(ref e) if interrupted(e) => {
+ trace!("{:?}: recv interrupted: {:?}", self.token, e);
+ continue;
+ }
+ Err(e) => {
+ debug!("{:?}: recv error: {:?}", self.token, e);
+ return Err(e);
+ }
+ }
+ }
+ }
+
+ fn flush_outbound(&mut self) -> Result<()> {
+ // Enqueue outbound messages to the outbound buffer, then try to write out to connection.
+ // There may be outbound messages even if there was no inbound processing, so always attempt
+ // to enqueue and flush.
+ trace!("{:?}: flush_outbound", self.token);
+ let r = self.driver.flush_outbound(&mut self.outbound);
+ trace!("{:?}: flush_outbound done: {:?}", self.token, r);
+ if let Err(e) = r {
+ debug!("{:?}: flush_outbound error: {:?}", self.token, e);
+ return Err(e);
+ }
+ Ok(())
+ }
+
+ fn send_outbound(&mut self, registry: &Registry) -> Result<bool> {
+ // Attempt to flush outbound buffer. If the connection's write buffer is full, register for WRITABLE
+ // and complete flushing when associated notitication arrives later.
+ while !self.outbound.is_empty() {
+ let r = self.io.send_msg(&mut self.outbound);
+ match r {
+ Ok(0) => {
+ trace!("{:?}: send EOF", self.token);
+ return Ok(true);
+ }
+ Ok(n) => {
+ trace!("{:?}: send bytes: {}", self.token, n);
+ }
+ Err(ref e) if would_block(e) => {
+ trace!(
+ "{:?}: send would_block: {:?}, setting write interest",
+ self.token,
+ e
+ );
+ // Register for write events.
+ self.set_writable(registry)?;
+ break;
+ }
+ Err(ref e) if interrupted(e) => {
+ trace!("{:?}: send interrupted: {:?}", self.token, e);
+ continue;
+ }
+ Err(e) => {
+ debug!("{:?}: send error: {:?}", self.token, e);
+ return Err(e);
+ }
+ }
+ trace!("{:?}: post-send: outbound {:?}", self.token, self.outbound);
+ }
+ // Outbound buffer flushed, clear registration for WRITABLE.
+ if self.outbound.is_empty() {
+ trace!("{:?}: outbound empty, clearing write interest", self.token);
+ self.clear_writable(registry)?;
+ }
+ Ok(false)
+ }
+}
+
+impl Drop for Connection {
+ fn drop(&mut self) {
+ debug!("{:?}: Connection drop", self.token);
+ }
+}
+
+fn would_block(err: &std::io::Error) -> bool {
+ err.kind() == std::io::ErrorKind::WouldBlock
+}
+
+fn interrupted(err: &std::io::Error) -> bool {
+ err.kind() == std::io::ErrorKind::Interrupted
+}
+
+// Driver only has a single implementation, but must be hidden behind a Trait object to
+// hide the varying FramedDriver sizes (due to different `T` values).
+trait Driver {
+ // Handle inbound messages. Returns true if Driver is done; this will trigger Connection removal and cleanup.
+ fn process_inbound(&mut self, inbound: &mut sys::ConnectionBuffer) -> Result<bool>;
+
+ // Write outbound messages to `outbound`.
+ fn flush_outbound(&mut self, outbound: &mut sys::ConnectionBuffer) -> Result<()>;
+}
+
+// Length-delimited connection framing and (de)serialization is handled by the inbound and outbound processing.
+// Handlers can then process message Requests and Responses without knowledge of serialization or
+// handle remoting.
+impl<T> Driver for FramedDriver<T>
+where
+ T: Handler,
+ T::In: DeserializeOwned + Debug + AssociateHandleForMessage,
+ T::Out: Serialize + Debug + AssociateHandleForMessage,
+{
+ // Caller passes `inbound` data, this function will trim any complete messages from `inbound` and pass them to the handler for processing.
+ fn process_inbound(&mut self, inbound: &mut sys::ConnectionBuffer) -> Result<bool> {
+ debug!("process_inbound: {:?}", inbound);
+
+ // Repeatedly call `decode` as long as it produces items, passing each produced item to the handler to action.
+ #[allow(unused_mut)]
+ while let Some(mut item) = self.codec.decode(&mut inbound.buf)? {
+ if item.has_associated_handle() {
+ // On Unix, dequeue a handle from the connection and update the item's handle.
+ #[cfg(unix)]
+ {
+ let new = inbound
+ .pop_handle()
+ .expect("inbound handle expected for item");
+ unsafe { item.set_local_handle(new.take()) };
+ }
+ // On Windows, the deserialized item contains the correct handle value, so
+ // convert it to an owned handle on the item.
+ #[cfg(windows)]
+ {
+ assert!(inbound.pop_handle().is_none());
+ unsafe { item.set_local_handle() };
+ }
+ }
+
+ self.handler.consume(item)?;
+ }
+
+ Ok(false)
+ }
+
+ // Caller will try to write `outbound` to associated connection, queuing any data that can't be transmitted immediately.
+ fn flush_outbound(&mut self, outbound: &mut sys::ConnectionBuffer) -> Result<()> {
+ debug!("flush_outbound: {:?}", outbound.buf);
+
+ // Repeatedly grab outgoing items from the handler, passing each to `encode` for serialization into `outbound`.
+ while let Some(mut item) = self.handler.produce()? {
+ let handle = if item.has_associated_handle() {
+ #[allow(unused_mut)]
+ let mut handle = item.take_handle();
+ // On Windows, the handle is transferred by duplicating it into the target remote process.
+ #[cfg(windows)]
+ unsafe {
+ item.set_remote_handle(handle.send_to_target()?);
+ }
+ Some(handle)
+ } else {
+ None
+ };
+
+ self.codec.encode(item, &mut outbound.buf)?;
+ if let Some(handle) = handle {
+ // `outbound` retains ownership of the handle until the associated
+ // encoded item in `outbound.buf` is sent to the remote process.
+ outbound.push_handle(handle);
+ }
+ }
+ Ok(())
+ }
+}
+
+struct FramedDriver<T: Handler> {
+ codec: LengthDelimitedCodec<T::Out, T::In>,
+ handler: T,
+}
+
+impl<T: Handler> FramedDriver<T> {
+ fn new(handler: T) -> FramedDriver<T> {
+ FramedDriver {
+ codec: Default::default(),
+ handler,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct EventLoopThread {
+ thread: Option<thread::JoinHandle<Result<()>>>,
+ name: String,
+ handle: EventLoopHandle,
+}
+
+impl EventLoopThread {
+ pub fn new<F1, F2>(
+ name: String,
+ stack_size: Option<usize>,
+ after_start: F1,
+ before_stop: F2,
+ ) -> Result<Self>
+ where
+ F1: Fn() + Send + 'static,
+ F2: Fn() + Send + 'static,
+ {
+ let mut event_loop = EventLoop::new(name.clone())?;
+ let handle = event_loop.handle();
+
+ let builder = thread::Builder::new()
+ .name(name.clone())
+ .stack_size(stack_size.unwrap_or(64 * 4096));
+
+ let thread = builder.spawn(move || {
+ trace!("{}: event loop thread enter", event_loop.name);
+ after_start();
+ let _thread_exit_guard = scopeguard::guard((), |_| before_stop());
+
+ let r = loop {
+ let start = std::time::Instant::now();
+ let r = event_loop.poll();
+ trace!(
+ "{}: event loop poll r={:?}, took={}μs",
+ event_loop.name,
+ r,
+ start.elapsed().as_micros()
+ );
+ match r {
+ Ok(true) => continue,
+ _ => break r,
+ }
+ };
+
+ trace!("{}: event loop thread exit", event_loop.name);
+ r.map(|_| ())
+ })?;
+
+ Ok(EventLoopThread {
+ thread: Some(thread),
+ name,
+ handle,
+ })
+ }
+
+ pub fn handle(&self) -> &EventLoopHandle {
+ &self.handle
+ }
+}
+
+impl Drop for EventLoopThread {
+ // Shut down event loop and executor thread. Blocks until complete.
+ fn drop(&mut self) {
+ trace!("{}: EventLoopThread shutdown", self.name);
+ if let Err(e) = self.handle.shutdown() {
+ debug!("{}: initiating shutdown failed: {:?}", self.name, e);
+ }
+ let thread = self.thread.take().expect("event loop thread");
+ if let Err(e) = thread.join() {
+ error!("{}: EventLoopThread failed: {:?}", self.name, e);
+ }
+ trace!("{}: EventLoopThread shutdown done", self.name);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use serde_derive::{Deserialize, Serialize};
+
+ use super::*;
+
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
+ enum TestServerMessage {
+ TestRequest,
+ }
+ impl AssociateHandleForMessage for TestServerMessage {}
+
+ struct TestServerImpl {}
+
+ impl Server for TestServerImpl {
+ type ServerMessage = TestServerMessage;
+ type ClientMessage = TestClientMessage;
+
+ fn process(&mut self, req: Self::ServerMessage) -> Self::ClientMessage {
+ assert_eq!(req, TestServerMessage::TestRequest);
+ TestClientMessage::TestResponse
+ }
+ }
+
+ #[derive(Debug, Serialize, Deserialize, PartialEq)]
+ enum TestClientMessage {
+ TestResponse,
+ }
+
+ impl AssociateHandleForMessage for TestClientMessage {}
+
+ struct TestClientImpl {}
+
+ impl Client for TestClientImpl {
+ type ServerMessage = TestServerMessage;
+ type ClientMessage = TestClientMessage;
+ }
+
+ fn init() {
+ let _ = env_logger::builder().is_test(true).try_init();
+ }
+
+ fn setup() -> (
+ EventLoopThread,
+ EventLoopThread,
+ Proxy<TestServerMessage, TestClientMessage>,
+ ) {
+ // Server setup and registration.
+ let server = EventLoopThread::new("test-server".to_string(), None, || {}, || {})
+ .expect("server EventLoopThread");
+ let server_handle = server.handle();
+
+ let (server_pipe, client_pipe) = sys::make_pipe_pair().expect("server make_pipe_pair");
+ server_handle
+ .bind_server(TestServerImpl {}, server_pipe)
+ .expect("server bind_server");
+
+ // Client setup and registration.
+ let client = EventLoopThread::new("test-client".to_string(), None, || {}, || {})
+ .expect("client EventLoopThread");
+ let client_handle = client.handle();
+
+ let client_pipe = unsafe { sys::Pipe::from_raw_handle(client_pipe) };
+ let client_proxy = client_handle
+ .bind_client::<TestClientImpl>(client_pipe)
+ .expect("client bind_client");
+
+ (server, client, client_proxy)
+ }
+
+ // Verify basic EventLoopThread functionality works. Create a server and client EventLoopThread, then send
+ // a single message from the client to the server and wait for the expected response.
+ #[test]
+ fn basic() {
+ init();
+ let (server, client, client_proxy) = setup();
+
+ // RPC message from client to server.
+ let response = client_proxy.call(TestServerMessage::TestRequest);
+ let response = response.expect("client response");
+ assert_eq!(response, TestClientMessage::TestResponse);
+
+ // Explicit shutdown.
+ drop(client);
+ drop(server);
+ }
+
+ // Same as `basic`, but shut down server before client.
+ #[test]
+ fn basic_reverse_drop_order() {
+ init();
+ let (server, client, client_proxy) = setup();
+
+ // RPC message from client to server.
+ let response = client_proxy.call(TestServerMessage::TestRequest);
+ let response = response.expect("client response");
+ assert_eq!(response, TestClientMessage::TestResponse);
+
+ // Explicit shutdown.
+ drop(server);
+ drop(client);
+ }
+
+ #[test]
+ fn dead_server() {
+ init();
+ let (server, _client, client_proxy) = setup();
+ drop(server);
+
+ let response = client_proxy.call(TestServerMessage::TestRequest);
+ response.expect_err("sending on closed channel");
+ }
+
+ #[test]
+ fn dead_client() {
+ init();
+ let (_server, client, client_proxy) = setup();
+ drop(client);
+
+ let response = client_proxy.call(TestServerMessage::TestRequest);
+ response.expect_err("sending on a closed channel");
+ }
+
+ #[test]
+ fn disconnected_handle() {
+ init();
+ let server = EventLoopThread::new("test-server".to_string(), None, || {}, || {})
+ .expect("server EventLoopThread");
+ let server_handle = server.handle().clone();
+ drop(server);
+
+ server_handle
+ .shutdown()
+ .expect_err("sending on closed channel");
+ }
+
+ #[test]
+ fn clone_after_drop() {
+ init();
+ let (server, client, client_proxy) = setup();
+ drop(server);
+ drop(client);
+
+ client_proxy
+ .try_clone()
+ .expect_err("cloning a closed proxy");
+ }
+
+ #[test]
+ fn basic_event_loop_thread_callbacks() {
+ init();
+ let (start_tx, start_rx) = mpsc::channel();
+ let (stop_tx, stop_rx) = mpsc::channel();
+
+ let elt = EventLoopThread::new(
+ "test-thread-callbacks".to_string(),
+ None,
+ move || start_tx.send(()).unwrap(),
+ move || stop_tx.send(()).unwrap(),
+ )
+ .expect("server EventLoopThread");
+
+ start_rx.recv().expect("after_start callback done");
+
+ drop(elt);
+
+ stop_rx.recv().expect("before_stop callback done");
+ }
+}
diff --git a/third_party/rust/audioipc2/src/lib.rs b/third_party/rust/audioipc2/src/lib.rs
new file mode 100644
index 0000000000..eb6d843ba4
--- /dev/null
+++ b/third_party/rust/audioipc2/src/lib.rs
@@ -0,0 +1,214 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+#![warn(unused_extern_crates)]
+#![recursion_limit = "1024"]
+#[macro_use]
+extern crate error_chain;
+#[macro_use]
+extern crate log;
+
+pub mod codec;
+#[allow(deprecated)]
+pub mod errors;
+pub mod messages;
+pub mod shm;
+
+pub mod ipccore;
+pub mod rpccore;
+pub mod sys;
+
+pub use crate::messages::{ClientMessage, ServerMessage};
+
+#[cfg(unix)]
+use std::os::unix::io::IntoRawFd;
+#[cfg(windows)]
+use std::os::windows::io::IntoRawHandle;
+
+use std::io::Result;
+
+// This must match the definition of
+// ipc::FileDescriptor::PlatformHandleType in Gecko.
+#[cfg(windows)]
+pub type PlatformHandleType = std::os::windows::raw::HANDLE;
+#[cfg(unix)]
+pub type PlatformHandleType = libc::c_int;
+
+// This stands in for RawFd/RawHandle.
+#[derive(Debug)]
+pub struct PlatformHandle(PlatformHandleType);
+
+#[cfg(unix)]
+pub const INVALID_HANDLE_VALUE: PlatformHandleType = -1isize as PlatformHandleType;
+
+#[cfg(windows)]
+pub const INVALID_HANDLE_VALUE: PlatformHandleType = winapi::um::handleapi::INVALID_HANDLE_VALUE;
+
+#[cfg(unix)]
+fn valid_handle(handle: PlatformHandleType) -> bool {
+ handle >= 0
+}
+
+#[cfg(windows)]
+fn valid_handle(handle: PlatformHandleType) -> bool {
+ handle != INVALID_HANDLE_VALUE && !handle.is_null()
+}
+
+impl PlatformHandle {
+ pub fn new(raw: PlatformHandleType) -> PlatformHandle {
+ assert!(valid_handle(raw));
+ PlatformHandle(raw)
+ }
+
+ #[cfg(windows)]
+ pub fn from<T: IntoRawHandle>(from: T) -> PlatformHandle {
+ PlatformHandle::new(from.into_raw_handle())
+ }
+
+ #[cfg(unix)]
+ pub fn from<T: IntoRawFd>(from: T) -> PlatformHandle {
+ PlatformHandle::new(from.into_raw_fd())
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn into_raw(self) -> PlatformHandleType {
+ let handle = self.0;
+ std::mem::forget(self);
+ handle
+ }
+
+ #[cfg(unix)]
+ pub fn duplicate(h: PlatformHandleType) -> Result<PlatformHandle> {
+ unsafe {
+ let newfd = libc::dup(h);
+ if !valid_handle(newfd) {
+ return Err(std::io::Error::last_os_error());
+ }
+ Ok(PlatformHandle::from(newfd))
+ }
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ #[cfg(windows)]
+ pub unsafe fn duplicate(h: PlatformHandleType) -> Result<PlatformHandle> {
+ let dup = duplicate_platform_handle(h, None)?;
+ Ok(PlatformHandle::new(dup))
+ }
+}
+
+impl Drop for PlatformHandle {
+ fn drop(&mut self) {
+ unsafe { close_platform_handle(self.0) }
+ }
+}
+
+#[cfg(unix)]
+unsafe fn close_platform_handle(handle: PlatformHandleType) {
+ libc::close(handle);
+}
+
+#[cfg(windows)]
+unsafe fn close_platform_handle(handle: PlatformHandleType) {
+ winapi::um::handleapi::CloseHandle(handle);
+}
+
+#[cfg(windows)]
+use winapi::shared::minwindef::{DWORD, FALSE};
+#[cfg(windows)]
+use winapi::um::{handleapi, processthreadsapi, winnt};
+
+// Duplicate `source_handle` to `target_pid`. Returns the value of the new handle inside the target process.
+// If `target_pid` is `None`, `source_handle` is duplicated in the current process.
+#[cfg(windows)]
+pub(crate) unsafe fn duplicate_platform_handle(
+ source_handle: PlatformHandleType,
+ target_pid: Option<DWORD>,
+) -> Result<PlatformHandleType> {
+ let source_process = processthreadsapi::GetCurrentProcess();
+ let target_process = if let Some(pid) = target_pid {
+ let target = processthreadsapi::OpenProcess(winnt::PROCESS_DUP_HANDLE, FALSE, pid);
+ if !valid_handle(target) {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "invalid target process",
+ ));
+ }
+ Some(target)
+ } else {
+ None
+ };
+
+ let mut target_handle = std::ptr::null_mut();
+ let ok = handleapi::DuplicateHandle(
+ source_process,
+ source_handle,
+ target_process.unwrap_or(source_process),
+ &mut target_handle,
+ 0,
+ FALSE,
+ winnt::DUPLICATE_SAME_ACCESS,
+ );
+ if let Some(target) = target_process {
+ handleapi::CloseHandle(target);
+ };
+ if ok == FALSE {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "DuplicateHandle failed",
+ ));
+ }
+ Ok(target_handle)
+}
+
+// Close `target_handle_to_close` inside target process `target_pid` using a
+// special invocation of `DuplicateHandle`. See
+// https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle#:~:text=Normally%20the%20target,dwOptions%20to%20DUPLICATE_CLOSE_SOURCE.
+#[cfg(windows)]
+pub(crate) unsafe fn close_target_handle(
+ target_handle_to_close: PlatformHandleType,
+ target_pid: DWORD,
+) -> Result<()> {
+ let target_process =
+ processthreadsapi::OpenProcess(winnt::PROCESS_DUP_HANDLE, FALSE, target_pid);
+ if !valid_handle(target_process) {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "invalid target process",
+ ));
+ }
+
+ let ok = handleapi::DuplicateHandle(
+ target_process,
+ target_handle_to_close,
+ std::ptr::null_mut(),
+ std::ptr::null_mut(),
+ 0,
+ FALSE,
+ winnt::DUPLICATE_CLOSE_SOURCE,
+ );
+ handleapi::CloseHandle(target_process);
+ if ok == FALSE {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "DuplicateHandle failed",
+ ));
+ }
+ Ok(())
+}
+
+#[cfg(windows)]
+pub fn server_platform_init() {
+ use winapi::shared::winerror;
+ use winapi::um::combaseapi;
+ use winapi::um::objbase;
+
+ unsafe {
+ let r = combaseapi::CoInitializeEx(std::ptr::null_mut(), objbase::COINIT_MULTITHREADED);
+ assert!(winerror::SUCCEEDED(r));
+ }
+}
+
+#[cfg(unix)]
+pub fn server_platform_init() {}
diff --git a/third_party/rust/audioipc2/src/messages.rs b/third_party/rust/audioipc2/src/messages.rs
new file mode 100644
index 0000000000..853f056bae
--- /dev/null
+++ b/third_party/rust/audioipc2/src/messages.rs
@@ -0,0 +1,632 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::PlatformHandle;
+use crate::PlatformHandleType;
+use crate::INVALID_HANDLE_VALUE;
+#[cfg(target_os = "linux")]
+use audio_thread_priority::RtPriorityThreadInfo;
+use cubeb::{self, ffi};
+use serde_derive::Deserialize;
+use serde_derive::Serialize;
+use std::ffi::{CStr, CString};
+use std::os::raw::{c_char, c_int, c_uint};
+use std::ptr;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Device {
+ #[serde(with = "serde_bytes")]
+ pub output_name: Option<Vec<u8>>,
+ #[serde(with = "serde_bytes")]
+ pub input_name: Option<Vec<u8>>,
+}
+
+impl<'a> From<&'a cubeb::DeviceRef> for Device {
+ fn from(info: &'a cubeb::DeviceRef) -> Self {
+ Self {
+ output_name: info.output_name_bytes().map(|s| s.to_vec()),
+ input_name: info.input_name_bytes().map(|s| s.to_vec()),
+ }
+ }
+}
+
+impl From<ffi::cubeb_device> for Device {
+ fn from(info: ffi::cubeb_device) -> Self {
+ Self {
+ output_name: dup_str(info.output_name),
+ input_name: dup_str(info.input_name),
+ }
+ }
+}
+
+impl From<Device> for ffi::cubeb_device {
+ fn from(info: Device) -> Self {
+ Self {
+ output_name: opt_str(info.output_name),
+ input_name: opt_str(info.input_name),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct DeviceInfo {
+ pub devid: usize,
+ #[serde(with = "serde_bytes")]
+ pub device_id: Option<Vec<u8>>,
+ #[serde(with = "serde_bytes")]
+ pub friendly_name: Option<Vec<u8>>,
+ #[serde(with = "serde_bytes")]
+ pub group_id: Option<Vec<u8>>,
+ #[serde(with = "serde_bytes")]
+ pub vendor_name: Option<Vec<u8>>,
+
+ pub device_type: ffi::cubeb_device_type,
+ pub state: ffi::cubeb_device_state,
+ pub preferred: ffi::cubeb_device_pref,
+
+ pub format: ffi::cubeb_device_fmt,
+ pub default_format: ffi::cubeb_device_fmt,
+ pub max_channels: u32,
+ pub default_rate: u32,
+ pub max_rate: u32,
+ pub min_rate: u32,
+
+ pub latency_lo: u32,
+ pub latency_hi: u32,
+}
+
+impl<'a> From<&'a cubeb::DeviceInfoRef> for DeviceInfo {
+ fn from(info: &'a cubeb::DeviceInfoRef) -> Self {
+ let info = unsafe { &*info.as_ptr() };
+ DeviceInfo {
+ devid: info.devid as _,
+ device_id: dup_str(info.device_id),
+ friendly_name: dup_str(info.friendly_name),
+ group_id: dup_str(info.group_id),
+ vendor_name: dup_str(info.vendor_name),
+
+ device_type: info.device_type,
+ state: info.state,
+ preferred: info.preferred,
+
+ format: info.format,
+ default_format: info.default_format,
+ max_channels: info.max_channels,
+ default_rate: info.default_rate,
+ max_rate: info.max_rate,
+ min_rate: info.min_rate,
+
+ latency_lo: info.latency_lo,
+ latency_hi: info.latency_hi,
+ }
+ }
+}
+
+impl From<DeviceInfo> for ffi::cubeb_device_info {
+ fn from(info: DeviceInfo) -> Self {
+ ffi::cubeb_device_info {
+ devid: info.devid as _,
+ device_id: opt_str(info.device_id),
+ friendly_name: opt_str(info.friendly_name),
+ group_id: opt_str(info.group_id),
+ vendor_name: opt_str(info.vendor_name),
+
+ device_type: info.device_type,
+ state: info.state,
+ preferred: info.preferred,
+
+ format: info.format,
+ default_format: info.default_format,
+ max_channels: info.max_channels,
+ default_rate: info.default_rate,
+ max_rate: info.max_rate,
+ min_rate: info.min_rate,
+
+ latency_lo: info.latency_lo,
+ latency_hi: info.latency_hi,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
+pub struct StreamParams {
+ pub format: ffi::cubeb_sample_format,
+ pub rate: c_uint,
+ pub channels: c_uint,
+ pub layout: ffi::cubeb_channel_layout,
+ pub prefs: ffi::cubeb_stream_prefs,
+}
+
+impl From<&cubeb::StreamParamsRef> for StreamParams {
+ fn from(x: &cubeb::StreamParamsRef) -> StreamParams {
+ unsafe { *(x.as_ptr() as *mut StreamParams) }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StreamCreateParams {
+ pub input_stream_params: Option<StreamParams>,
+ pub output_stream_params: Option<StreamParams>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StreamInitParams {
+ #[serde(with = "serde_bytes")]
+ pub stream_name: Option<Vec<u8>>,
+ pub input_device: usize,
+ pub input_stream_params: Option<StreamParams>,
+ pub output_device: usize,
+ pub output_stream_params: Option<StreamParams>,
+ pub latency_frames: u32,
+}
+
+fn dup_str(s: *const c_char) -> Option<Vec<u8>> {
+ if s.is_null() {
+ None
+ } else {
+ let vec: Vec<u8> = unsafe { CStr::from_ptr(s) }.to_bytes().to_vec();
+ Some(vec)
+ }
+}
+
+fn opt_str(v: Option<Vec<u8>>) -> *mut c_char {
+ match v {
+ Some(v) => match CString::new(v) {
+ Ok(s) => s.into_raw(),
+ Err(_) => {
+ debug!("Failed to convert bytes to CString");
+ ptr::null_mut()
+ }
+ },
+ None => ptr::null_mut(),
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct StreamCreate {
+ pub token: usize,
+ pub shm_handle: SerializableHandle,
+ pub shm_area_size: usize,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct RegisterDeviceCollectionChanged {
+ pub platform_handle: SerializableHandle,
+}
+
+// Client -> Server messages.
+// TODO: Callbacks should be different messages types so
+// ServerConn::process_msg doesn't have a catch-all case.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum ServerMessage {
+ ClientConnect(u32),
+ ClientDisconnect,
+
+ ContextGetBackendId,
+ ContextGetMaxChannelCount,
+ ContextGetMinLatency(StreamParams),
+ ContextGetPreferredSampleRate,
+ ContextGetDeviceEnumeration(ffi::cubeb_device_type),
+ ContextSetupDeviceCollectionCallback,
+ ContextRegisterDeviceCollectionChanged(ffi::cubeb_device_type, bool),
+
+ StreamCreate(StreamCreateParams),
+ StreamInit(usize, StreamInitParams),
+ StreamDestroy(usize),
+
+ StreamStart(usize),
+ StreamStop(usize),
+ StreamGetPosition(usize),
+ StreamGetLatency(usize),
+ StreamGetInputLatency(usize),
+ StreamSetVolume(usize, f32),
+ StreamSetName(usize, CString),
+ StreamGetCurrentDevice(usize),
+ StreamRegisterDeviceChangeCallback(usize, bool),
+
+ #[cfg(target_os = "linux")]
+ PromoteThreadToRealTime([u8; std::mem::size_of::<RtPriorityThreadInfo>()]),
+}
+
+// Server -> Client messages.
+// TODO: Streams need id.
+#[derive(Debug, Serialize, Deserialize)]
+pub enum ClientMessage {
+ ClientConnected,
+ ClientDisconnected,
+
+ ContextBackendId(String),
+ ContextMaxChannelCount(u32),
+ ContextMinLatency(u32),
+ ContextPreferredSampleRate(u32),
+ ContextEnumeratedDevices(Vec<DeviceInfo>),
+ ContextSetupDeviceCollectionCallback(RegisterDeviceCollectionChanged),
+ ContextRegisteredDeviceCollectionChanged,
+
+ StreamCreated(StreamCreate),
+ StreamInitialized(SerializableHandle),
+ StreamDestroyed,
+
+ StreamStarted,
+ StreamStopped,
+ StreamPosition(u64),
+ StreamLatency(u32),
+ StreamInputLatency(u32),
+ StreamVolumeSet,
+ StreamNameSet,
+ StreamCurrentDevice(Device),
+ StreamRegisterDeviceChangeCallback,
+
+ #[cfg(target_os = "linux")]
+ ThreadPromoted,
+
+ Error(c_int),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum CallbackReq {
+ Data {
+ nframes: isize,
+ input_frame_size: usize,
+ output_frame_size: usize,
+ },
+ State(ffi::cubeb_state),
+ DeviceChange,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum CallbackResp {
+ Data(isize),
+ State,
+ DeviceChange,
+ Error(c_int),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum DeviceCollectionReq {
+ DeviceChange(ffi::cubeb_device_type),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub enum DeviceCollectionResp {
+ DeviceChange,
+}
+
+// Represents a platform handle in various transitional states during serialization and remoting.
+// The process of serializing and remoting handles and the ownership during various states differs
+// between Windows and Unix. SerializableHandle changes during IPC as follows:
+//
+// 1. Created in the initial state `Owned`, with a valid `target_pid`.
+// 2. Ownership is transferred out for processing during IPC send, becoming `Empty` temporarily.
+// See `AssociateHandleForMessage::take_handle`.
+// - Windows: DuplicateHandle transfers the handle to the remote process.
+// This produces a new handle value in the local process representing the remote handle.
+// This value must be sent to the remote, so `AssociateHandleForMessage::set_remote_handle`
+// is used to transform the handle into a `SerializableValue`.
+// - Unix: sendmsg transfers the handle to the remote process. The handle is left `Empty`.
+// (Note: this occurs later, when the serialized message buffer is sent)
+// 3. Message containing `SerializableValue` or `Empty` (depending on handle processing in step 2)
+// is serialized and sent via IPC.
+// 4. Message received and deserialized in target process.
+// - Windows: `AssociateHandleForMessage::set_local_handle converts the received `SerializableValue` into `Owned`, ready for use.
+// - Unix: Handle (with a new value in the target process) is received out-of-band via `recvmsg`
+// and converted to `Owned` via `AssociateHandleForMessage::set_local_handle`.
+#[derive(Debug)]
+pub enum SerializableHandle {
+ // Owned handle, with optional target_pid on sending side.
+ Owned(PlatformHandle, Option<u32>),
+ // Transitional IPC states:
+ SerializableValue(PlatformHandleType), // Windows
+ Empty, // Unix
+}
+
+// PlatformHandle is non-Send and contains a pointer (HANDLE) on Windows.
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl Send for SerializableHandle {}
+
+impl SerializableHandle {
+ pub fn new(handle: PlatformHandle, target_pid: u32) -> SerializableHandle {
+ SerializableHandle::Owned(handle, Some(target_pid))
+ }
+
+ // Called on the receiving side to take ownership of the handle.
+ pub fn take_handle(&mut self) -> PlatformHandle {
+ match std::mem::replace(self, SerializableHandle::Empty) {
+ SerializableHandle::Owned(handle, target_pid) => {
+ assert!(target_pid.is_none());
+ handle
+ }
+ _ => panic!("take_handle called in invalid state"),
+ }
+ }
+
+ // Called on the sending side to take ownership of the handle for
+ // handling platform-specific remoting.
+ fn take_handle_for_send(&mut self) -> RemoteHandle {
+ match std::mem::replace(self, SerializableHandle::Empty) {
+ SerializableHandle::Owned(handle, target_pid) => unsafe {
+ RemoteHandle::new(
+ handle.into_raw(),
+ target_pid.expect("target process required"),
+ )
+ },
+ _ => panic!("take_handle_for_send called in invalid state"),
+ }
+ }
+
+ fn new_owned(handle: PlatformHandleType) -> SerializableHandle {
+ SerializableHandle::Owned(PlatformHandle::new(handle), None)
+ }
+
+ #[cfg(windows)]
+ fn make_owned(&mut self) {
+ if let SerializableHandle::SerializableValue(handle) = self {
+ *self = SerializableHandle::new_owned(*handle);
+ } else {
+ panic!("make_owned called in invalid state")
+ }
+ }
+
+ fn new_serializable_value(handle: PlatformHandleType) -> SerializableHandle {
+ SerializableHandle::SerializableValue(handle)
+ }
+
+ fn get_serializable_value(&self) -> PlatformHandleType {
+ match *self {
+ SerializableHandle::SerializableValue(handle) => handle,
+ SerializableHandle::Empty => INVALID_HANDLE_VALUE,
+ _ => panic!("get_remote_handle called in invalid state"),
+ }
+ }
+}
+
+// Raw handle values are serialized as i64. Additional handling external to (de)serialization is required during IPC
+// send/receive to convert these raw values into valid handles.
+impl serde::Serialize for SerializableHandle {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ let handle = self.get_serializable_value();
+ serializer.serialize_i64(handle as i64)
+ }
+}
+
+impl<'de> serde::Deserialize<'de> for SerializableHandle {
+ fn deserialize<D>(deserializer: D) -> Result<SerializableHandle, D::Error>
+ where
+ D: serde::Deserializer<'de>,
+ {
+ deserializer.deserialize_i64(SerializableHandleVisitor)
+ }
+}
+
+struct SerializableHandleVisitor;
+impl serde::de::Visitor<'_> for SerializableHandleVisitor {
+ type Value = SerializableHandle;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ formatter.write_str("an integer between -2^63 and 2^63")
+ }
+
+ fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ Ok(SerializableHandle::new_serializable_value(
+ value as PlatformHandleType,
+ ))
+ }
+}
+
+// Represents a PlatformHandle in-flight between processes.
+// On Unix platforms, this is just a plain owned Handle, closed on drop.
+// On Windows, `RemoteHandle` also retains ownership of the `target_handle`
+// in the `target` process. Once the handle has been successfully sent
+// to the remote, the sender should call `mark_sent()` to relinquish
+// ownership of `target_handle` in the remote.
+#[derive(Debug)]
+pub struct RemoteHandle {
+ pub(crate) handle: PlatformHandleType,
+ #[cfg(windows)]
+ pub(crate) target: u32,
+ #[cfg(windows)]
+ pub(crate) target_handle: Option<PlatformHandleType>,
+}
+
+impl RemoteHandle {
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn new(handle: PlatformHandleType, _target: u32) -> Self {
+ RemoteHandle {
+ handle,
+ #[cfg(windows)]
+ target: _target,
+ #[cfg(windows)]
+ target_handle: None,
+ }
+ }
+
+ #[cfg(windows)]
+ pub fn mark_sent(&mut self) {
+ self.target_handle.take();
+ }
+
+ #[cfg(windows)]
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn send_to_target(&mut self) -> std::io::Result<PlatformHandleType> {
+ let target_handle = crate::duplicate_platform_handle(self.handle, Some(self.target))?;
+ self.target_handle = Some(target_handle);
+ Ok(target_handle)
+ }
+
+ #[cfg(unix)]
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn take(self) -> PlatformHandleType {
+ let h = self.handle;
+ std::mem::forget(self);
+ h
+ }
+}
+
+impl Drop for RemoteHandle {
+ fn drop(&mut self) {
+ unsafe {
+ crate::close_platform_handle(self.handle);
+ }
+ #[cfg(windows)]
+ unsafe {
+ if let Some(target_handle) = self.target_handle {
+ if let Err(e) = crate::close_target_handle(target_handle, self.target) {
+ trace!("RemoteHandle failed to close target handle: {:?}", e);
+ }
+ }
+ }
+ }
+}
+
+unsafe impl Send for RemoteHandle {}
+
+pub trait AssociateHandleForMessage {
+ // True if this item has an associated handle attached for remoting.
+ fn has_associated_handle(&self) -> bool {
+ false
+ }
+
+ // Take ownership of the associated handle, leaving the item's
+ // associated handle empty.
+ fn take_handle(&mut self) -> RemoteHandle {
+ panic!("take_handle called on item without associated handle");
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ // Replace an empty associated handle with a non-owning serializable value
+ // indicating the value of the handle in the remote process.
+ #[cfg(windows)]
+ unsafe fn set_remote_handle(&mut self, _: PlatformHandleType) {
+ panic!("set_remote_handle called on item without associated handle");
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ // Replace a serialized associated handle value with an owned local handle.
+ #[cfg(windows)]
+ unsafe fn set_local_handle(&mut self) {
+ panic!("set_local_handle called on item without associated handle");
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ // Replace an empty associated handle with an owned local handle.
+ #[cfg(unix)]
+ unsafe fn set_local_handle(&mut self, _: PlatformHandleType) {
+ panic!("set_local_handle called on item without associated handle");
+ }
+}
+
+impl AssociateHandleForMessage for ClientMessage {
+ fn has_associated_handle(&self) -> bool {
+ matches!(
+ *self,
+ ClientMessage::StreamCreated(_)
+ | ClientMessage::StreamInitialized(_)
+ | ClientMessage::ContextSetupDeviceCollectionCallback(_)
+ )
+ }
+
+ fn take_handle(&mut self) -> RemoteHandle {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => data.shm_handle.take_handle_for_send(),
+ ClientMessage::StreamInitialized(ref mut data) => data.take_handle_for_send(),
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ data.platform_handle.take_handle_for_send()
+ }
+ _ => panic!("take_handle called on item without associated handle"),
+ }
+ }
+
+ #[cfg(windows)]
+ unsafe fn set_remote_handle(&mut self, handle: PlatformHandleType) {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => {
+ data.shm_handle = SerializableHandle::new_serializable_value(handle);
+ }
+ ClientMessage::StreamInitialized(ref mut data) => {
+ *data = SerializableHandle::new_serializable_value(handle);
+ }
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ data.platform_handle = SerializableHandle::new_serializable_value(handle);
+ }
+ _ => panic!("set_remote_handle called on item without associated handle"),
+ }
+ }
+
+ #[cfg(windows)]
+ unsafe fn set_local_handle(&mut self) {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => data.shm_handle.make_owned(),
+ ClientMessage::StreamInitialized(ref mut data) => data.make_owned(),
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ data.platform_handle.make_owned()
+ }
+ _ => panic!("set_local_handle called on item without associated handle"),
+ }
+ }
+
+ #[cfg(unix)]
+ unsafe fn set_local_handle(&mut self, handle: PlatformHandleType) {
+ match *self {
+ ClientMessage::StreamCreated(ref mut data) => {
+ data.shm_handle = SerializableHandle::new_owned(handle);
+ }
+ ClientMessage::StreamInitialized(ref mut data) => {
+ *data = SerializableHandle::new_owned(handle);
+ }
+ ClientMessage::ContextSetupDeviceCollectionCallback(ref mut data) => {
+ data.platform_handle = SerializableHandle::new_owned(handle);
+ }
+ _ => panic!("set_local_handle called on item without associated handle"),
+ }
+ }
+}
+
+impl AssociateHandleForMessage for ServerMessage {}
+
+impl AssociateHandleForMessage for DeviceCollectionReq {}
+impl AssociateHandleForMessage for DeviceCollectionResp {}
+
+impl AssociateHandleForMessage for CallbackReq {}
+impl AssociateHandleForMessage for CallbackResp {}
+
+#[cfg(test)]
+mod test {
+ use super::StreamParams;
+ use cubeb::ffi;
+ use std::mem;
+
+ #[test]
+ fn stream_params_size_check() {
+ assert_eq!(
+ mem::size_of::<StreamParams>(),
+ mem::size_of::<ffi::cubeb_stream_params>()
+ )
+ }
+
+ #[test]
+ fn stream_params_from() {
+ let raw = ffi::cubeb_stream_params {
+ format: ffi::CUBEB_SAMPLE_FLOAT32BE,
+ rate: 96_000,
+ channels: 32,
+ layout: ffi::CUBEB_LAYOUT_3F1_LFE,
+ prefs: ffi::CUBEB_STREAM_PREF_LOOPBACK,
+ };
+ let wrapped = ::cubeb::StreamParams::from(raw);
+ let params = StreamParams::from(wrapped.as_ref());
+ assert_eq!(params.format, raw.format);
+ assert_eq!(params.rate, raw.rate);
+ assert_eq!(params.channels, raw.channels);
+ assert_eq!(params.layout, raw.layout);
+ assert_eq!(params.prefs, raw.prefs);
+ }
+}
diff --git a/third_party/rust/audioipc2/src/rpccore.rs b/third_party/rust/audioipc2/src/rpccore.rs
new file mode 100644
index 0000000000..2fbd821007
--- /dev/null
+++ b/third_party/rust/audioipc2/src/rpccore.rs
@@ -0,0 +1,340 @@
+// Copyright © 2021 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use std::collections::VecDeque;
+use std::io::{self, Error, ErrorKind, Result};
+use std::mem::ManuallyDrop;
+use std::sync::{Arc, Mutex};
+
+use crossbeam_channel::{self, Receiver, Sender};
+use mio::Token;
+use slab::Slab;
+
+use crate::ipccore::EventLoopHandle;
+
+// RPC message handler. Implemented by ClientHandler (for Client)
+// and ServerHandler (for Server).
+pub(crate) trait Handler {
+ type In;
+ type Out;
+
+ // Consume a request
+ fn consume(&mut self, request: Self::In) -> Result<()>;
+
+ // Produce a response
+ fn produce(&mut self) -> Result<Option<Self::Out>>;
+}
+
+// Client RPC definition. This supplies the expected message
+// request and response types.
+pub trait Client {
+ type ServerMessage;
+ type ClientMessage;
+}
+
+// Server RPC definition. This supplies the expected message
+// request and response types. `process` is passed inbound RPC
+// requests by the ServerHandler to be responded to by the server.
+pub trait Server {
+ type ServerMessage;
+ type ClientMessage;
+
+ fn process(&mut self, req: Self::ServerMessage) -> Self::ClientMessage;
+}
+
+// RPC Client Proxy implementation.
+type ProxyKey = usize;
+type ProxyRequest<Request> = (ProxyKey, Request);
+
+// RPC Proxy that may be `clone`d for use by multiple owners/threads.
+// A Proxy `call` arranges for the supplied request to be transmitted
+// to the associated Server via RPC and blocks awaiting the response
+// via `response_rx`.
+// A Proxy is associated with the ClientHandler via `handler_tx` to send requests,
+// `response_rx` to receive responses, and uses `key` to identify the Proxy with
+// the sending side of `response_rx` ClientHandler.
+// Each Proxy is registered with the ClientHandler on initialization via the
+// ProxyManager and unregistered when dropped.
+// A ClientHandler normally lives until the last Proxy is dropped, but if the ClientHandler
+// encounters an internal error, `handler_tx` and `response_tx` will be closed.
+#[derive(Debug)]
+pub struct Proxy<Request, Response> {
+ handle: Option<(EventLoopHandle, Token)>,
+ key: ProxyKey,
+ response_rx: Receiver<Response>,
+ handler_tx: ManuallyDrop<Sender<ProxyRequest<Request>>>,
+ proxy_mgr: Arc<ProxyManager<Response>>,
+}
+
+impl<Request, Response> Proxy<Request, Response> {
+ fn new(
+ handler_tx: Sender<ProxyRequest<Request>>,
+ proxy_mgr: Arc<ProxyManager<Response>>,
+ ) -> Result<Self> {
+ let (tx, rx) = crossbeam_channel::bounded(1);
+ Ok(Self {
+ handle: None,
+ key: proxy_mgr.register_proxy(tx)?,
+ response_rx: rx,
+ handler_tx: ManuallyDrop::new(handler_tx),
+ proxy_mgr,
+ })
+ }
+
+ pub fn try_clone(&self) -> Result<Self> {
+ let mut clone = Self::new((*self.handler_tx).clone(), self.proxy_mgr.clone())?;
+ let (handle, token) = self
+ .handle
+ .as_ref()
+ .expect("proxy not connected to event loop");
+ clone.connect_event_loop(handle.clone(), *token);
+ Ok(clone)
+ }
+
+ pub fn call(&self, request: Request) -> Result<Response> {
+ match self.handler_tx.send((self.key, request)) {
+ Ok(_) => self.wake_connection(),
+ Err(_) => return Err(Error::new(ErrorKind::Other, "proxy send error")),
+ }
+ match self.response_rx.recv() {
+ Ok(resp) => Ok(resp),
+ Err(_) => Err(Error::new(ErrorKind::Other, "proxy recv error")),
+ }
+ }
+
+ pub(crate) fn connect_event_loop(&mut self, handle: EventLoopHandle, token: Token) {
+ self.handle = Some((handle, token));
+ }
+
+ fn wake_connection(&self) {
+ let (handle, token) = self
+ .handle
+ .as_ref()
+ .expect("proxy not connected to event loop");
+ handle.wake_connection(*token);
+ }
+}
+
+impl<Request, Response> Drop for Proxy<Request, Response> {
+ fn drop(&mut self) {
+ trace!("Proxy drop, waking EventLoop");
+ let _ = self.proxy_mgr.unregister_proxy(self.key);
+ // Must drop `handler_tx` before waking the connection, otherwise
+ // the wake may be processed before Sender is closed.
+ unsafe {
+ ManuallyDrop::drop(&mut self.handler_tx);
+ }
+ if self.handle.is_some() {
+ self.wake_connection()
+ }
+ }
+}
+
+const RPC_CLIENT_INITIAL_PROXIES: usize = 32; // Initial proxy pre-allocation per client.
+
+// Manage the Sender side of a ClientHandler's Proxies. Each Proxy registers itself with
+// the manager on initialization.
+#[derive(Debug)]
+struct ProxyManager<Response> {
+ proxies: Mutex<Option<Slab<Sender<Response>>>>,
+}
+
+impl<Response> ProxyManager<Response> {
+ fn new() -> Self {
+ Self {
+ proxies: Mutex::new(Some(Slab::with_capacity(RPC_CLIENT_INITIAL_PROXIES))),
+ }
+ }
+
+ // Register a Proxy's response Sender, returning a unique ID identifying
+ // the Proxy to the ClientHandler.
+ fn register_proxy(&self, tx: Sender<Response>) -> Result<ProxyKey> {
+ let mut proxies = self.proxies.lock().unwrap();
+ match &mut *proxies {
+ Some(proxies) => {
+ let entry = proxies.vacant_entry();
+ let key = entry.key();
+ entry.insert(tx);
+ Ok(key)
+ }
+ None => Err(Error::new(
+ ErrorKind::Other,
+ "register: proxy manager disconnected",
+ )),
+ }
+ }
+
+ fn unregister_proxy(&self, key: ProxyKey) -> Result<()> {
+ let mut proxies = self.proxies.lock().unwrap();
+ match &mut *proxies {
+ Some(proxies) => match proxies.try_remove(key) {
+ Some(_) => Ok(()),
+ None => Err(Error::new(
+ ErrorKind::Other,
+ "unregister: unknown proxy key",
+ )),
+ },
+ None => Err(Error::new(
+ ErrorKind::Other,
+ "unregister: proxy manager disconnected",
+ )),
+ }
+ }
+
+ // Deliver ClientHandler's Response to the Proxy associated with `key`.
+ fn deliver(&self, key: ProxyKey, resp: Response) -> Result<()> {
+ let proxies = self.proxies.lock().unwrap();
+ match &*proxies {
+ Some(proxies) => match proxies.get(key) {
+ Some(proxy) => {
+ drop(proxy.send(resp));
+ Ok(())
+ }
+ None => Err(Error::new(ErrorKind::Other, "deliver: unknown proxy key")),
+ },
+ None => Err(Error::new(
+ ErrorKind::Other,
+ "unregister: proxy manager disconnected",
+ )),
+ }
+ }
+
+ // ClientHandler disconnected, close Proxy Senders to unblock any waiters.
+ fn disconnect_handler(&self) {
+ *self.proxies.lock().unwrap() = None;
+ }
+}
+
+// Client-specific Handler implementation.
+// The IPC EventLoop Driver calls this to execute client-specific
+// RPC handling. Serialized messages sent via a Proxy are queued
+// for transmission when `produce` is called.
+// Deserialized messages are passed via `consume` to
+// trigger response completion by sending the response via a channel
+// connected to a ProxyResponse.
+pub(crate) struct ClientHandler<C: Client> {
+ messages: Receiver<ProxyRequest<C::ServerMessage>>,
+ // Proxies hold an Arc<ProxyManager> to register on initialization.
+ // When ClientHandler drops, any Proxies blocked on a response will
+ // error due to their Sender closing.
+ proxies: Arc<ProxyManager<C::ClientMessage>>,
+ in_flight: VecDeque<ProxyKey>,
+}
+
+impl<C: Client> ClientHandler<C> {
+ fn new(rx: Receiver<ProxyRequest<C::ServerMessage>>) -> ClientHandler<C> {
+ ClientHandler::<C> {
+ messages: rx,
+ proxies: Arc::new(ProxyManager::new()),
+ in_flight: VecDeque::with_capacity(RPC_CLIENT_INITIAL_PROXIES),
+ }
+ }
+
+ fn proxy_manager(&self) -> &Arc<ProxyManager<<C as Client>::ClientMessage>> {
+ &self.proxies
+ }
+}
+
+impl<C: Client> Handler for ClientHandler<C> {
+ type In = C::ClientMessage;
+ type Out = C::ServerMessage;
+
+ fn consume(&mut self, response: Self::In) -> Result<()> {
+ trace!("ClientHandler::consume");
+ // `proxy` identifies the waiting Proxy expecting `response`.
+ if let Some(proxy) = self.in_flight.pop_front() {
+ self.proxies.deliver(proxy, response)?;
+ } else {
+ return Err(Error::new(ErrorKind::Other, "request/response mismatch"));
+ }
+
+ Ok(())
+ }
+
+ fn produce(&mut self) -> Result<Option<Self::Out>> {
+ trace!("ClientHandler::produce");
+
+ // Try to get a new message
+ match self.messages.try_recv() {
+ Ok((proxy, request)) => {
+ trace!(" --> received request");
+ self.in_flight.push_back(proxy);
+ Ok(Some(request))
+ }
+ Err(crossbeam_channel::TryRecvError::Empty) => {
+ trace!(" --> no request");
+ Ok(None)
+ }
+ Err(e) => {
+ trace!(" --> client disconnected");
+ Err(io::Error::new(io::ErrorKind::ConnectionAborted, e))
+ }
+ }
+ }
+}
+
+impl<C: Client> Drop for ClientHandler<C> {
+ fn drop(&mut self) {
+ self.proxies.disconnect_handler();
+ }
+}
+
+#[allow(clippy::type_complexity)]
+pub(crate) fn make_client<C: Client>(
+) -> Result<(ClientHandler<C>, Proxy<C::ServerMessage, C::ClientMessage>)> {
+ let (tx, rx) = crossbeam_channel::bounded(RPC_CLIENT_INITIAL_PROXIES);
+
+ let handler = ClientHandler::new(rx);
+ let proxy_mgr = handler.proxy_manager().clone();
+
+ Ok((handler, Proxy::new(tx, proxy_mgr)?))
+}
+
+// Server-specific Handler implementation.
+// The IPC EventLoop Driver calls this to execute server-specific
+// RPC handling. Deserialized messages are passed via `consume` to the
+// associated `server` for processing. Server responses are then queued
+// for RPC to the associated client when `produce` is called.
+pub(crate) struct ServerHandler<S: Server> {
+ server: S,
+ in_flight: VecDeque<S::ClientMessage>,
+}
+
+impl<S: Server> Handler for ServerHandler<S> {
+ type In = S::ServerMessage;
+ type Out = S::ClientMessage;
+
+ fn consume(&mut self, message: Self::In) -> Result<()> {
+ trace!("ServerHandler::consume");
+ let response = self.server.process(message);
+ self.in_flight.push_back(response);
+ Ok(())
+ }
+
+ fn produce(&mut self) -> Result<Option<Self::Out>> {
+ trace!("ServerHandler::produce");
+
+ // Return the ready response
+ match self.in_flight.pop_front() {
+ Some(res) => {
+ trace!(" --> received response");
+ Ok(Some(res))
+ }
+ None => {
+ trace!(" --> no response ready");
+ Ok(None)
+ }
+ }
+ }
+}
+
+const RPC_SERVER_INITIAL_CLIENTS: usize = 32; // Initial client allocation per server.
+
+pub(crate) fn make_server<S: Server>(server: S) -> ServerHandler<S> {
+ ServerHandler::<S> {
+ server,
+ in_flight: VecDeque::with_capacity(RPC_SERVER_INITIAL_CLIENTS),
+ }
+}
diff --git a/third_party/rust/audioipc2/src/shm.rs b/third_party/rust/audioipc2/src/shm.rs
new file mode 100644
index 0000000000..2aaa92ef36
--- /dev/null
+++ b/third_party/rust/audioipc2/src/shm.rs
@@ -0,0 +1,334 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+#![allow(clippy::missing_safety_doc)]
+
+use crate::errors::*;
+use crate::PlatformHandle;
+use std::{convert::TryInto, ffi::c_void, slice};
+
+#[cfg(unix)]
+pub use unix::SharedMem;
+#[cfg(windows)]
+pub use windows::SharedMem;
+
+#[derive(Copy, Clone)]
+struct SharedMemView {
+ ptr: *mut c_void,
+ size: usize,
+}
+
+unsafe impl Send for SharedMemView {}
+
+impl SharedMemView {
+ pub unsafe fn get_slice(&self, size: usize) -> Result<&[u8]> {
+ let map = slice::from_raw_parts(self.ptr as _, self.size);
+ if size <= self.size {
+ Ok(&map[..size])
+ } else {
+ bail!("mmap size");
+ }
+ }
+
+ pub unsafe fn get_mut_slice(&mut self, size: usize) -> Result<&mut [u8]> {
+ let map = slice::from_raw_parts_mut(self.ptr as _, self.size);
+ if size <= self.size {
+ Ok(&mut map[..size])
+ } else {
+ bail!("mmap size")
+ }
+ }
+}
+
+#[cfg(unix)]
+mod unix {
+ use super::*;
+ use memmap2::{MmapMut, MmapOptions};
+ use std::fs::File;
+ use std::os::unix::io::{AsRawFd, FromRawFd};
+
+ #[cfg(target_os = "android")]
+ fn open_shm_file(_id: &str, size: usize) -> Result<File> {
+ unsafe {
+ let fd = ashmem::ASharedMemory_create(std::ptr::null(), size);
+ if fd >= 0 {
+ // Drop PROT_EXEC
+ let r = ashmem::ASharedMemory_setProt(fd, libc::PROT_READ | libc::PROT_WRITE);
+ assert_eq!(r, 0);
+ return Ok(File::from_raw_fd(fd.try_into().unwrap()));
+ }
+ Err(std::io::Error::last_os_error().into())
+ }
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn open_shm_file(id: &str, size: usize) -> Result<File> {
+ let file = open_shm_file_impl(id)?;
+ allocate_file(&file, size)?;
+ Ok(file)
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn open_shm_file_impl(id: &str) -> Result<File> {
+ use std::env::temp_dir;
+ use std::fs::{remove_file, OpenOptions};
+
+ let id_cstring = std::ffi::CString::new(id).unwrap();
+
+ #[cfg(target_os = "linux")]
+ {
+ unsafe {
+ let r = libc::syscall(libc::SYS_memfd_create, id_cstring.as_ptr(), 0);
+ if r >= 0 {
+ return Ok(File::from_raw_fd(r.try_into().unwrap()));
+ }
+ }
+
+ let mut path = std::path::PathBuf::from("/dev/shm");
+ path.push(id);
+
+ if let Ok(file) = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create_new(true)
+ .open(&path)
+ {
+ let _ = remove_file(&path);
+ return Ok(file);
+ }
+ }
+
+ unsafe {
+ let fd = libc::shm_open(
+ id_cstring.as_ptr(),
+ libc::O_RDWR | libc::O_CREAT | libc::O_EXCL,
+ 0o600,
+ );
+ if fd >= 0 {
+ libc::shm_unlink(id_cstring.as_ptr());
+ return Ok(File::from_raw_fd(fd));
+ }
+ }
+
+ let mut path = temp_dir();
+ path.push(id);
+
+ let file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create_new(true)
+ .open(&path)?;
+
+ let _ = remove_file(&path);
+ Ok(file)
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn handle_enospc(s: &str) -> Result<()> {
+ let err = std::io::Error::last_os_error();
+ let errno = err.raw_os_error().unwrap_or(0);
+ assert_ne!(errno, 0);
+ debug!("allocate_file: {} failed errno={}", s, errno);
+ if errno == libc::ENOSPC {
+ return Err(err.into());
+ }
+ Ok(())
+ }
+
+ #[cfg(not(target_os = "android"))]
+ fn allocate_file(file: &File, size: usize) -> Result<()> {
+ // First, set the file size. This may create a sparse file on
+ // many systems, which can fail with SIGBUS when accessed via a
+ // mapping and the lazy backing allocation fails due to low disk
+ // space. To avoid this, try to force the entire file to be
+ // preallocated before mapping using OS-specific approaches below.
+
+ file.set_len(size.try_into().unwrap())?;
+
+ let fd = file.as_raw_fd();
+ let size: libc::off_t = size.try_into().unwrap();
+
+ // Try Linux-specific fallocate.
+ #[cfg(target_os = "linux")]
+ {
+ if unsafe { libc::fallocate(fd, 0, 0, size) } == 0 {
+ return Ok(());
+ }
+ handle_enospc("fallocate()")?;
+ }
+
+ // Try macOS-specific fcntl.
+ #[cfg(target_os = "macos")]
+ {
+ let params = libc::fstore_t {
+ fst_flags: libc::F_ALLOCATEALL,
+ fst_posmode: libc::F_PEOFPOSMODE,
+ fst_offset: 0,
+ fst_length: size,
+ fst_bytesalloc: 0,
+ };
+ if unsafe { libc::fcntl(fd, libc::F_PREALLOCATE, &params) } == 0 {
+ return Ok(());
+ }
+ handle_enospc("fcntl(F_PREALLOCATE)")?;
+ }
+
+ // Fall back to portable version, where available.
+ #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly"))]
+ {
+ if unsafe { libc::posix_fallocate(fd, 0, size) } == 0 {
+ return Ok(());
+ }
+ handle_enospc("posix_fallocate()")?;
+ }
+
+ Ok(())
+ }
+
+ pub struct SharedMem {
+ file: File,
+ _mmap: MmapMut,
+ view: SharedMemView,
+ }
+
+ impl SharedMem {
+ pub fn new(id: &str, size: usize) -> Result<SharedMem> {
+ let file = open_shm_file(id, size)?;
+ let mut mmap = unsafe { MmapOptions::new().len(size).map_mut(&file)? };
+ assert_eq!(mmap.len(), size);
+ let view = SharedMemView {
+ ptr: mmap.as_mut_ptr() as _,
+ size,
+ };
+ Ok(SharedMem {
+ file,
+ _mmap: mmap,
+ view,
+ })
+ }
+
+ pub unsafe fn make_handle(&self) -> Result<PlatformHandle> {
+ PlatformHandle::duplicate(self.file.as_raw_fd()).map_err(|e| e.into())
+ }
+
+ pub unsafe fn from(handle: PlatformHandle, size: usize) -> Result<SharedMem> {
+ let file = File::from_raw_fd(handle.into_raw());
+ let mut mmap = MmapOptions::new().len(size).map_mut(&file)?;
+ assert_eq!(mmap.len(), size);
+ let view = SharedMemView {
+ ptr: mmap.as_mut_ptr() as _,
+ size,
+ };
+ Ok(SharedMem {
+ file,
+ _mmap: mmap,
+ view,
+ })
+ }
+
+ pub unsafe fn get_slice(&self, size: usize) -> Result<&[u8]> {
+ self.view.get_slice(size)
+ }
+
+ pub unsafe fn get_mut_slice(&mut self, size: usize) -> Result<&mut [u8]> {
+ self.view.get_mut_slice(size)
+ }
+
+ pub fn get_size(&self) -> usize {
+ self.view.size
+ }
+ }
+}
+
+#[cfg(windows)]
+mod windows {
+ use super::*;
+ use std::ptr;
+ use winapi::{
+ shared::{minwindef::DWORD, ntdef::HANDLE},
+ um::{
+ handleapi::CloseHandle,
+ memoryapi::{MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS},
+ winbase::CreateFileMappingA,
+ winnt::PAGE_READWRITE,
+ },
+ };
+
+ use crate::INVALID_HANDLE_VALUE;
+
+ pub struct SharedMem {
+ handle: HANDLE,
+ view: SharedMemView,
+ }
+
+ unsafe impl Send for SharedMem {}
+
+ impl Drop for SharedMem {
+ fn drop(&mut self) {
+ unsafe {
+ let ok = UnmapViewOfFile(self.view.ptr);
+ assert_ne!(ok, 0);
+ let ok = CloseHandle(self.handle);
+ assert_ne!(ok, 0);
+ }
+ }
+ }
+
+ impl SharedMem {
+ pub fn new(_id: &str, size: usize) -> Result<SharedMem> {
+ unsafe {
+ let handle = CreateFileMappingA(
+ INVALID_HANDLE_VALUE,
+ ptr::null_mut(),
+ PAGE_READWRITE,
+ (size as u64 >> 32).try_into().unwrap(),
+ (size as u64 & (DWORD::MAX as u64)).try_into().unwrap(),
+ ptr::null(),
+ );
+ if handle.is_null() {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ let ptr = MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
+ if ptr.is_null() {
+ return Err(std::io::Error::last_os_error().into());
+ }
+
+ Ok(SharedMem {
+ handle,
+ view: SharedMemView { ptr, size },
+ })
+ }
+ }
+
+ pub unsafe fn make_handle(&self) -> Result<PlatformHandle> {
+ PlatformHandle::duplicate(self.handle).map_err(|e| e.into())
+ }
+
+ pub unsafe fn from(handle: PlatformHandle, size: usize) -> Result<SharedMem> {
+ let handle = handle.into_raw();
+ let ptr = MapViewOfFile(handle, FILE_MAP_ALL_ACCESS, 0, 0, size);
+ if ptr.is_null() {
+ return Err(std::io::Error::last_os_error().into());
+ }
+ Ok(SharedMem {
+ handle,
+ view: SharedMemView { ptr, size },
+ })
+ }
+
+ pub unsafe fn get_slice(&self, size: usize) -> Result<&[u8]> {
+ self.view.get_slice(size)
+ }
+
+ pub unsafe fn get_mut_slice(&mut self, size: usize) -> Result<&mut [u8]> {
+ self.view.get_mut_slice(size)
+ }
+
+ pub fn get_size(&self) -> usize {
+ self.view.size
+ }
+ }
+}
diff --git a/third_party/rust/audioipc2/src/sys/mod.rs b/third_party/rust/audioipc2/src/sys/mod.rs
new file mode 100644
index 0000000000..0bcfdaa15e
--- /dev/null
+++ b/third_party/rust/audioipc2/src/sys/mod.rs
@@ -0,0 +1,77 @@
+// Copyright © 2021 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use std::{collections::VecDeque, io::Result};
+
+use bytes::BytesMut;
+use mio::{event::Source, Interest, Registry, Token};
+
+#[cfg(unix)]
+mod unix;
+use crate::messages::RemoteHandle;
+
+#[cfg(unix)]
+pub use self::unix::*;
+
+#[cfg(windows)]
+mod windows;
+#[cfg(windows)]
+pub use self::windows::*;
+
+impl Source for Pipe {
+ fn register(&mut self, registry: &Registry, token: Token, interests: Interest) -> Result<()> {
+ self.io.register(registry, token, interests)
+ }
+
+ fn reregister(&mut self, registry: &Registry, token: Token, interests: Interest) -> Result<()> {
+ self.io.reregister(registry, token, interests)
+ }
+
+ fn deregister(&mut self, registry: &Registry) -> Result<()> {
+ self.io.deregister(registry)
+ }
+}
+
+const HANDLE_QUEUE_LIMIT: usize = 16;
+
+#[derive(Debug)]
+pub struct ConnectionBuffer {
+ pub buf: BytesMut,
+ handles: VecDeque<RemoteHandle>,
+}
+
+impl ConnectionBuffer {
+ pub fn with_capacity(cap: usize) -> Self {
+ ConnectionBuffer {
+ buf: BytesMut::with_capacity(cap),
+ handles: VecDeque::with_capacity(HANDLE_QUEUE_LIMIT),
+ }
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.buf.is_empty()
+ }
+
+ pub fn push_handle(&mut self, handle: RemoteHandle) {
+ assert!(self.handles.len() < self.handles.capacity());
+ self.handles.push_back(handle)
+ }
+
+ pub fn pop_handle(&mut self) -> Option<RemoteHandle> {
+ self.handles.pop_front()
+ }
+}
+
+pub trait RecvMsg {
+ // Receive data from the associated connection. `recv_msg` expects the capacity of
+ // the `ConnectionBuffer` members have been adjusted appropriately by the caller.
+ fn recv_msg(&mut self, buf: &mut ConnectionBuffer) -> Result<usize>;
+}
+
+pub trait SendMsg {
+ // Send data on the associated connection. `send_msg` consumes and adjusts the length of the
+ // `ConnectionBuffer` members based on the size of the successful send operation.
+ fn send_msg(&mut self, buf: &mut ConnectionBuffer) -> Result<usize>;
+}
diff --git a/third_party/rust/audioipc2/src/sys/unix/cmsg.rs b/third_party/rust/audioipc2/src/sys/unix/cmsg.rs
new file mode 100644
index 0000000000..0451f11926
--- /dev/null
+++ b/third_party/rust/audioipc2/src/sys/unix/cmsg.rs
@@ -0,0 +1,103 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use crate::sys::HANDLE_QUEUE_LIMIT;
+use bytes::{BufMut, BytesMut};
+use libc::{self, cmsghdr};
+use std::convert::TryInto;
+use std::os::unix::io::RawFd;
+use std::{mem, slice};
+
+trait AsBytes {
+ fn as_bytes(&self) -> &[u8];
+}
+
+impl<'a, T: Sized> AsBytes for &'a [T] {
+ fn as_bytes(&self) -> &[u8] {
+ // TODO: This should account for the alignment of T
+ let byte_count = self.len() * mem::size_of::<T>();
+ unsafe { slice::from_raw_parts(self.as_ptr() as *const _, byte_count) }
+ }
+}
+
+// Encode `handles` into a cmsghdr in `buf`.
+pub fn encode_handles(cmsg: &mut BytesMut, handles: &[RawFd]) {
+ assert!(handles.len() <= HANDLE_QUEUE_LIMIT);
+ let msg = handles.as_bytes();
+
+ let cmsg_space = space(msg.len());
+ assert!(cmsg.remaining_mut() >= cmsg_space);
+
+ // Some definitions of cmsghdr contain padding. Rather
+ // than try to keep an up-to-date #cfg list to handle
+ // that, just use a pre-zeroed struct to fill out any
+ // fields we don't care about.
+ let zeroed = unsafe { mem::zeroed() };
+ #[allow(clippy::needless_update)]
+ // `cmsg_len` is `usize` on some platforms, `u32` on others.
+ #[allow(clippy::useless_conversion)]
+ let cmsghdr = cmsghdr {
+ cmsg_len: len(msg.len()).try_into().unwrap(),
+ cmsg_level: libc::SOL_SOCKET,
+ cmsg_type: libc::SCM_RIGHTS,
+ ..zeroed
+ };
+
+ unsafe {
+ let cmsghdr_ptr = cmsg.chunk_mut().as_mut_ptr();
+ std::ptr::copy_nonoverlapping(
+ &cmsghdr as *const _ as *const _,
+ cmsghdr_ptr,
+ mem::size_of::<cmsghdr>(),
+ );
+ let cmsg_data_ptr = libc::CMSG_DATA(cmsghdr_ptr as _);
+ std::ptr::copy_nonoverlapping(msg.as_ptr(), cmsg_data_ptr, msg.len());
+ cmsg.advance_mut(cmsg_space);
+ }
+}
+
+// Decode `buf` containing a cmsghdr with one or more handle(s).
+pub fn decode_handles(buf: &mut BytesMut) -> arrayvec::ArrayVec<RawFd, HANDLE_QUEUE_LIMIT> {
+ let mut fds = arrayvec::ArrayVec::<RawFd, HANDLE_QUEUE_LIMIT>::new();
+
+ let cmsghdr_len = len(0);
+
+ if buf.len() < cmsghdr_len {
+ // No more entries---not enough data in `buf` for a
+ // complete message.
+ return fds;
+ }
+
+ let cmsg: &cmsghdr = unsafe { &*(buf.as_ptr() as *const _) };
+ let cmsg_len = cmsg.cmsg_len as usize;
+
+ match (cmsg.cmsg_level, cmsg.cmsg_type) {
+ (libc::SOL_SOCKET, libc::SCM_RIGHTS) => {
+ trace!("Found SCM_RIGHTS...");
+ let slice = &buf[cmsghdr_len..cmsg_len];
+ let slice = unsafe {
+ slice::from_raw_parts(
+ slice.as_ptr() as *const _,
+ slice.len() / mem::size_of::<i32>(),
+ )
+ };
+ fds.try_extend_from_slice(slice).unwrap();
+ }
+ (level, kind) => {
+ trace!("Skipping cmsg level, {}, type={}...", level, kind);
+ }
+ }
+
+ assert!(fds.len() <= HANDLE_QUEUE_LIMIT);
+ fds
+}
+
+fn len(len: usize) -> usize {
+ unsafe { libc::CMSG_LEN(len.try_into().unwrap()) as usize }
+}
+
+pub fn space(len: usize) -> usize {
+ unsafe { libc::CMSG_SPACE(len.try_into().unwrap()) as usize }
+}
diff --git a/third_party/rust/audioipc2/src/sys/unix/cmsghdr.c b/third_party/rust/audioipc2/src/sys/unix/cmsghdr.c
new file mode 100644
index 0000000000..82d7852867
--- /dev/null
+++ b/third_party/rust/audioipc2/src/sys/unix/cmsghdr.c
@@ -0,0 +1,23 @@
+#include <sys/socket.h>
+#include <inttypes.h>
+#include <string.h>
+
+const uint8_t*
+cmsghdr_bytes(size_t* size)
+{
+ int myfd = 0;
+
+ static union {
+ uint8_t buf[CMSG_SPACE(sizeof(myfd))];
+ struct cmsghdr align;
+ } u;
+
+ u.align.cmsg_len = CMSG_LEN(sizeof(myfd));
+ u.align.cmsg_level = SOL_SOCKET;
+ u.align.cmsg_type = SCM_RIGHTS;
+
+ memcpy(CMSG_DATA(&u.align), &myfd, sizeof(myfd));
+
+ *size = sizeof(u);
+ return (const uint8_t*)&u.buf;
+}
diff --git a/third_party/rust/audioipc2/src/sys/unix/mod.rs b/third_party/rust/audioipc2/src/sys/unix/mod.rs
new file mode 100644
index 0000000000..84f3f1edf2
--- /dev/null
+++ b/third_party/rust/audioipc2/src/sys/unix/mod.rs
@@ -0,0 +1,126 @@
+// Copyright © 2021 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use std::io::Result;
+use std::os::unix::prelude::{AsRawFd, FromRawFd};
+
+use bytes::{Buf, BufMut, BytesMut};
+use iovec::IoVec;
+use mio::net::UnixStream;
+
+use crate::PlatformHandle;
+
+use super::{ConnectionBuffer, RecvMsg, SendMsg, HANDLE_QUEUE_LIMIT};
+
+pub mod cmsg;
+mod msg;
+
+pub struct Pipe {
+ pub(crate) io: UnixStream,
+ cmsg: BytesMut,
+}
+
+impl Pipe {
+ fn new(io: UnixStream) -> Self {
+ Pipe {
+ io,
+ cmsg: BytesMut::with_capacity(cmsg::space(
+ std::mem::size_of::<i32>() * HANDLE_QUEUE_LIMIT,
+ )),
+ }
+ }
+}
+
+// Create a connected "pipe" pair. The `Pipe` is the server end,
+// the `PlatformHandle` is the client end to be remoted.
+pub fn make_pipe_pair() -> Result<(Pipe, PlatformHandle)> {
+ let (server, client) = UnixStream::pair()?;
+ Ok((Pipe::new(server), PlatformHandle::from(client)))
+}
+
+impl Pipe {
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn from_raw_handle(handle: crate::PlatformHandle) -> Pipe {
+ Pipe::new(UnixStream::from_raw_fd(handle.into_raw()))
+ }
+
+ pub fn shutdown(&mut self) -> Result<()> {
+ self.io.shutdown(std::net::Shutdown::Both)
+ }
+}
+
+impl RecvMsg for Pipe {
+ // Receive data (and fds) from the associated connection. `recv_msg` expects the capacity of
+ // the `ConnectionBuffer` members has been adjusted appropriate by the caller.
+ fn recv_msg(&mut self, buf: &mut ConnectionBuffer) -> Result<usize> {
+ assert!(buf.buf.remaining_mut() > 0);
+ // TODO: MSG_CMSG_CLOEXEC not portable.
+ // TODO: MSG_NOSIGNAL not portable; macOS can set socket option SO_NOSIGPIPE instead.
+ #[cfg(target_os = "linux")]
+ let flags = libc::MSG_CMSG_CLOEXEC | libc::MSG_NOSIGNAL;
+ #[cfg(not(target_os = "linux"))]
+ let flags = 0;
+ let r = unsafe {
+ let chunk = buf.buf.chunk_mut();
+ let slice = std::slice::from_raw_parts_mut(chunk.as_mut_ptr(), chunk.len());
+ let mut iovec = [<&mut IoVec>::from(slice)];
+ msg::recv_msg_with_flags(
+ self.io.as_raw_fd(),
+ &mut iovec,
+ self.cmsg.chunk_mut(),
+ flags,
+ )
+ };
+ match r {
+ Ok((n, cmsg_n, msg_flags)) => unsafe {
+ trace!("recv_msg_with_flags flags={}", msg_flags);
+ buf.buf.advance_mut(n);
+ self.cmsg.advance_mut(cmsg_n);
+ let handles = cmsg::decode_handles(&mut self.cmsg);
+ self.cmsg.clear();
+ let unused = 0;
+ for h in handles {
+ buf.push_handle(super::RemoteHandle::new(h, unused));
+ }
+ Ok(n)
+ },
+ Err(e) => Err(e),
+ }
+ }
+}
+
+impl SendMsg for Pipe {
+ // Send data (and fds) on the associated connection. `send_msg` adjusts the length of the
+ // `ConnectionBuffer` members based on the size of the successful send operation.
+ fn send_msg(&mut self, buf: &mut ConnectionBuffer) -> Result<usize> {
+ assert!(!buf.buf.is_empty());
+ if !buf.handles.is_empty() {
+ let mut handles = [-1i32; HANDLE_QUEUE_LIMIT];
+ for (i, h) in buf.handles.iter().enumerate() {
+ handles[i] = h.handle;
+ }
+ cmsg::encode_handles(&mut self.cmsg, &handles[..buf.handles.len()]);
+ }
+ let r = {
+ // TODO: MSG_NOSIGNAL not portable; macOS can set socket option SO_NOSIGPIPE instead.
+ #[cfg(target_os = "linux")]
+ let flags = libc::MSG_NOSIGNAL;
+ #[cfg(not(target_os = "linux"))]
+ let flags = 0;
+ let iovec = [<&IoVec>::from(&buf.buf[..buf.buf.len()])];
+ msg::send_msg_with_flags(self.io.as_raw_fd(), &iovec, &self.cmsg, flags)
+ };
+ match r {
+ Ok(n) => {
+ buf.buf.advance(n);
+ // Discard sent handles.
+ while buf.handles.pop_front().is_some() {}
+ self.cmsg.clear();
+ Ok(n)
+ }
+ Err(e) => Err(e),
+ }
+ }
+}
diff --git a/third_party/rust/audioipc2/src/sys/unix/msg.rs b/third_party/rust/audioipc2/src/sys/unix/msg.rs
new file mode 100644
index 0000000000..a21a893b93
--- /dev/null
+++ b/third_party/rust/audioipc2/src/sys/unix/msg.rs
@@ -0,0 +1,81 @@
+// Copyright © 2017 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details.
+
+use bytes::buf::UninitSlice;
+use iovec::unix;
+use iovec::IoVec;
+use std::os::unix::io::RawFd;
+use std::{cmp, io, mem, ptr};
+
+fn cvt(r: libc::ssize_t) -> io::Result<usize> {
+ if r == -1 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(r as usize)
+ }
+}
+
+// Convert return of -1 into error message, handling retry on EINTR
+fn cvt_r<F: FnMut() -> libc::ssize_t>(mut f: F) -> io::Result<usize> {
+ loop {
+ match cvt(f()) {
+ Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
+ other => return other,
+ }
+ }
+}
+
+pub(crate) fn recv_msg_with_flags(
+ socket: RawFd,
+ bufs: &mut [&mut IoVec],
+ cmsg: &mut UninitSlice,
+ flags: libc::c_int,
+) -> io::Result<(usize, usize, libc::c_int)> {
+ let slice = unix::as_os_slice_mut(bufs);
+ let len = cmp::min(<libc::c_int>::max_value() as usize, slice.len());
+ let (control, controllen) = if cmsg.len() == 0 {
+ (ptr::null_mut(), 0)
+ } else {
+ (cmsg.as_mut_ptr() as _, cmsg.len())
+ };
+
+ let mut msghdr: libc::msghdr = unsafe { mem::zeroed() };
+ msghdr.msg_name = ptr::null_mut();
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = slice.as_mut_ptr();
+ msghdr.msg_iovlen = len as _;
+ msghdr.msg_control = control;
+ msghdr.msg_controllen = controllen as _;
+
+ let n = cvt_r(|| unsafe { libc::recvmsg(socket, &mut msghdr as *mut _, flags) })?;
+
+ let controllen = msghdr.msg_controllen as usize;
+ Ok((n, controllen, msghdr.msg_flags))
+}
+
+pub(crate) fn send_msg_with_flags(
+ socket: RawFd,
+ bufs: &[&IoVec],
+ cmsg: &[u8],
+ flags: libc::c_int,
+) -> io::Result<usize> {
+ let slice = unix::as_os_slice(bufs);
+ let len = cmp::min(<libc::c_int>::max_value() as usize, slice.len());
+ let (control, controllen) = if cmsg.is_empty() {
+ (ptr::null_mut(), 0)
+ } else {
+ (cmsg.as_ptr() as *mut _, cmsg.len())
+ };
+
+ let mut msghdr: libc::msghdr = unsafe { mem::zeroed() };
+ msghdr.msg_name = ptr::null_mut();
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = slice.as_ptr() as *mut _;
+ msghdr.msg_iovlen = len as _;
+ msghdr.msg_control = control;
+ msghdr.msg_controllen = controllen as _;
+
+ cvt_r(|| unsafe { libc::sendmsg(socket, &msghdr as *const _, flags) })
+}
diff --git a/third_party/rust/audioipc2/src/sys/windows/mod.rs b/third_party/rust/audioipc2/src/sys/windows/mod.rs
new file mode 100644
index 0000000000..6352bbe5a7
--- /dev/null
+++ b/third_party/rust/audioipc2/src/sys/windows/mod.rs
@@ -0,0 +1,102 @@
+// Copyright © 2021 Mozilla Foundation
+//
+// This program is made available under an ISC-style license. See the
+// accompanying file LICENSE for details
+
+use std::{
+ fs::OpenOptions,
+ io::{Read, Write},
+ os::windows::prelude::{FromRawHandle, OpenOptionsExt},
+ sync::atomic::{AtomicUsize, Ordering},
+};
+
+use std::io::Result;
+
+use bytes::{Buf, BufMut};
+use mio::windows::NamedPipe;
+use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
+
+use crate::PlatformHandle;
+
+use super::{ConnectionBuffer, RecvMsg, SendMsg};
+
+pub struct Pipe {
+ pub(crate) io: NamedPipe,
+}
+
+// Create a connected "pipe" pair. The `Pipe` is the server end,
+// the `PlatformHandle` is the client end to be remoted.
+pub fn make_pipe_pair() -> Result<(Pipe, PlatformHandle)> {
+ let pipe_name = get_pipe_name();
+ let server = NamedPipe::new(&pipe_name)?;
+
+ let client = {
+ let mut opts = OpenOptions::new();
+ opts.read(true)
+ .write(true)
+ .custom_flags(FILE_FLAG_OVERLAPPED);
+ let file = opts.open(&pipe_name)?;
+ PlatformHandle::from(file)
+ };
+
+ Ok((Pipe::new(server), client))
+}
+
+static PIPE_ID: AtomicUsize = AtomicUsize::new(0);
+
+fn get_pipe_name() -> String {
+ let pid = std::process::id();
+ let pipe_id = PIPE_ID.fetch_add(1, Ordering::Relaxed);
+ format!("\\\\.\\pipe\\LOCAL\\cubeb-pipe-{}-{}", pid, pipe_id)
+}
+
+impl Pipe {
+ pub fn new(io: NamedPipe) -> Self {
+ Self { io }
+ }
+
+ #[allow(clippy::missing_safety_doc)]
+ pub unsafe fn from_raw_handle(handle: crate::PlatformHandle) -> Pipe {
+ Pipe::new(NamedPipe::from_raw_handle(handle.into_raw()))
+ }
+
+ pub fn shutdown(&mut self) -> Result<()> {
+ self.io.disconnect()
+ }
+}
+
+impl RecvMsg for Pipe {
+ // Receive data from the associated connection. `recv_msg` expects the capacity of
+ // the `ConnectionBuffer` members has been adjusted appropriate by the caller.
+ fn recv_msg(&mut self, buf: &mut ConnectionBuffer) -> Result<usize> {
+ assert!(buf.buf.remaining_mut() > 0);
+ let r = unsafe {
+ let chunk = buf.buf.chunk_mut();
+ let slice = std::slice::from_raw_parts_mut(chunk.as_mut_ptr(), chunk.len());
+ self.io.read(slice)
+ };
+ match r {
+ Ok(n) => unsafe {
+ buf.buf.advance_mut(n);
+ Ok(n)
+ },
+ e => e,
+ }
+ }
+}
+
+impl SendMsg for Pipe {
+ // Send data on the associated connection. `send_msg` adjusts the length of the
+ // `ConnectionBuffer` members based on the size of the successful send operation.
+ fn send_msg(&mut self, buf: &mut ConnectionBuffer) -> Result<usize> {
+ assert!(!buf.buf.is_empty());
+ let r = self.io.write(&buf.buf[..buf.buf.len()]);
+ if let Ok(n) = r {
+ buf.buf.advance(n);
+ while let Some(mut handle) = buf.pop_handle() {
+ handle.mark_sent()
+ }
+ }
+ r
+ }
+}