Implement credential-safe programs smoke gate (#97)
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
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
This commit is contained in:
210
programs/src/test_client/voice.rs
Normal file
210
programs/src/test_client/voice.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
//! 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())
|
||||
}
|
||||
Reference in New Issue
Block a user