Harden dependency and supply-chain policy (#100)
Some checks failed
Native code generation / deterministic (push) Failing after 2m6s
Imaging and meshing gate / native (push) Failing after 2m48s
JPEG 2000 feature / linux (push) Successful in 2m43s
Release platform and feature matrix / audit (push) Successful in 35s
Native Rust workspace compile / compile (push) Failing after 57s
Skia feature / linux (push) Successful in 31m0s
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled

This commit is contained in:
2026-08-11 22:57:13 +00:00
parent 9779e50ce9
commit 9e3b532a7e
21 changed files with 2086 additions and 78 deletions

View File

@@ -0,0 +1,28 @@
[package]
name = "libremetaverse-opus"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Safe system-libopus adapter for MetaCrate"
publish = false
build = "build.rs"
[build-dependencies]
pkg-config = "0.3"
vcpkg = "0.2"
# cargo-machete cannot associate target-conditional build.rs references with
# build dependencies. Both crates are invoked directly in build.rs.
[package.metadata.cargo-machete]
ignored = ["pkg-config", "vcpkg"]
# Unsafe code is permitted only inside this reviewed native ABI boundary.
# Every consumer receives an owned, validated safe Rust API.
[lints.rust]
unsafe_code = "allow"
[lints.clippy]
all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }

View File

@@ -0,0 +1,16 @@
use std::env;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let target = env::var("TARGET").expect("Cargo provides TARGET");
if target.contains("msvc") {
vcpkg::Config::new()
.find_package("opus")
.expect("WebRTC voice requires libopus 1.3 or newer from vcpkg");
} else {
pkg_config::Config::new()
.atleast_version("1.3")
.probe("opus")
.expect("WebRTC voice requires pkg-config and libopus 1.3 or newer");
}
}

View File

