Files
MetaCrate/programs/src/test_client/voice.rs
Chili Palmer a9e2711447
Some checks failed
Native code generation / deterministic (push) Failing after 2m12s
Imaging and meshing gate / native (push) Failing after 5m40s
JPEG 2000 feature / linux (push) Successful in 2m50s
Native Rust workspace compile / compile (push) Failing after 56s
Skia feature / linux (push) Successful in 31m24s
Implement credential-safe programs smoke gate (#97)
2026-08-11 18:23:10 +00:00

211 lines
7.4 KiB
Rust

//! Credential-safe native `TestClient` voice capability commands.
use super::{FakeBackend, InventoryFuture as BackendFuture, LiveBackend, lock};
use libremetaverse::types::compat::{CancellationToken, Uri};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDParser};
use std::collections::HashMap;
use std::time::Duration;
const MAX_RESPONSE_BYTES: usize = 1024 * 1024;
const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Clone, Copy)]
pub(super) enum Command {
Parcel,
Account,
}
pub(super) fn commands() -> [super::CommandEntry; 2] {
use super::{CommandCategory as Cat, CommandHandler};
[
(
"voiceparcel",
"Obtain redacted parcel voice information. Usage: voiceparcel",
Cat::Other,
CommandHandler::Voice(Command::Parcel),
),
(
"voiceaccount",
"Provision voice account information with credentials redacted. Usage: voiceaccount",
Cat::Other,
CommandHandler::Voice(Command::Account),
),
]
}
pub(super) trait Backend {
fn voice_query(
&self,
command: Command,
cancellation: CancellationToken,
) -> BackendFuture<'_, Result<VoiceResult, String>>;
}
pub(super) enum VoiceResult {
Parcel {
region_name_present: bool,
parcel_local_id: i32,
channel_present: bool,
},
Account {
username_present: bool,
password_present: bool,
},
}
pub(super) async fn execute<B: Backend + ?Sized>(
backend: &B,
command: Command,
args: &[String],
cancellation: CancellationToken,
) -> String {
if !args.is_empty() {
return match command {
Command::Parcel => "Usage: voiceparcel",
Command::Account => "Usage: voiceaccount",
}
.into();
}
match backend.voice_query(command, cancellation).await {
Ok(VoiceResult::Parcel {
region_name_present,
parcel_local_id,
channel_present,
}) => format!(
"Parcel voice info: region-name-present={region_name_present}, parcel-local-id={parcel_local_id}, channel=<{}>",
if channel_present {
"redacted"
} else {
"missing"
}
),
Ok(VoiceResult::Account {
username_present,
password_present,
}) => format!(
"Voice account provisioned: credentials=<{}>",
if username_present && password_present {
"redacted"
} else {
"incomplete"
}
),
Err(error) => error,
}
}
impl Backend for FakeBackend {
fn voice_query(
&self,
command: Command,
cancellation: CancellationToken,
) -> BackendFuture<'_, Result<VoiceResult, String>> {
Box::pin(async move {
if cancellation.is_cancellation_requested() {
return Err("Voice capability request cancelled".into());
}
Ok(match command {
Command::Parcel => {
self.record("CALL voice-parcel-info".into());
VoiceResult::Parcel {
region_name_present: true,
parcel_local_id: 42,
channel_present: true,
}
}
Command::Account => {
self.record("CALL voice-provision-account credentials=<redacted>".into());
VoiceResult::Account {
username_present: true,
password_present: true,
}
}
})
})
}
}
impl Backend for LiveBackend {
fn voice_query(
&self,
command: Command,
cancellation: CancellationToken,
) -> BackendFuture<'_, Result<VoiceResult, String>> {
Box::pin(async move {
let capability = match command {
Command::Parcel => "ParcelVoiceInfoRequest",
Command::Account => "ProvisionVoiceAccountRequest",
};
let simulator = self
.network
.current_sim()
.ok_or_else(|| "No current simulator is available".to_owned())?;
let uri = simulator
.native_capability_uri(capability)
.map_err(|_| "Voice capability lookup failed".to_owned())?
.ok_or_else(|| "Required voice capability is unavailable".to_owned())?;
let http = lock(&self.client).http_caps_client();
let request = http.post_with_uri_osd_format_osd_cancellation_token_i_progress(
Uri(uri.0),
OSDFormat::Xml,
OSD::Map(HashMap::new()),
cancellation.clone(),
None,
);
let result = tokio::select! {
() = cancellation.cancelled() => return Err("Voice capability request cancelled".into()),
() = tokio::time::sleep(REQUEST_TIMEOUT) => return Err("Voice capability request timed out".into()),
result = request => result,
}
.map_err(|_| "Voice capability request failed".to_owned())?;
let (response, bytes) = result;
if !response.is_success_status_code() || bytes.len() > MAX_RESPONSE_BYTES {
return Err("Voice capability returned an error".into());
}
let OSD::Map(map) = OSDParser::deserialize_with_bytes(bytes)
.map_err(|_| "Voice capability returned malformed LLSD".to_owned())?
else {
return Err("Voice capability response was not an LLSD map".into());
};
Ok(match command {
Command::Parcel => {
let region_name_present = present_string(&map, "region_name");
let parcel_local_id = map
.get("parcel_local_id")
.and_then(|value| value.as_integer().ok())
.ok_or_else(|| "Parcel voice response omitted local ID".to_owned())?;
let channel_present = match map.get("voice_credentials") {
Some(OSD::Map(credentials)) => present_string(credentials, "channel_uri"),
_ => false,
};
if !region_name_present || !channel_present {
return Err("Parcel voice response omitted required fields".into());
}
VoiceResult::Parcel {
region_name_present,
parcel_local_id,
channel_present,
}
}
Command::Account => {
let username_present = present_string(&map, "username");
let password_present = present_string(&map, "password");
if !username_present || !password_present {
return Err("Voice account response omitted credentials".into());
}
VoiceResult::Account {
username_present,
password_present,
}
}
})
})
}
}
fn present_string(map: &HashMap<String, OSD>, key: &str) -> bool {
map.get(key)
.and_then(|value| value.as_string().ok())
.is_some_and(|value| !value.is_empty())
}