Audit public API and SemVer surface (#103)
Some checks failed
API and SemVer surface / api-surface (push) Failing after 13m8s
Native code generation / deterministic (push) Failing after 2m8s
Concurrency and resource soak audit / soak (push) Failing after 6m39s
Imaging and meshing gate / native (push) Has been cancelled
Release platform and feature matrix / audit (push) Has been cancelled
Release platform and feature matrix / matrix (false, linux-stable-minimal, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, macos-stable-portable, x86_64-apple-darwin, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (false, windows-stable-portable, x86_64-pc-windows-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-msrv-portable, x86_64-unknown-linux-gnu, 1.96.0) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-default, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-features, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Release platform and feature matrix / matrix (true, linux-stable-release-surface, x86_64-unknown-linux-gnu, stable) (push) Has been cancelled
Documentation / documentation (push) Has been cancelled
Native Rust workspace compile / compile (push) Has been cancelled
Dependency and supply-chain audit / audit (push) Has been cancelled
JPEG 2000 feature / linux (push) Successful in 2m46s
Skia feature / linux (push) Successful in 31m0s

This commit is contained in:
2026-08-12 01:35:26 +00:00
parent 5eb3f01122
commit d08b59c9a9
35 changed files with 5112 additions and 67 deletions

View File

@@ -501,6 +501,7 @@ const fn map_decode_error(error: openjpeg::Error) -> Error {
openjpeg::Error::LimitExceeded => Error::Argument,
openjpeg::Error::Allocation => Error::InvalidOperation,
openjpeg::Error::InvalidInput | openjpeg::Error::Codec => parse("JPEG 2000 codestream"),
_ => parse("JPEG 2000 backend"),
}
}
@@ -508,6 +509,7 @@ const fn map_encode_error(error: openjpeg::Error) -> Error {
match error {
openjpeg::Error::InvalidInput | openjpeg::Error::LimitExceeded => Error::Argument,
openjpeg::Error::Allocation | openjpeg::Error::Codec => Error::InvalidOperation,
_ => Error::InvalidOperation,
}
}

View File

@@ -76,6 +76,7 @@ pub struct ComponentRef<'a> {
/// Stable error categories; `OpenJPEG` diagnostics never cross the safe API.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
InvalidInput,
LimitExceeded,

View File

@@ -30,6 +30,7 @@ pub struct RlvSourceSpan {
/// Stable malformed-input categories exposed by the native parser.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum RlvParseErrorKind {
MessageTooLong,
MissingPrefix,

View File

@@ -1133,7 +1133,7 @@ pub use crate::enum_compat::EnumInfoExtensions;
///
/// C# signature: `System.Object LibreMetaverse.ExpiringCache<TKey, TValue>.Item`.
/// Mapping contract: ownership `shared_self,owned`; async `sync`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// errors `none`; overload `direct_snake_case`; kind `method`.
pub use crate::collections::ExpiringCache;
/// C# type: `T:LibreMetaverse.ExtraParamType`.

View File

@@ -44,6 +44,7 @@ impl std::error::Error for NotImplemented {}
/// Shared error contract for translated fallible APIs.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// The API surface exists but its production body has not been ported yet.
NotImplemented(NotImplemented),

View File

@@ -157,6 +157,7 @@ impl fmt::Debug for VivoxEvent {
/// Failure from the native Vivox control protocol.
#[derive(Debug)]
#[non_exhaustive]
pub enum VivoxError {
/// TCP or stream I/O failed.
Io(io::Error),

View File

@@ -9,7 +9,7 @@
///
/// Native Rust mapping of C# `LibreMetaverse.Voice.WebRTC.IVoiceLogger` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub trait IVoiceLogger {
pub trait IVoiceLogger: Send + Sync {
/// C# member: `M:LibreMetaverse.Voice.WebRTC.IVoiceLogger.Debug(System.String,LibreMetaverse.GridClient)`.
///
/// C# signature: `System.Void LibreMetaverse.Voice.WebRTC.IVoiceLogger.Debug(System.String message, LibreMetaverse.GridClient client = default)`.

View File

@@ -42,6 +42,7 @@ const MAX_DATA_BYTES: usize = 64 * 1024;
/// Errors produced by the native WebRTC adapter.
#[derive(Debug)]
#[non_exhaustive]
pub enum WebRtcError {
InvalidInput(&'static str),
Io(std::io::Error),

View File

@@ -557,7 +557,7 @@ struct HttpCapsClientInner {
disposed: AtomicBool,
}
/// Native capability HTTP client with injectable fake and reqwest backends.
/// Native capability HTTP client with injectable fake and production backends.
#[derive(Clone)]
pub struct HttpCapsClient(Arc<HttpCapsClientInner>);
@@ -614,24 +614,47 @@ impl HttpCapsClient {
})))
}
pub fn with_reqwest_client(
client: reqwest::Client,
/// Creates the production HTTP backend without exposing the selected HTTP
/// implementation in the public API.
pub fn with_native_transport(
user_agent: &str,
timeout: Duration,
max_connections: usize,
rate_limiter: Option<CapsRateLimiter>,
limits: CapsHttpLimits,
) -> Result<Self, Error> {
Self::with_reqwest_client_and_concurrency(client, rate_limiter, limits, 32)
if timeout.is_zero() || max_connections == 0 {
return Err(Error::Argument);
}
limits.validate()?;
let max_redirects = limits.max_redirects;
let redirect = reqwest::redirect::Policy::custom(move |attempt| {
if attempt.previous().len() >= max_redirects {
return attempt.stop();
}
match attempt.url().scheme() {
"http" | "https" => attempt.follow(),
_ => attempt.stop(),
}
});
let client = reqwest::Client::builder()
.redirect(redirect)
.timeout(timeout)
.connect_timeout(timeout)
.pool_max_idle_per_host(max_connections)
.user_agent(user_agent)
.build()
.map_err(|_| Error::HttpRequest)?;
Self::with_reqwest_client(client, rate_limiter, limits, max_connections)
}
fn with_reqwest_client_and_concurrency(
fn with_reqwest_client(
client: reqwest::Client,
rate_limiter: Option<CapsRateLimiter>,
limits: CapsHttpLimits,
max_connections: usize,
) -> Result<Self, Error> {
limits.validate()?;
if max_connections == 0 {
return Err(Error::Argument);
}
Ok(Self(Arc::new(HttpCapsClientInner {
backend: HttpBackend::Reqwest(client),
limits,
@@ -648,33 +671,12 @@ impl HttpCapsClient {
max_connections: usize,
rate_limiter: CapsRateLimiter,
) -> Result<Self, Error> {
if max_connections == 0 {
return Err(Error::Argument);
}
let limits = CapsHttpLimits::default();
let max_redirects = limits.max_redirects;
let redirect = reqwest::redirect::Policy::custom(move |attempt| {
if attempt.previous().len() > max_redirects {
return attempt.stop();
}
match attempt.url().scheme() {
"http" | "https" => attempt.follow(),
_ => attempt.stop(),
}
});
let client = reqwest::Client::builder()
.redirect(redirect)
.timeout(timeout)
.connect_timeout(timeout)
.pool_max_idle_per_host(max_connections)
.user_agent(user_agent)
.build()
.map_err(|_| Error::HttpRequest)?;
Self::with_reqwest_client_and_concurrency(
client,
Some(rate_limiter),
limits,
Self::with_native_transport(
user_agent,
timeout,
max_connections,
Some(rate_limiter),
CapsHttpLimits::default(),
)
}

View File

@@ -11,6 +11,7 @@ use std::sync::{Arc, Condvar, Mutex};
/// A validated client-core failure with no credential or capability payload.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum ClientCoreError {
/// A public settings field failed validation.
InvalidConfiguration {

View File

@@ -17035,7 +17035,7 @@ pub trait IBakingTextureProvider: std::any::Any + Send + Sync {
///
/// Native Rust mapping of C# `LibreMetaverse.IGridClient` using decision
/// `native_metacrate_type`. See the [pinned C# source](https://github.com/cinderblocks/libremetaverse/tree/2aa70bb68513b39795da5d13c88f31b86e85a3ba).
pub trait IGridClient {
pub trait IGridClient: Send + Sync {
/// C# member: `P:LibreMetaverse.IGridClient.AisClient`.
///
/// C# signature: `LibreMetaverse.InventoryAISClient LibreMetaverse.IGridClient.AisClient`.
@@ -19408,7 +19408,7 @@ impl InventoryManager {
/// C# member: `M:LibreMetaverse.InventoryManager.CreateLinksAsync(LibreMetaverse.UUID,System.Collections.Generic.IEnumerable{System.ValueTuple{LibreMetaverse.InventoryBase,System.String}},System.Action{System.Boolean},System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task LibreMetaverse.InventoryManager.CreateLinksAsync(LibreMetaverse.UUID folderID, System.Collections.Generic.IEnumerable<System.ValueTuple<LibreMetaverse.InventoryBase, System.String>> linksToCreate, System.Action<System.Boolean> callback, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `shared_self,owned,owned,optional_owned,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,owned,owned,optional_owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub async fn create_links(
&self,
@@ -19562,7 +19562,7 @@ impl InventoryManager {
/// C# member: `M:LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID,LibreMetaverse.UUID,System.Boolean,System.Boolean,LibreMetaverse.InventorySortOrder,System.Threading.CancellationToken,System.Boolean)`.
///
/// C# signature: `System.Threading.Tasks.Task<System.Collections.Generic.List<LibreMetaverse.InventoryBase>> LibreMetaverse.InventoryManager.FolderContentsAsync(LibreMetaverse.UUID folder, LibreMetaverse.UUID owner, System.Boolean fetchFolders, System.Boolean fetchItems, LibreMetaverse.InventorySortOrder order, System.Threading.CancellationToken cancellationToken = default, System.Boolean followLinks = default)`.
/// Mapping contract: ownership `shared_self,owned,owned,owned,owned,owned,optional_owned,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,owned,owned,owned,owned,owned,optional_owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub async fn folder_contents(
&self,
@@ -19604,7 +19604,7 @@ impl InventoryManager {
/// C# member: `M:LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID,System.UInt32,LibreMetaverse.Simulator,System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task<System.Collections.Generic.List<LibreMetaverse.InventoryBase>> LibreMetaverse.InventoryManager.GetTaskInventoryAsync(LibreMetaverse.UUID objectID, System.UInt32 objectLocalID, LibreMetaverse.Simulator simulator = default, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `shared_self,owned,owned,optional_owned,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,owned,owned,optional_owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub async fn get_task_inventory(
&self,
@@ -35686,12 +35686,12 @@ pub use crate::terrain_codec::TerrainCompressor;
/// C# member: `M:LibreMetaverse.TerrainManager.GetTerrainMaterialOverridesAsync(System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task<LibreMetaverse.Assets.AssetMaterial[]> LibreMetaverse.TerrainManager.GetTerrainMaterialOverridesAsync(System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `shared_self,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
/// C# member: `M:LibreMetaverse.TerrainManager.SetTerrainMaterialOverridesAsync(LibreMetaverse.Assets.AssetMaterial[],System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task<System.Boolean> LibreMetaverse.TerrainManager.SetTerrainMaterialOverridesAsync(LibreMetaverse.Assets.AssetMaterial[] overrides, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `shared_self,owned,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,owned,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub use crate::terrain_manager::TerrainManager;
@@ -37970,7 +37970,7 @@ pub mod appearance {
/// C# member: `M:LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task LibreMetaverse.Appearance.CompositeCurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List<LibreMetaverse.InventoryItem> addedItems, System.Collections.Generic.List<LibreMetaverse.InventoryItem> removedItems, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `shared_self,optional_shared,optional_shared,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,optional_shared,optional_shared,optional_owned`; async `async`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
pub async fn report_item_change(
&self,
@@ -38273,7 +38273,7 @@ pub mod appearance {
/// C# member: `M:LibreMetaverse.Appearance.ICurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Collections.Generic.List{LibreMetaverse.InventoryItem},System.Threading.CancellationToken)`.
///
/// C# signature: `System.Threading.Tasks.Task LibreMetaverse.Appearance.ICurrentOutfitPolicy.ReportItemChangeAsync(System.Collections.Generic.List<LibreMetaverse.InventoryItem> addedItems, System.Collections.Generic.List<LibreMetaverse.InventoryItem> removedItems, System.Threading.CancellationToken cancellationToken = default)`.
/// Mapping contract: ownership `shared_self,optional_shared,optional_shared,optional_owned`; async `sync`;
/// Mapping contract: ownership `shared_self,optional_shared,optional_shared,optional_owned`; async `boxed_future`;
/// errors `Result<_, crate::Error>`; overload `direct_snake_case`; kind `method`.
fn report_item_change(
&self,

View File

@@ -29,6 +29,7 @@ const THROTTLE_BURST_PERIODS: usize = 4;
/// The variants deliberately carry no datagram bytes, credentials, endpoint
/// query data, or operating-system error strings.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum UdpTransportError {
InvalidConfiguration(&'static str),
InvalidBuffer,

View File

@@ -797,8 +797,32 @@ async fn read_http_request(stream: &mut tokio::net::TcpStream) -> Vec<u8> {
}
}
#[test]
fn native_transport_rejects_invalid_resource_policy_before_building() {
assert!(matches!(
HttpCapsClient::with_native_transport(
"MetaCrate-caps-test/0.0.1",
Duration::ZERO,
1,
None,
CapsHttpLimits::default(),
),
Err(Error::Argument)
));
assert!(matches!(
HttpCapsClient::with_native_transport(
"MetaCrate-caps-test/0.0.1",
Duration::from_secs(1),
0,
None,
CapsHttpLimits::default(),
),
Err(Error::Argument)
));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn injected_reqwest_backend_follows_redirects_streams_and_decompresses() {
async fn native_http_backend_follows_redirects_streams_and_decompresses() {
let listener = tokio::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, 0))
.await
.expect("bind fake server");
@@ -842,12 +866,10 @@ async fn injected_reqwest_backend_follows_redirects_streams_and_decompresses() {
}
});
let reqwest = reqwest::Client::builder()
.redirect(reqwest::redirect::Policy::limited(3))
.build()
.expect("reqwest client");
let client = HttpCapsClient::with_reqwest_client(
reqwest,
let client = HttpCapsClient::with_native_transport(
"MetaCrate-caps-test/0.0.1",
Duration::from_secs(5),
4,
None,
CapsHttpLimits {
max_request_bytes: 1_024,