@@ -0,0 +1,313 @@
//! Safe, narrowly scoped access to the system `libopus` codec.
//!
//! The foreign ABI, raw handles, and error pointers are contained in this
//! crate. Callers can move an encoder or decoder between threads, but all
//! codec operations require exclusive access because libopus mutates them.
#![allow(unsafe_code)]
use std::ffi::{CStr, c_char, c_int};
use std::fmt;
use std::ptr::NonNull;
const OPUS_OK: c_int = 0;
const OPUS_BAD_ARG: c_int = -1;
const OPUS_ALLOC_FAIL: c_int = -7;
const OPUS_APPLICATION_VOIP: c_int = 2_048;
const MAX_PACKET_BYTES: usize = 1_275;
#[repr(C)]
struct OpusEncoder {
_private: [u8; 0],
}
#[repr(C)]
struct OpusDecoder {
_private: [u8; 0],
}
#[link(name = "opus")]
unsafe extern "C" {
fn opus_encoder_create(
sample_rate: c_int,
channels: c_int,
application: c_int,
error: *mut c_int,
) -> *mut OpusEncoder;
fn opus_encoder_destroy(encoder: *mut OpusEncoder);
fn opus_encode(
encoder: *mut OpusEncoder,
pcm: *const i16,
frame_size: c_int,
output: *mut u8,
max_output_bytes: c_int,
) -> c_int;
fn opus_decoder_create(
sample_rate: c_int,
channels: c_int,
error: *mut c_int,
) -> *mut OpusDecoder;
fn opus_decoder_destroy(decoder: *mut OpusDecoder);
fn opus_decode(
decoder: *mut OpusDecoder,
packet: *const u8,
packet_bytes: c_int,
pcm: *mut i16,
frame_size: c_int,
decode_fec: c_int,
) -> c_int;
fn opus_strerror(error: c_int) -> *const c_char;
}
/// Channel layouts accepted by the Opus multirate API.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i32)]
pub enum Channels {
Mono = 1,
Stereo = 2,
}
impl Channels {
const fn count(self) -> usize {
self as usize
}
}
/// Stable libopus error code returned by a codec operation.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Error(c_int);
impl Error {
#[must_use]
pub const fn code(self) -> i32 {
self.0
}
}
impl fmt::Display for Error {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = unsafe {
let pointer = opus_strerror(self.0);
(!pointer.is_null()).then(|| CStr::from_ptr(pointer).to_string_lossy())
};
match message {
Some(message) => write!(formatter, "libopus error {}: {message}", self.0),
None => write!(formatter, "libopus error {}", self.0),
}
}
}
impl std::error::Error for Error {}
/// Owned Opus `VoIP` encoder.
pub struct Encoder {
raw: NonNull<OpusEncoder>,
channels: Channels,
sample_rate: usize,
}
// libopus codec handles have no thread affinity. Exclusive method access
// prevents concurrent calls after the owned handle is moved to another thread.
unsafe impl Send for Encoder {}
impl Encoder {
/// Creates a `VoIP` encoder for a supported Opus sample rate.
///
/// # Errors
///
/// Returns the native libopus error when the configuration is unsupported
/// or the codec cannot be allocated.
pub fn voip(sample_rate: u32, channels: Channels) -> Result<Self, Error> {
let sample_rate = validate_sample_rate(sample_rate)?;
let sample_rate_usize = usize::try_from(sample_rate).map_err(|_| Error(OPUS_BAD_ARG))?;
let mut error = OPUS_OK;
let raw = unsafe {
opus_encoder_create(
sample_rate,
channels as c_int,
OPUS_APPLICATION_VOIP,
&raw mut error,
)
};
let raw = NonNull::new(raw).ok_or(Error(if error == OPUS_OK {
OPUS_ALLOC_FAIL
} else {
error
}))?;
if error != OPUS_OK {
unsafe { opus_encoder_destroy(raw.as_ptr()) };
return Err(Error(error));
}
Ok(Self {
raw,
channels,
sample_rate: sample_rate_usize,
})
}
/// Encodes one complete Opus frame into a caller-owned packet buffer.
///
/// # Errors
///
/// Rejects malformed channel/frame lengths and oversized output buffers,
/// and returns any native codec failure.
pub fn encode(&mut self, pcm: &[i16], output: &mut [u8]) -> Result<usize, Error> {
let channels = self.channels.count();
if !pcm.len().is_multiple_of(channels) || output.is_empty() {
return Err(Error(OPUS_BAD_ARG));
}
let frame_size = pcm.len() / channels;
let base = self.sample_rate / 400;
if ![base, base * 2, base * 4, base * 8, base * 16, base * 24].contains(&frame_size) {
return Err(Error(OPUS_BAD_ARG));
}
let result = unsafe {
opus_encode(
self.raw.as_ptr(),
pcm.as_ptr(),
c_int::try_from(frame_size).map_err(|_| Error(OPUS_BAD_ARG))?,
output.as_mut_ptr(),
c_int::try_from(output.len()).map_err(|_| Error(OPUS_BAD_ARG))?,
)
};
result_to_size(result)
}
}
impl Drop for Encoder {
fn drop(&mut self) {
unsafe { opus_encoder_destroy(self.raw.as_ptr()) };
}
}
/// Owned Opus decoder.
pub struct Decoder {
raw: NonNull<OpusDecoder>,
channels: Channels,
sample_rate: usize,
}
// See the Encoder safety argument above.
unsafe impl Send for Decoder {}
impl Decoder {
/// Creates a decoder for a supported Opus sample rate.
///
/// # Errors
///
/// Returns the native libopus error when the configuration is unsupported
/// or the codec cannot be allocated.
pub fn new(sample_rate: u32, channels: Channels) -> Result<Self, Error> {
let sample_rate = validate_sample_rate(sample_rate)?;
let sample_rate_usize = usize::try_from(sample_rate).map_err(|_| Error(OPUS_BAD_ARG))?;
let mut error = OPUS_OK;
let raw = unsafe { opus_decoder_create(sample_rate, channels as c_int, &raw mut error) };
let raw = NonNull::new(raw).ok_or(Error(if error == OPUS_OK {
OPUS_ALLOC_FAIL
} else {
error
}))?;
if error != OPUS_OK {
unsafe { opus_decoder_destroy(raw.as_ptr()) };
return Err(Error(error));
}
Ok(Self {
raw,
channels,
sample_rate: sample_rate_usize,
})
}
/// Decodes one Opus packet, or performs packet-loss concealment for `None`.
///
/// # Errors
///
/// Rejects invalid output alignment/length and packets too large for the
/// libopus single-stream format, and returns any native codec failure.
pub fn decode(
&mut self,
packet: Option<&[u8]>,
pcm: &mut [i16],
decode_fec: bool,
) -> Result<usize, Error> {
let channels = self.channels.count();
if !pcm.len().is_multiple_of(channels)
|| pcm.is_empty()
|| pcm.len() / channels > self.sample_rate * 120 / 1_000
|| packet.is_some_and(|value| value.is_empty() || value.len() > MAX_PACKET_BYTES)
{
return Err(Error(OPUS_BAD_ARG));
}
let frame_size = pcm.len() / channels;
let (packet_pointer, packet_bytes) = packet.map_or((std::ptr::null(), 0), |value| {
(
value.as_ptr(),
c_int::try_from(value.len()).unwrap_or(c_int::MAX),
)
});
let result = unsafe {
opus_decode(
self.raw.as_ptr(),
packet_pointer,
packet_bytes,
pcm.as_mut_ptr(),
c_int::try_from(frame_size).map_err(|_| Error(OPUS_BAD_ARG))?,
c_int::from(decode_fec),
)
};
result_to_size(result)
}
}
impl Drop for Decoder {
fn drop(&mut self) {
unsafe { opus_decoder_destroy(self.raw.as_ptr()) };
}
}
fn validate_sample_rate(sample_rate: u32) -> Result<c_int, Error> {
if matches!(sample_rate, 8_000 | 12_000 | 16_000 | 24_000 | 48_000) {
c_int::try_from(sample_rate).map_err(|_| Error(OPUS_BAD_ARG))
} else {
Err(Error(OPUS_BAD_ARG))
}
}
fn result_to_size(result: c_int) -> Result<usize, Error> {
usize::try_from(result).map_err(|_| Error(result))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn system_libopus_encodes_and_decodes_a_real_frame() {
let mut encoder = Encoder::voip(48_000, Channels::Mono).unwrap();
let mut decoder = Decoder::new(48_000, Channels::Mono).unwrap();
let pcm = (0..960)
.map(|index| if index % 40 < 20 { 8_000 } else { -8_000 })
.collect::<Vec<_>>();
let mut packet = [0_u8; MAX_PACKET_BYTES];
let packet_bytes = encoder.encode(&pcm, &mut packet).unwrap();
assert!(packet_bytes > 0);
let mut output_pcm = [0_i16; 960];
assert_eq!(
decoder
.decode(Some(&packet[..packet_bytes]), &mut output_pcm, false)
.unwrap(),
960
);
assert!(output_pcm.iter().any(|sample| *sample != 0));
}
#[test]
fn safe_boundary_rejects_invalid_shapes() {
assert!(Encoder::voip(44_100, Channels::Mono).is_err());
let mut encoder = Encoder::voip(48_000, Channels::Mono).unwrap();
assert!(encoder.encode(&[0; 100], &mut [0; 100]).is_err());
let mut decoder = Decoder::new(48_000, Channels::Stereo).unwrap();
assert!(decoder.decode(Some(&[1]), &mut [0; 3], false).is_err());
}
}