Implement native login and session initialization (#56)
All checks were successful
Native code generation / deterministic (push) Successful in 11m53s
Imaging and meshing gate / native (push) Successful in 4m3s
JPEG 2000 feature / linux (push) Successful in 2m33s
Native Rust workspace compile / compile (push) Successful in 4m3s
Skia feature / linux (push) Successful in 31m24s

This commit is contained in:
2026-08-09 18:18:28 +00:00
parent aa269f1785
commit 3951bdb809
15 changed files with 3992 additions and 1355 deletions

300
Cargo.lock generated
View File

@@ -17,12 +17,27 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "android_system_properties"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.18.0" version = "1.18.0"
@@ -102,6 +117,15 @@ dependencies = [
"hybrid-array", "hybrid-array",
] ]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.3" version = "3.20.3"
@@ -141,6 +165,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
version = "0.2.2" version = "0.2.2"
@@ -277,6 +307,16 @@ dependencies = [
"crypto-common 0.2.2", "crypto-common 0.2.2",
] ]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"objc2",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.7" version = "0.2.7"
@@ -724,7 +764,7 @@ dependencies = [
"jni-sys", "jni-sys",
"log", "log",
"simd_cesu8", "simd_cesu8",
"thiserror", "thiserror 2.0.20",
"walkdir", "walkdir",
"windows-link", "windows-link",
] ]
@@ -808,6 +848,8 @@ dependencies = [
"libremetaverse-imaging", "libremetaverse-imaging",
"libremetaverse-structured-data", "libremetaverse-structured-data",
"libremetaverse-types", "libremetaverse-types",
"mac_address2",
"os_info",
"reqwest", "reqwest",
"roxmltree", "roxmltree",
"tokio", "tokio",
@@ -997,6 +1039,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "mac_address2"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc5e04aa39a4dc96076bca67bf60b63201ff06f9568e62f7904293d39de8cb0b"
dependencies = [
"nix 0.28.0",
"thiserror 1.0.69",
"widestring",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@@ -1013,6 +1067,15 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@@ -1040,6 +1103,31 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases 0.1.1",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases 0.2.2",
"libc",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "7.1.3" version = "7.1.3"
@@ -1050,6 +1138,165 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.4" version = "1.21.4"
@@ -1062,6 +1309,21 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "os_info"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
dependencies = [
"android_system_properties",
"log",
"nix 0.31.3",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"
@@ -1115,14 +1377,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
dependencies = [ dependencies = [
"bytes", "bytes",
"cfg_aliases", "cfg_aliases 0.2.2",
"pin-project-lite", "pin-project-lite",
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2",
"thiserror", "thiserror 2.0.20",
"tokio", "tokio",
"tracing", "tracing",
"web-time", "web-time",
@@ -1145,7 +1407,7 @@ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
"thiserror", "thiserror 2.0.20",
"tinyvec", "tinyvec",
"tracing", "tracing",
"web-time", "web-time",
@@ -1157,7 +1419,7 @@ version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
dependencies = [ dependencies = [
"cfg_aliases", "cfg_aliases 0.2.2",
"libc", "libc",
"once_cell", "once_cell",
"socket2", "socket2",
@@ -1694,13 +1956,33 @@ dependencies = [
"xattr", "xattr",
] ]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.20" version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 2.0.20",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
] ]
[[package]] [[package]]
@@ -2078,6 +2360,12 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "widestring"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.11" version = "0.1.11"

View File

@@ -294,6 +294,15 @@ retry handling while preserving permanent 401/403/404/410 failures. Capability
URLs and tokens are absent from errors and diagnostics. Ownership, limits, URLs and tokens are absent from errors and diagnostics. Ownership, limits,
injection, and offline fake-server coverage are documented in injection, and offline fake-server coverage are documented in
[`docs/caps-http.md`](docs/caps-http.md). [`docs/caps-http.md`](docs/caps-http.md).
Login is implemented against that transport with the C# password-hashing and
token rules, viewer/channel/platform fields, start-location normalization,
bounded redirects, cancellation, and typed status transitions. Native response
parsing populates session, simulator, inventory, buddy, account-benefit, and
service fields before the initial UDP circuit and seed capability are installed.
Credentials and session/capability tokens are redacted from diagnostics, and
offline fake login/simulator tests cover success, rejection, redirect, timeout,
and cancellation cleanup. The login contract is documented with the network
lifecycle in [`docs/network-manager.md`](docs/network-manager.md).
Seed-cap discovery and `EventQueueGet` are native Rust too: the full reference Seed-cap discovery and `EventQueueGet` are native Rust too: the full reference
capability list is posted as LLSD/XML, accepted URIs are rate-categorized, and capability list is posted as LLSD/XML, accepted URIs are rate-categorized, and
bounded long polls preserve ack IDs, reconnect retries, shutdown `done`, and bounded long polls preserve ack IDs, reconnect retries, shutdown `done`, and

View File

@@ -4,7 +4,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand.
| Assembly | Types | Members | Status | | Assembly | Types | Members | Status |
|---|---:|---:|---| |---|---:|---:|---|
| `LibreMetaverse` | 2,711 | 27,281 | native implementation: 68 types / 13,858 members; remaining surface is callable failure-only shims | | `LibreMetaverse` | 2,711 | 27,281 | native implementation: 77 types / 14,065 members; remaining surface is callable failure-only shims |
| `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain | | `LibreMetaverse.Imaging.Abstractions` | 3 | 20 | native implementation: 3 types / 20 members; no generated shims remain |
| `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain | | `LibreMetaverse.Imaging.Skia` | 1 | 3 | native implementation: 1 type / 3 members; no generated shims remain |
| `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim | | `LibreMetaverse.LslTools` | 164 | 768 | callable failure-only shim |

View File

@@ -19,6 +19,8 @@ futures-util = "0.3.31"
libremetaverse-imaging = { path = "../libremetaverse-imaging" } libremetaverse-imaging = { path = "../libremetaverse-imaging" }
libremetaverse-structured-data = { path = "../libremetaverse-structured-data" } libremetaverse-structured-data = { path = "../libremetaverse-structured-data" }
libremetaverse-types = { path = "../libremetaverse-types" } libremetaverse-types = { path = "../libremetaverse-types" }
mac_address2 = "2.0.2"
os_info = { version = "3.15.0", default-features = false }
reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "stream"] } reqwest = { version = "0.13.4", default-features = false, features = ["rustls", "stream"] }
roxmltree = "0.21.1" roxmltree = "0.21.1"
tokio = { version = "1.47.1", features = ["macros", "net", "rt", "sync", "time"] } tokio = { version = "1.47.1", features = ["macros", "net", "rt", "sync", "time"] }

View File

@@ -116,6 +116,7 @@ struct ClientRuntime {
agent_throttle_sender: Mutex<Option<Arc<dyn crate::udp_transport::AgentThrottleSender>>>, agent_throttle_sender: Mutex<Option<Arc<dyn crate::udp_transport::AgentThrottleSender>>>,
http_caps_client: Mutex<crate::caps_http::HttpCapsClient>, http_caps_client: Mutex<crate::caps_http::HttpCapsClient>,
caps_rate_limiter: Mutex<crate::caps_http::CapsRateLimiter>, caps_rate_limiter: Mutex<crate::caps_http::CapsRateLimiter>,
network_manager: Mutex<std::sync::Weak<crate::network_manager::NetworkManagerInner>>,
shutdown_complete: Condvar, shutdown_complete: Condvar,
shutdown_wait: Mutex<()>, shutdown_wait: Mutex<()>,
} }
@@ -142,6 +143,7 @@ impl ClientRuntime {
agent_throttle_sender: Mutex::new(None), agent_throttle_sender: Mutex::new(None),
http_caps_client: Mutex::new(http_caps_client), http_caps_client: Mutex::new(http_caps_client),
caps_rate_limiter: Mutex::new(caps_rate_limiter), caps_rate_limiter: Mutex::new(caps_rate_limiter),
network_manager: Mutex::new(std::sync::Weak::new()),
shutdown_complete: Condvar::new(), shutdown_complete: Condvar::new(),
shutdown_wait: Mutex::new(()), shutdown_wait: Mutex::new(()),
} }
@@ -350,6 +352,31 @@ impl GridClient {
.clone() .clone()
} }
pub(crate) fn native_network(&self) -> Result<crate::NetworkManager, crate::Error> {
let mut cached = self
.runtime
.network_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if let Some(inner) = cached.upgrade() {
return Ok(crate::network_manager::NetworkManager::native_from_inner(
inner,
));
}
let manager = crate::network_manager::NetworkManager::native_new(self.clone())?;
*cached = manager.native_inner_weak();
Ok(manager)
}
#[allow(clippy::needless_pass_by_value)] // The mapped C# property setter owns its value.
pub(crate) fn native_set_network(&self, value: crate::NetworkManager) {
*self
.runtime
.network_manager
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) = value.native_inner_weak();
}
pub(crate) fn native_set_caps_rate_limiter( pub(crate) fn native_set_caps_rate_limiter(
&mut self, &mut self,
value: crate::caps_http::CapsRateLimiter, value: crate::caps_http::CapsRateLimiter,

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ mod generated;
#[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator. #[rustfmt::skip] // Deterministic machine output is formatted by the pinned generator.
mod genepool_catalog; mod genepool_catalog;
mod j2k; mod j2k;
mod login;
mod message_codec; mod message_codec;
mod message_decoder; mod message_decoder;
mod network_manager; mod network_manager;

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map. #![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the compatibility map.
#![allow(clippy::needless_pass_by_value)] // Owned mapped parameters preserve the public API. #![allow(clippy::needless_pass_by_value)] // Owned mapped parameters preserve the public API.
#![allow(clippy::too_many_arguments)] // Login overloads preserve the mapped public API.
#![allow(clippy::too_many_lines)] // Packet dispatch keeps the complete protocol switch together. #![allow(clippy::too_many_lines)] // Packet dispatch keeps the complete protocol switch together.
#![allow(clippy::type_complexity)] // Delegate signatures are fixed by the compatibility map. #![allow(clippy::type_complexity)] // Delegate signatures are fixed by the compatibility map.
@@ -18,11 +19,15 @@ use crate::packets::{
}; };
use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransportConfig}; use crate::udp_transport::{UDPBase, UDPPacketBuffer, UdpPacketHandler, UdpTransportConfig};
use crate::{ use crate::{
Avatar, Caps, Error, GenericStreamingMethod, GridClient, Helpers, LoginResponseData, Primitive, AccountLevelBenefits, Avatar, Caps, Error, GenericStreamingMethod, GridClient, Helpers,
RegionFlags, RegionProtocols, SimAccess, SimulatorDataPool, SimulatorFeatures, LoginCredential, LoginParams, LoginProgressEventArgs, LoginResponseData, LoginStatus,
SimulatorSimStats, TerrainPatch, NetworkManagerLoginResponseCallback, Primitive, RegionFlags, RegionProtocols, SimAccess,
SimulatorDataPool, SimulatorFeatures, SimulatorSimStats, TerrainPatch,
};
use libremetaverse_structured_data::{OSD, OSDFormat, OSDMap, OSDParser};
use libremetaverse_types::compat::{
CancellationToken, CancellationTokenSource, EventHandler, Object, Subscription, Uri,
}; };
use libremetaverse_types::compat::{EventHandler, Object, Subscription, Uri};
use libremetaverse_types::{UUID, Vector2}; use libremetaverse_types::{UUID, Vector2};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
@@ -90,6 +95,76 @@ fn positive_millisecond_duration(value: i32) -> Duration {
Duration::from_millis(u64::try_from(value.max(1)).unwrap_or(1)) Duration::from_millis(u64::try_from(value.max(1)).unwrap_or(1))
} }
fn validate_login_uri(value: &str) -> Result<Uri, Error> {
let parsed = reqwest::Url::parse(value).map_err(|_| Error::Argument)?;
if !matches!(parsed.scheme(), "http" | "https") || parsed.host_str().is_none() {
return Err(Error::Argument);
}
Ok(Uri(parsed.to_string()))
}
struct LoginWireSecrets {
password: String,
token: String,
mfa_hash: String,
}
impl Drop for LoginWireSecrets {
fn drop(&mut self) {
// Overwrite the live buffers before releasing them. This cannot erase
// allocator copies, but it prevents the owned request state surviving
// the login/redirect sequence.
self.password
.replace_range(.., &"\0".repeat(self.password.len()));
self.token.replace_range(.., &"\0".repeat(self.token.len()));
self.mfa_hash
.replace_range(.., &"\0".repeat(self.mfa_hash.len()));
self.password.clear();
self.token.clear();
self.mfa_hash.clear();
}
}
fn login_request(params: &LoginParams, secrets: &LoginWireSecrets) -> OSD {
let mut map = HashMap::from([
("first".to_owned(), OSD::String(params.first_name.clone())),
("last".to_owned(), OSD::String(params.last_name.clone())),
("passwd".to_owned(), OSD::String(secrets.password.clone())),
("start".to_owned(), OSD::String(params.start.clone())),
("channel".to_owned(), OSD::String(params.channel.clone())),
("version".to_owned(), OSD::String(params.version.clone())),
("platform".to_owned(), OSD::String(params.platform.clone())),
(
"platform_version".to_owned(),
OSD::String(params.platform_version.clone()),
),
("mac".to_owned(), OSD::String(params.mac.clone())),
("agree_to_tos".to_owned(), OSD::Boolean(params.agree_to_tos)),
(
"read_critical".to_owned(),
OSD::Boolean(params.read_critical),
),
(
"viewer_digest".to_owned(),
OSD::String(params.viewer_digest.clone()),
),
("id0".to_owned(), OSD::String(params.id0.clone())),
(
"last_exec_event".to_owned(),
OSD::Integer(params.last_exec_event as i32),
),
(
"options".to_owned(),
OSD::Array(params.options.iter().cloned().map(OSD::String).collect()),
),
]);
if params.mfa_enabled {
map.insert("token".to_owned(), OSD::String(secrets.token.clone()));
map.insert("mfa_hash".to_owned(), OSD::String(secrets.mfa_hash.clone()));
}
OSD::Map(map)
}
async fn run_blocking_compat<T: Send + 'static>( async fn run_blocking_compat<T: Send + 'static>(
name: &str, name: &str,
operation: impl FnOnce() -> T + Send + 'static, operation: impl FnOnce() -> T + Send + 'static,
@@ -857,6 +932,8 @@ pub struct SimulatorData {
handshake_wait_lock: Mutex<()>, handshake_wait_lock: Mutex<()>,
disconnect_candidate: AtomicBool, disconnect_candidate: AtomicBool,
circuit_code: AtomicU32, circuit_code: AtomicU32,
agent_id: RwLock<UUID>,
session_id: RwLock<UUID>,
seed_caps: RwLock<Option<Uri>>, seed_caps: RwLock<Option<Uri>>,
caps_state: RwLock<Option<Arc<crate::caps::CapsInner>>>, caps_state: RwLock<Option<Arc<crate::caps::CapsInner>>>,
event_queue_generation: Mutex<u64>, event_queue_generation: Mutex<u64>,
@@ -1018,6 +1095,8 @@ impl Simulator {
handshake_wait_lock: Mutex::new(()), handshake_wait_lock: Mutex::new(()),
disconnect_candidate: AtomicBool::new(false), disconnect_candidate: AtomicBool::new(false),
circuit_code: AtomicU32::new(0), circuit_code: AtomicU32::new(0),
agent_id: RwLock::new(UUID::zero()),
session_id: RwLock::new(UUID::zero()),
seed_caps: RwLock::new(None), seed_caps: RwLock::new(None),
caps_state: RwLock::new(None), caps_state: RwLock::new(None),
event_queue_generation: Mutex::new(0), event_queue_generation: Mutex::new(0),
@@ -1058,6 +1137,8 @@ impl Simulator {
fn attach_manager(&self, manager: &Arc<NetworkManagerInner>, circuit_code: u32) { fn attach_manager(&self, manager: &Arc<NetworkManagerInner>, circuit_code: u32) {
*mutex(&self.manager) = Arc::downgrade(manager); *mutex(&self.manager) = Arc::downgrade(manager);
self.circuit_code.store(circuit_code, Ordering::Release); self.circuit_code.store(circuit_code, Ordering::Release);
*write(&self.agent_id) = *read(&manager.agent_id);
*write(&self.session_id) = *read(&manager.session_id);
} }
pub fn native_connect(&self, _move_to_sim: bool) -> Result<bool, Error> { pub fn native_connect(&self, _move_to_sim: bool) -> Result<bool, Error> {
@@ -1414,8 +1495,8 @@ impl Simulator {
fn use_circuit_code_bytes(&self) -> Result<Vec<u8>, Error> { fn use_circuit_code_bytes(&self) -> Result<Vec<u8>, Error> {
let mut packet = UseCircuitCodePacket::new_with_constructor()?; let mut packet = UseCircuitCodePacket::new_with_constructor()?;
packet.circuit_code.code = self.circuit_code.load(Ordering::Acquire); packet.circuit_code.code = self.circuit_code.load(Ordering::Acquire);
packet.circuit_code.id = UUID::zero(); packet.circuit_code.id = *read(&self.agent_id);
packet.circuit_code.session_id = UUID::zero(); packet.circuit_code.session_id = *read(&self.session_id);
packet.to_bytes_with_method() packet.to_bytes_with_method()
} }
@@ -1566,6 +1647,7 @@ struct NetworkEvents {
disconnected: EventRegistry<DisconnectedEventArgs>, disconnected: EventRegistry<DisconnectedEventArgs>,
event_queue_running: EventRegistry<EventQueueRunningEventArgs>, event_queue_running: EventRegistry<EventQueueRunningEventArgs>,
generic_streaming_message: EventRegistry<GenericStreamingMessageEventArgs>, generic_streaming_message: EventRegistry<GenericStreamingMessageEventArgs>,
login_progress: EventRegistry<LoginProgressEventArgs>,
packet_sent: EventRegistry<PacketSentEventArgs>, packet_sent: EventRegistry<PacketSentEventArgs>,
sim_changed: EventRegistry<SimChangedEventArgs>, sim_changed: EventRegistry<SimChangedEventArgs>,
sim_connected: EventRegistry<SimConnectedEventArgs>, sim_connected: EventRegistry<SimConnectedEventArgs>,
@@ -1573,7 +1655,23 @@ struct NetworkEvents {
sim_disconnected: EventRegistry<SimDisconnectedEventArgs>, sim_disconnected: EventRegistry<SimDisconnectedEventArgs>,
} }
struct NetworkManagerInner { struct LoginRuntimeState {
next_generation: u64,
active: Option<(u64, CancellationTokenSource)>,
response_callbacks: Vec<(NetworkManagerLoginResponseCallback, Option<Vec<String>>)>,
}
impl Default for LoginRuntimeState {
fn default() -> Self {
Self {
next_generation: 1,
active: None,
response_callbacks: Vec::new(),
}
}
}
pub(crate) struct NetworkManagerInner {
client: GridClient, client: GridClient,
simulators: SimulatorCollection, simulators: SimulatorCollection,
packet_events: PacketEventDictionary, packet_events: PacketEventDictionary,
@@ -1582,6 +1680,14 @@ struct NetworkManagerInner {
current_sim: RwLock<Option<Simulator>>, current_sim: RwLock<Option<Simulator>>,
connected: AtomicBool, connected: AtomicBool,
circuit_code: AtomicU32, circuit_code: AtomicU32,
agent_id: RwLock<UUID>,
session_id: RwLock<UUID>,
login_status: RwLock<LoginStatus>,
login_error_key: RwLock<String>,
login_message: RwLock<String>,
login_seed_capability: RwLock<Option<Uri>>,
login_response: LoginResponseData,
login_runtime: Mutex<LoginRuntimeState>,
inbox_count: AtomicI32, inbox_count: AtomicI32,
outbox_count: AtomicI32, outbox_count: AtomicI32,
workers: Mutex<Option<NetworkWorkers>>, workers: Mutex<Option<NetworkWorkers>>,
@@ -2039,10 +2145,23 @@ impl fmt::Debug for NetworkManager {
} }
impl NetworkManager { impl NetworkManager {
pub(crate) fn native_from_inner(inner: Arc<NetworkManagerInner>) -> Self {
Self {
login_response_data: Some(inner.login_response.clone()),
simulators: inner.simulators.clone(),
inner,
}
}
pub(crate) fn native_inner_weak(&self) -> Weak<NetworkManagerInner> {
Arc::downgrade(&self.inner)
}
pub fn native_new(client: GridClient) -> Result<Self, Error> { pub fn native_new(client: GridClient) -> Result<Self, Error> {
let simulators = SimulatorCollection::default(); let simulators = SimulatorCollection::default();
let packet_events = PacketEventDictionary::new(client.clone())?; let packet_events = PacketEventDictionary::new(client.clone())?;
let caps_events = CapsEventDictionary::new(client.clone())?; let caps_events = CapsEventDictionary::new(client.clone())?;
let login_response = LoginResponseData::new()?;
let inner = Arc::new(NetworkManagerInner { let inner = Arc::new(NetworkManagerInner {
client, client,
simulators: simulators.clone(), simulators: simulators.clone(),
@@ -2052,6 +2171,14 @@ impl NetworkManager {
current_sim: RwLock::new(None), current_sim: RwLock::new(None),
connected: AtomicBool::new(false), connected: AtomicBool::new(false),
circuit_code: AtomicU32::new(0), circuit_code: AtomicU32::new(0),
agent_id: RwLock::new(UUID::zero()),
session_id: RwLock::new(UUID::zero()),
login_status: RwLock::new(LoginStatus::None),
login_error_key: RwLock::new(String::new()),
login_message: RwLock::new(String::new()),
login_seed_capability: RwLock::new(None),
login_response: login_response.clone(),
login_runtime: Mutex::new(LoginRuntimeState::default()),
inbox_count: AtomicI32::new(0), inbox_count: AtomicI32::new(0),
outbox_count: AtomicI32::new(0), outbox_count: AtomicI32::new(0),
workers: Mutex::new(None), workers: Mutex::new(None),
@@ -2075,7 +2202,7 @@ impl NetworkManager {
return; return;
}; };
let manager = NetworkManager { let manager = NetworkManager {
login_response_data: None, login_response_data: Some(inner.login_response.clone()),
simulators: inner.simulators.clone(), simulators: inner.simulators.clone(),
inner: Arc::clone(&inner), inner: Arc::clone(&inner),
}; };
@@ -2099,12 +2226,623 @@ impl NetworkManager {
}), }),
)?; )?;
Ok(Self { Ok(Self {
login_response_data: None, login_response_data: Some(login_response),
simulators, simulators,
inner, inner,
}) })
} }
pub fn native_subscribe_login_progress(
&self,
handler: EventHandler<LoginProgressEventArgs>,
) -> Subscription {
self.inner.events.login_progress.subscribe(handler)
}
fn update_login_status(&self, status: LoginStatus, message: String) {
if status == LoginStatus::Failed {
*write(&self.inner.agent_id) = UUID::zero();
*write(&self.inner.session_id) = UUID::zero();
*write(&self.inner.login_seed_capability) = None;
self.inner.login_response.clear_secrets();
}
*write(&self.inner.login_status) = status;
write(&self.inner.login_message).clone_from(&message);
let fail_reason = read(&self.inner.login_error_key).clone();
if let Ok(args) = LoginProgressEventArgs::new(status, message, fail_reason) {
self.inner.events.login_progress.emit_with(|| args.clone());
}
}
fn set_login_error(&self, value: &str) {
let mut error = write(&self.inner.login_error_key);
error.clear();
error.push_str(value);
}
#[must_use]
pub fn native_login_status_code(&self) -> LoginStatus {
*read(&self.inner.login_status)
}
pub fn native_set_login_status_code(&self, value: LoginStatus) {
*write(&self.inner.login_status) = value;
}
#[must_use]
pub fn native_login_error_key(&self) -> String {
read(&self.inner.login_error_key).clone()
}
pub fn native_set_login_error_key(&self, value: String) {
*write(&self.inner.login_error_key) = value;
}
#[must_use]
pub fn native_login_message(&self) -> String {
read(&self.inner.login_message).clone()
}
pub fn native_set_login_message(&self, value: String) {
*write(&self.inner.login_message) = value;
}
#[must_use]
pub fn native_login_seed_capability(&self) -> Option<Uri> {
read(&self.inner.login_seed_capability).clone()
}
pub fn native_set_login_seed_capability(&self, value: Option<Uri>) {
*write(&self.inner.login_seed_capability) = value;
}
#[must_use]
pub fn native_account_level_benefits(&self) -> Option<AccountLevelBenefits> {
self.inner.login_response.account_level_benefits()
}
#[must_use]
pub fn native_agent_appearance_service_url(&self) -> Option<String> {
let value = self.inner.login_response.agent_appearance_service_url();
(!value.is_empty()).then_some(value)
}
#[must_use]
pub fn native_max_agent_groups(&self) -> i32 {
let fallback = self.inner.login_response.max_agent_groups();
self.inner
.login_response
.account_level_benefits()
.map_or(fallback, |benefits| {
let limit = benefits.group_membership_limit();
if limit > 0 { limit } else { fallback }
})
}
pub fn native_default_login_params(
&self,
first_name: String,
last_name: String,
password: String,
channel: String,
version: String,
) -> Result<LoginParams, Error> {
LoginParams::new_with_grid_client_string_string_string_string_string(
self.inner.client.clone(),
first_name,
last_name,
password,
channel,
version,
)
}
pub fn native_register_login_response_callback(
&self,
callback: NetworkManagerLoginResponseCallback,
options: Option<Vec<String>>,
) -> Result<(), Error> {
let mut state = mutex(&self.inner.login_runtime);
if state
.response_callbacks
.iter()
.any(|(candidate, _)| candidate.ptr_eq(&callback))
{
return Err(Error::Argument);
}
state.response_callbacks.push((callback, options));
Ok(())
}
pub fn native_unregister_login_response_callback(
&self,
callback: &NetworkManagerLoginResponseCallback,
) -> Result<(), Error> {
let mut state = mutex(&self.inner.login_runtime);
state
.response_callbacks
.retain(|(candidate, _)| !candidate.ptr_eq(callback));
Ok(())
}
fn reserve_login(
&self,
cancellation_token: Option<CancellationToken>,
) -> Result<(u64, CancellationTokenSource), Error> {
let mut runtime = mutex(&self.inner.login_runtime);
if runtime.active.is_some() {
return Err(Error::InvalidOperation);
}
let caller = cancellation_token.unwrap_or_default();
let source =
CancellationTokenSource::new_linked(&[caller, self.inner.client.cancellation_token()]);
let generation = runtime.next_generation;
runtime.next_generation = runtime.next_generation.wrapping_add(1).max(1);
runtime.active = Some((generation, source.clone()));
Ok((generation, source))
}
fn release_login(&self, generation: u64) {
let mut runtime = mutex(&self.inner.login_runtime);
if runtime
.active
.as_ref()
.is_some_and(|(active, _)| *active == generation)
{
runtime.active = None;
}
}
pub fn native_abort_login(&self) -> Result<(), Error> {
let active = mutex(&self.inner.login_runtime).active.take();
if let Some((_, source)) = active {
source.cancel();
}
self.update_login_status(LoginStatus::Failed, "Abort Requested".to_owned());
Ok(())
}
pub fn native_begin_login(&self, login_params: LoginParams) -> Result<(), Error> {
let (generation, source) = self.reserve_login(None)?;
let manager = Self {
login_response_data: Some(self.inner.login_response.clone()),
simulators: self.simulators.clone(),
inner: Arc::clone(&self.inner),
};
thread::Builder::new()
.name("libremetaverse-login".to_owned())
.spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
let result = match runtime {
Ok(runtime) => {
runtime.block_on(manager.perform_login(login_params, source.token()))
}
Err(_) => Err(Error::InvalidOperation),
};
if let Err(error) = result {
manager.login_transport_failure(&error);
}
manager.release_login(generation);
})
.map_err(|_| {
self.release_login(generation);
Error::InvalidOperation
})?;
Ok(())
}
pub async fn native_login(
&self,
login_params: LoginParams,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
Ok(self
.native_login_response_async(login_params, cancellation_token)
.await?
.is_some_and(|response| response.success()))
}
pub async fn native_login_response_async(
&self,
login_params: LoginParams,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<LoginResponseData>, Error> {
let timeout = positive_millisecond_duration(login_params.timeout);
let (generation, source) = self.reserve_login(cancellation_token)?;
let token = source.token();
let result = tokio::select! {
result = tokio::time::timeout(timeout, self.perform_login(login_params, token.clone())) => {
if let Ok(result) = result {
result
} else {
source.cancel();
self.set_login_error("timeout");
self.update_login_status(LoginStatus::Failed, "Login timed out".to_owned());
Ok(None)
}
}
() = token.cancelled() => {
self.set_login_error("canceled");
self.update_login_status(LoginStatus::Failed, "Canceled".to_owned());
Ok(None)
}
};
self.release_login(generation);
match result {
Ok(value) => Ok(value),
Err(error) => {
self.login_transport_failure(&error);
Ok(None)
}
}
}
pub async fn native_login_credential(
&self,
credential: LoginCredential,
channel: String,
start: Option<String>,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
let mut login_params = LoginParams::new_with_grid_client_login_credential_string_string(
self.inner.client.clone(),
credential,
channel,
version,
)?;
if let Some(start) = start {
login_params.start = start;
}
self.native_login(login_params, cancellation_token).await
}
pub async fn native_login_credential_response(
&self,
credential: LoginCredential,
channel: String,
start: Option<String>,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<LoginResponseData>, Error> {
let mut login_params = LoginParams::new_with_grid_client_login_credential_string_string(
self.inner.client.clone(),
credential,
channel,
version,
)?;
if let Some(start) = start {
login_params.start = start;
}
self.native_login_response_async(login_params, cancellation_token)
.await
}
pub async fn native_login_strings(
&self,
first_name: String,
last_name: String,
password: String,
channel: String,
start: String,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<bool, Error> {
let mut params =
self.native_default_login_params(first_name, last_name, password, channel, version)?;
params.start = start;
self.native_login(params, cancellation_token).await
}
pub async fn native_login_strings_response(
&self,
first_name: String,
last_name: String,
password: String,
channel: String,
start: String,
version: String,
cancellation_token: Option<CancellationToken>,
) -> Result<Option<LoginResponseData>, Error> {
let mut params =
self.native_default_login_params(first_name, last_name, password, channel, version)?;
params.start = start;
self.native_login_response_async(params, cancellation_token)
.await
}
async fn perform_login(
&self,
mut params: LoginParams,
cancellation_token: CancellationToken,
) -> Result<Option<LoginResponseData>, Error> {
if params.password.len() != 35 && !params.password.starts_with("$1$") {
params.password = libremetaverse_types::Utils::md5_with_string(params.password)?;
}
if params.channel.trim().is_empty() {
params.channel = crate::Settings::user_agent();
}
if params.version.trim().is_empty() {
params.version = String::from("?.?.?");
}
let wire_secrets = LoginWireSecrets {
password: std::mem::take(&mut params.password),
token: std::mem::take(&mut params.token),
mfa_hash: std::mem::take(&mut params.mfa_hash),
};
params.clear_secrets();
let requested_start = if params.login_location.trim().is_empty() {
params.start.clone()
} else {
params.login_location.clone()
};
params.start = crate::login::normalize_start(&requested_start)?;
let callback_options: Vec<String> = mutex(&self.inner.login_runtime)
.response_callbacks
.iter()
.filter_map(|(_, options)| options.as_ref())
.flatten()
.cloned()
.collect();
for option in callback_options {
if !params.options.contains(&option) {
params.options.push(option);
}
}
let mut redirects = 0_usize;
loop {
cancellation_token.throw_if_cancellation_requested()?;
let login_uri = validate_login_uri(&params.uri)?;
self.update_login_status(
LoginStatus::ConnectingToLogin,
format!(
"Logging in as {} {}...",
params.first_name, params.last_name
),
);
let request = login_request(&params, &wire_secrets);
let response = self
.inner
.client
.native_http_caps_client()
.post_with_uri_osd_format_osd_cancellation_token_i_progress(
login_uri,
OSDFormat::Xml,
request,
cancellation_token.clone(),
None,
)
.await;
let (http_response, response_data) = match response {
Ok(response) => response,
Err(Error::Cancelled) => {
self.set_login_error("canceled");
self.update_login_status(LoginStatus::Failed, "Login canceled".to_owned());
return Ok(None);
}
Err(error) => {
self.login_transport_failure(&error);
return Ok(None);
}
};
if !http_response.is_success_status_code() {
self.set_login_error("bad response");
self.update_login_status(
LoginStatus::Failed,
format!("Login service returned HTTP {}", http_response.status_code),
);
return Ok(None);
}
self.update_login_status(
LoginStatus::ReadingResponse,
"Reading login response...".to_owned(),
);
let parsed = match OSDParser::deserialize_with_bytes(response_data) {
Ok(OSD::Map(map)) => OSDMap::new_with_dictionary(map),
_ => Err(Error::Argument),
};
let Ok(parsed) = parsed else {
self.set_login_error("bad response");
self.update_login_status(
LoginStatus::Failed,
"Empty or corrupt login response".to_owned(),
);
return Ok(None);
};
let has_login = parsed.get("login").is_some();
self.inner.login_response.parse_with_osd_map(parsed)?;
let response = self.inner.login_response.clone();
if !has_login {
self.update_login_status(
LoginStatus::Failed,
"Login parameter missing in the response".to_owned(),
);
return Ok(None);
}
match response.login() {
crate::LoginState::Indeterminate => {
redirects += 1;
if redirects > 10 {
self.set_login_error("redirect loop");
self.update_login_status(
LoginStatus::Failed,
"Too many login redirects".to_owned(),
);
return Ok(None);
}
self.update_login_status(LoginStatus::Redirecting, response.message());
params.uri = response.next_url();
validate_login_uri(&params.uri)?;
let delay = Duration::from_secs(
u64::try_from(response.next_duration().clamp(0, 300)).unwrap_or(0),
);
tokio::select! {
() = tokio::time::sleep(delay) => {}
() = cancellation_token.cancelled() => {
self.set_login_error("canceled");
self.update_login_status(LoginStatus::Failed, "Login canceled".to_owned());
return Ok(None);
}
}
}
crate::LoginState::True => {
self.invoke_login_response(true, false, &response);
if response.seed_capability().is_empty() {
self.set_login_error("bad response");
self.update_login_status(
LoginStatus::Failed,
"Login response did not include a seed capability".to_owned(),
);
return Ok(None);
}
if self.initialize_login_response(
response.clone(),
response.message(),
"Unable to establish a UDP connection to the simulator",
"Login server did not return a simulator address",
)? {
return Ok(Some(response));
}
return Ok(None);
}
crate::LoginState::False => {
let reason = response.reason();
self.set_login_error(if reason.is_empty() {
"unknown"
} else {
&reason
});
self.update_login_status(LoginStatus::Failed, response.message());
return Ok(None);
}
}
}
}
fn invoke_login_response(&self, success: bool, redirect: bool, response: &LoginResponseData) {
let callbacks: Vec<_> = mutex(&self.inner.login_runtime)
.response_callbacks
.iter()
.map(|(callback, _)| callback.clone())
.collect();
for callback in callbacks {
let _ = callback.invoke(
success,
redirect,
response.message(),
response.reason(),
Some(response.clone()),
);
}
}
fn login_transport_failure(&self, error: &Error) {
let (key, message) = match error {
Error::Cancelled => ("canceled", "Login canceled"),
Error::HttpRequest => ("no connection", "Unable to contact login service"),
Error::Argument => ("bad response", "Invalid login request or response"),
_ => ("no connection", "Login failed"),
};
self.set_login_error(key);
self.update_login_status(LoginStatus::Failed, message.to_owned());
}
pub fn native_login_response(&self, response: LoginResponseData) -> Result<bool, Error> {
self.initialize_login_response(
response,
"Login success".to_owned(),
"Unable to connect to simulator",
"Unable to connect to simulator",
)
}
fn initialize_login_response(
&self,
response: LoginResponseData,
success_message: String,
connect_failure_message: &str,
address_failure_message: &str,
) -> Result<bool, Error> {
self.inner.login_response.copy_from(&response);
self.inner
.circuit_code
.store(response.circuit_code().cast_unsigned(), Ordering::Release);
*write(&self.inner.agent_id) = response.agent_id();
*write(&self.inner.session_id) = response.session_id();
let seed = if response.seed_capability().is_empty() {
None
} else {
Some(validate_login_uri(&response.seed_capability())?)
};
write(&self.inner.login_seed_capability).clone_from(&seed);
self.update_login_status(
LoginStatus::ConnectingToSim,
"Connecting to simulator...".to_owned(),
);
let Some(ip) = response.sim_ip() else {
self.set_login_error("bad response");
self.update_login_status(LoginStatus::Failed, address_failure_message.to_owned());
return Ok(false);
};
if response.sim_port() == 0 {
self.set_login_error("bad response");
self.update_login_status(LoginStatus::Failed, address_failure_message.to_owned());
return Ok(false);
}
let handle = (u64::from(response.region_x()) << 32) | u64::from(response.region_y());
let endpoint = std::net::SocketAddr::new(ip, response.sim_port());
let Ok(connected) = self.native_connect(
endpoint,
handle,
true,
seed,
response.region_size_x(),
response.region_size_y(),
) else {
self.cleanup_failed_login_connection(endpoint);
self.set_login_error("no connection");
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
return Ok(false);
};
if let Some(simulator) = connected {
let economy_request =
Packet::build_packet_with_packet_type(PacketType::EconomyDataRequest);
if economy_request
.and_then(|packet| simulator.native_send_packet(packet))
.is_err()
{
self.cleanup_failed_login_connection(endpoint);
self.set_login_error("no connection");
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
return Ok(false);
}
self.update_login_status(LoginStatus::Success, success_message);
Ok(true)
} else {
self.cleanup_failed_login_connection(endpoint);
self.update_login_status(LoginStatus::Failed, connect_failure_message.to_owned());
Ok(false)
}
}
fn cleanup_failed_login_connection(&self, endpoint: std::net::SocketAddr) {
if let Some(simulator) = self.native_find_endpoint(endpoint) {
let _ = simulator.native_disconnect(false);
self.simulators.remove(&simulator);
let mut current = write(&self.inner.current_sim);
if current.as_ref() == Some(&simulator) {
*current = None;
}
}
if self.simulators.is_empty() {
self.inner.connected.store(false, Ordering::Release);
}
}
pub fn native_subscribe_disconnected( pub fn native_subscribe_disconnected(
&self, &self,
handler: EventHandler<DisconnectedEventArgs>, handler: EventHandler<DisconnectedEventArgs>,
@@ -2299,7 +3037,7 @@ impl NetworkManager {
size_y: u32, size_y: u32,
) -> Result<Option<Simulator>, Error> { ) -> Result<Option<Simulator>, Error> {
let manager = Self { let manager = Self {
login_response_data: None, login_response_data: Some(self.inner.login_response.clone()),
simulators: self.simulators.clone(), simulators: self.simulators.clone(),
inner: Arc::clone(&self.inner), inner: Arc::clone(&self.inner),
}; };

View File

@@ -7,10 +7,16 @@ use libremetaverse::packets::{
RegionHandshakePacket, RegionHandshakeReplyPacket, StartPingCheckPacket, UseCircuitCodePacket, RegionHandshakePacket, RegionHandshakeReplyPacket, StartPingCheckPacket, UseCircuitCodePacket,
}; };
use libremetaverse::{ use libremetaverse::{
CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, NetworkManager, CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers, HttpCapsClient, LoginState,
NetworkManagerDisconnectType, PacketEventDictionary, Simulator, LoginStatus, NetworkManager, NetworkManagerDisconnectType, NetworkManagerLoginResponseCallback,
PacketEventDictionary, Simulator,
}; };
use libremetaverse_types::compat::{EventHandler, Uri}; use libremetaverse_structured_data::{OSD, OSDParser};
use libremetaverse_types::compat::{
CancellationTokenSource, EventHandler, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
};
use libremetaverse_types::{UUID, Utils};
use std::collections::{BTreeMap, HashMap};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel}; use std::sync::mpsc::{Receiver, Sender, channel};
@@ -204,6 +210,35 @@ fn synchronous_specific_dispatch_suppresses_default_async_chain_like_reference()
); );
} }
#[test]
fn abort_and_callback_unregistration_preserve_reference_noop_behavior() {
let client = GridClient::new().unwrap();
let manager = client.network();
let progress = Arc::new(Mutex::new(Vec::new()));
let observed = Arc::clone(&progress);
let _guard = manager.subscribe_login_progress(Arc::new(move |args| {
observed.lock().unwrap().push(args);
}));
let callback = NetworkManagerLoginResponseCallback::from_handler(|_, _, _, _, _| {});
manager
.register_login_response_callback_with_login_response_callback(callback.clone())
.unwrap();
manager
.unregister_login_response_callback(callback.clone())
.unwrap();
manager
.unregister_login_response_callback(callback)
.unwrap();
manager.abort_login().unwrap();
assert_eq!(manager.login_status_code(), LoginStatus::Failed);
assert_eq!(manager.login_message(), "Abort Requested");
let progress = progress.lock().unwrap();
assert_eq!(progress.len(), 1);
assert_eq!(progress[0].status(), LoginStatus::Failed);
assert_eq!(progress[0].message(), "Abort Requested");
}
struct TestCapsMessage; struct TestCapsMessage;
impl IMessage for TestCapsMessage {} impl IMessage for TestCapsMessage {}
@@ -319,6 +354,7 @@ fn sim_connecting_can_cancel_without_leaking_collection_state() {
enum ServerCommand { enum ServerCommand {
Ping, Ping,
Disable, Disable,
ExpectEconomy,
Stop, Stop,
} }
@@ -327,6 +363,7 @@ enum ServerReport {
Ack(u32), Ack(u32),
Circuit(u32), Circuit(u32),
HandshakeReply(u32), HandshakeReply(u32),
Economy,
Ping(u8), Ping(u8),
} }
@@ -472,6 +509,13 @@ fn spawn_fake_server() -> FakeServer {
bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED); bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
socket.send_to(&bytes, client_endpoint).unwrap(); socket.send_to(&bytes, client_endpoint).unwrap();
} }
ServerCommand::ExpectEconomy => loop {
let (length, _) = socket.recv_from(&mut buffer).expect("EconomyDataRequest");
if packet_type(&buffer[..length]) == Some(PacketType::EconomyDataRequest) {
report_sender.send(ServerReport::Economy).unwrap();
break;
}
},
ServerCommand::Stop => break, ServerCommand::Stop => break,
} }
} }
@@ -620,7 +664,6 @@ fn fake_server_drives_circuit_handshake_ping_disable_and_disconnect_reasons() {
server.reports.recv_timeout(Duration::from_secs(1)).unwrap(), server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
ServerReport::HandshakeReply(0x1 | 0x2 | 0x4) ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
); );
assert_reuse_preserves_connected_override(&mut manager, &server, &sim); assert_reuse_preserves_connected_override(&mut manager, &server, &sim);
assert_ping_exchange(&server, &packet_receiver); assert_ping_exchange(&server, &packet_receiver);
@@ -897,3 +940,396 @@ fn handshake_timeout_preserves_reference_connect_result_and_shutdown_tears_down_
assert_eq!(sim_disconnects.load(Ordering::Relaxed), 1); assert_eq!(sim_disconnects.load(Ordering::Relaxed), 1);
drop(guard); drop(guard);
} }
fn login_http_response(body: OSD) -> HttpResponse {
let body = OSDParser::serialize_llsd_xml_bytes(body).expect("serialize LLSD");
HttpResponse {
status_code: 200,
headers: BTreeMap::from([("Content-Length".into(), body.len().to_string())]),
content_type: Some("application/llsd+xml".into()),
body,
}
}
fn successful_login_response(endpoint: SocketAddr, seed: &str) -> OSD {
OSD::Map(HashMap::from([
("login".into(), OSD::Boolean(true)),
("message".into(), OSD::String("Welcome".into())),
(
"agent_id".into(),
OSD::UUID(
UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap(),
),
),
(
"session_id".into(),
OSD::UUID(
UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap(),
),
),
(
"secure_session_id".into(),
OSD::UUID(
UUID::new_with_string("cccccccc-cccc-cccc-cccc-cccccccccccc".into()).unwrap(),
),
),
("circuit_code".into(), OSD::Integer(0x1234)),
("region_x".into(), OSD::Integer(1000)),
("region_y".into(), OSD::Integer(2000)),
("region_size_x".into(), OSD::Integer(256)),
("region_size_y".into(), OSD::Integer(256)),
("sim_ip".into(), OSD::String(endpoint.ip().to_string())),
("sim_port".into(), OSD::Integer(i32::from(endpoint.port()))),
("seed_capability".into(), OSD::String(seed.into())),
(
"account_level_benefits".into(),
OSD::Map(HashMap::from([(
"group_membership_limit".into(),
OSD::Integer(70),
)])),
),
]))
}
#[tokio::test(flavor = "current_thread")]
async fn login_hashes_credentials_parses_response_and_hands_off_session_to_udp_and_caps() {
let server = spawn_fake_server();
let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
let recorded = Arc::clone(&requests);
let endpoint = server.endpoint;
let handler = HttpMessageHandler::new(move |request, _| {
let recorded = Arc::clone(&recorded);
async move {
let response = if request.uri.0.contains("/login") {
login_http_response(successful_login_response(
endpoint,
"http://caps.test/seed?token=secret",
))
} else {
login_http_response(OSD::Map(HashMap::new()))
};
recorded.lock().unwrap().push(request);
response
}
});
let mut client = GridClient::new().unwrap();
client.settings().timing().login_timeout = 2_000;
client.settings().connection_mut().mfa_enabled = true;
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
let manager = client.network();
let callback_calls = Arc::new(AtomicUsize::new(0));
let observed_calls = Arc::clone(&callback_calls);
let callback = NetworkManagerLoginResponseCallback::from_handler(
move |success, redirect, message, reason, response| {
assert!(success);
assert!(!redirect);
assert_eq!(message, "Welcome");
assert!(reason.is_empty());
assert!(response.is_some_and(|value| value.success()));
observed_calls.fetch_add(1, Ordering::Relaxed);
},
);
manager
.register_login_response_callback_with_login_response_callback_string_array(
callback,
Some(vec!["custom-login-option".into()]),
)
.unwrap();
let progress = Arc::new(Mutex::new(Vec::new()));
let observed = Arc::clone(&progress);
let _progress_guard = manager.subscribe_login_progress(Arc::new(move |args| {
observed.lock().unwrap().push(args.status());
}));
let mut params = manager
.default_login_params(
"Ada".into(),
"Lovelace".into(),
"plain-secret".into(),
"MetaCrate".into(),
"1.0".into(),
)
.unwrap();
params.uri = "http://login.test/login".into();
params.token = "123456".into();
params.mfa_hash = "saved-mfa-hash".into();
let response = manager
.login_with_response_with_login_params_cancellation_token(params, None)
.await
.unwrap()
.expect("successful response");
assert_eq!(response.login(), LoginState::True);
assert_eq!(manager.login_status_code(), LoginStatus::Success);
assert_eq!(manager.login_message(), "Welcome");
assert_eq!(manager.max_agent_groups(), 70);
assert_eq!(callback_calls.load(Ordering::Relaxed), 1);
assert!(manager.connected());
assert!(manager.current_sim().is_some());
assert_eq!(
manager.login_seed_capability(),
Some(Uri("http://caps.test/seed?token=secret".into()))
);
assert_eq!(
server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
ServerReport::Circuit(0x1234)
);
assert_eq!(
server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
);
server.commands.send(ServerCommand::ExpectEconomy).unwrap();
assert_eq!(
server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
ServerReport::Economy
);
let login_request = requests
.lock()
.unwrap()
.iter()
.find(|request| request.uri.0.contains("/login"))
.cloned()
.expect("login request");
let request = OSDParser::deserialize_with_bytes(login_request.body).unwrap();
let OSD::Map(request) = request else {
panic!("login request must be an LLSD map");
};
assert_eq!(
request["passwd"].as_string().unwrap(),
Utils::md5_with_string("plain-secret".into()).unwrap()
);
assert_eq!(request["token"].as_string().unwrap(), "123456");
assert_eq!(request["mfa_hash"].as_string().unwrap(), "saved-mfa-hash");
assert_eq!(request["start"].as_string().unwrap(), "last");
assert_eq!(request["channel"].as_string().unwrap(), "MetaCrate");
assert_eq!(request["version"].as_string().unwrap(), "1.0");
assert_eq!(
request["platform"].as_string().unwrap(),
NetworkManager::get_platform().unwrap()
);
assert!(!request["platform_version"].as_string().unwrap().is_empty());
assert_eq!(
request["mac"].as_string().unwrap(),
request["id0"].as_string().unwrap()
);
let OSD::Array(options) = &request["options"] else {
panic!("login options must be an LLSD array");
};
assert!(
options
.iter()
.any(|option| option.as_string().as_deref() == Ok("custom-login-option"))
);
assert_eq!(
*progress.lock().unwrap(),
[
LoginStatus::ConnectingToLogin,
LoginStatus::ReadingResponse,
LoginStatus::ConnectingToSim,
LoginStatus::Success,
]
);
manager
.shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
.unwrap();
}
#[tokio::test(flavor = "current_thread")]
async fn redirect_reuses_hashed_credentials_and_failure_is_typed_without_partial_session() {
let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
let recorded = Arc::clone(&requests);
let calls = Arc::new(AtomicUsize::new(0));
let handler_calls = Arc::clone(&calls);
let handler = HttpMessageHandler::new(move |request, _| {
let recorded = Arc::clone(&recorded);
let index = handler_calls.fetch_add(1, Ordering::Relaxed);
async move {
recorded.lock().unwrap().push(request);
if index == 0 {
login_http_response(OSD::Map(HashMap::from([
("login".into(), OSD::String("indeterminate".into())),
(
"next_url".into(),
OSD::String("http://login.test/redirected".into()),
),
("next_duration".into(), OSD::Integer(0)),
("message".into(), OSD::String("Redirecting".into())),
])))
} else {
login_http_response(OSD::Map(HashMap::from([
("login".into(), OSD::Boolean(false)),
("reason".into(), OSD::String("key".into())),
("message".into(), OSD::String("Denied".into())),
])))
}
}
});
let mut client = GridClient::new().unwrap();
client.settings().timing().login_timeout = 1_000;
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
let manager = client.network();
let callback_calls = Arc::new(AtomicUsize::new(0));
let observed_calls = Arc::clone(&callback_calls);
manager
.register_login_response_callback_with_login_response_callback(
NetworkManagerLoginResponseCallback::from_handler(move |_, _, _, _, _| {
observed_calls.fetch_add(1, Ordering::Relaxed);
}),
)
.unwrap();
let mut params = manager
.default_login_params(
"Ada".into(),
"Lovelace".into(),
"plain-secret".into(),
"MetaCrate".into(),
"1.0".into(),
)
.unwrap();
params.uri = "http://login.test/login".into();
assert!(
!manager
.login_with_login_params_cancellation_token(params, None)
.await
.unwrap()
);
assert_eq!(manager.login_status_code(), LoginStatus::Failed);
assert_eq!(manager.login_error_key(), "key");
assert_eq!(manager.login_message(), "Denied");
assert!(!manager.connected());
assert!(manager.current_sim().is_none());
assert_eq!(
callback_calls.load(Ordering::Relaxed),
0,
"the C# callback only runs for a successful login"
);
let passwords: Vec<_> = requests
.lock()
.unwrap()
.iter()
.map(|request| {
let OSD::Map(map) = OSDParser::deserialize_with_bytes(request.body.clone()).unwrap()
else {
panic!("request must be map");
};
map["passwd"].as_string().unwrap()
})
.collect();
assert_eq!(passwords.len(), 2);
assert_eq!(passwords[0], passwords[1]);
assert_ne!(passwords[0], "plain-secret");
}
#[tokio::test(flavor = "current_thread")]
async fn successful_authentication_with_invalid_simulator_clears_partial_session() {
let handler = HttpMessageHandler::new(|_, _| async move {
let mut response = successful_login_response(
"127.0.0.1:1".parse().unwrap(),
"http://caps.test/seed?token=must-not-survive",
);
let OSD::Map(ref mut fields) = response else {
unreachable!();
};
fields.insert("sim_port".into(), OSD::Integer(0));
login_http_response(response)
});
let mut client = GridClient::new().unwrap();
client.settings().timing().login_timeout = 1_000;
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
let manager = client.network();
let mut params = manager
.default_login_params(
"Ada".into(),
"Lovelace".into(),
"plain-secret".into(),
"MetaCrate".into(),
"1.0".into(),
)
.unwrap();
params.uri = "http://login.test/login".into();
assert!(
!manager
.login_with_login_params_cancellation_token(params, None)
.await
.unwrap()
);
assert_eq!(manager.login_status_code(), LoginStatus::Failed);
assert!(manager.current_sim().is_none());
assert!(manager.simulators.is_empty());
assert!(!manager.connected());
assert!(manager.login_seed_capability().is_none());
let response = manager.login_response_data.as_ref().unwrap();
assert_eq!(response.session_id(), UUID::zero());
assert_eq!(response.secure_session_id(), UUID::zero());
assert!(response.seed_capability().is_empty());
assert!(response.account_level_benefits().is_none());
}
#[tokio::test(flavor = "current_thread", start_paused = true)]
async fn login_timeout_is_typed_and_leaves_no_partial_session() {
let handler =
HttpMessageHandler::new(|_, _| async move { std::future::pending::<HttpResponse>().await });
let mut client = GridClient::new().unwrap();
client.settings().timing().login_timeout = 25;
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
let manager = client.network();
let mut params = manager
.default_login_params(
"Ada".into(),
"Lovelace".into(),
"plain-secret".into(),
"MetaCrate".into(),
"1.0".into(),
)
.unwrap();
params.uri = "http://login.test/login".into();
assert!(
!manager
.login_with_login_params_cancellation_token(params, None)
.await
.unwrap()
);
assert_eq!(manager.login_status_code(), LoginStatus::Failed);
assert_eq!(manager.login_error_key(), "timeout");
assert_eq!(manager.login_message(), "Login timed out");
assert!(manager.current_sim().is_none());
assert!(manager.login_seed_capability().is_none());
}
#[tokio::test(flavor = "current_thread")]
async fn canceled_login_finishes_failed_without_retaining_simulator_work() {
let handler = HttpMessageHandler::new(|_, cancellation| async move {
cancellation.cancelled().await;
login_http_response(OSD::Map(HashMap::new()))
});
let mut client = GridClient::new().unwrap();
client.settings().timing().login_timeout = 1_000;
client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
let manager = client.network();
let mut params = manager
.default_login_params(
"Ada".into(),
"Lovelace".into(),
"plain-secret".into(),
"MetaCrate".into(),
"1.0".into(),
)
.unwrap();
params.uri = "http://login.test/login".into();
let cancellation = CancellationTokenSource::new();
cancellation.cancel();
assert!(
!manager
.login_with_login_params_cancellation_token(params, Some(cancellation.token()))
.await
.unwrap()
);
assert_eq!(manager.login_status_code(), LoginStatus::Failed);
assert_eq!(manager.login_error_key(), "canceled");
assert!(!manager.connected());
assert!(manager.simulators.is_empty());
}

View File

@@ -66,14 +66,32 @@ the last simulator reports `SimShutdown`. Explicit shutdown preserves the
caller-provided reason/message and sends `CloseCircuit` only for client or caller-provided reason/message and sends `CloseCircuit` only for client or
network-timeout shutdowns. network-timeout shutdowns.
## Login behavior
The native login path constructs the reference LLSD request, including the C#
MD5 password form, token/MFA fields, viewer identity, platform metadata,
requested options, and normalized home/last/region start locations. It uses the
client's injectable capability HTTP transport, limits redirects and server
delays, observes linked caller/client cancellation, and reports the mapped
`LoginStatus` transitions. Only a successful response invokes registered login
callbacks and proceeds to an ACK-gated initial simulator connection.
`LoginResponseData` parses the session and circuit identifiers, home and look-at
vectors, simulator endpoint and dimensions, seed capability, inventory roots and
skeletons, buddy rights, account benefits, service URLs, category/config arrays,
premium packages, and initial outfit. A rejected, malformed, timed-out, or
canceled login leaves no simulator installed. Passwords, login tokens, MFA
hashes, session identifiers, seed capabilities, and estate access tokens are
excluded from `Debug` output and diagnostic messages.
The deterministic tests use loopback fake simulators and require no live grid: The deterministic tests use loopback fake simulators and require no live grid:
```sh ```sh
cargo test -p libremetaverse --test network_manager cargo test -p libremetaverse --test network_manager
``` ```
They cover ACK-gated circuit setup, typed handshake reply/state, ping response, They cover login hashing, redirects, cancellation and initial handoff alongside
CAPS enable, UDP disable, current/seed selection, callback ordering and ACK-gated circuit setup, typed handshake reply/state, ping response, CAPS enable,
filtering, RAII unregistration, runtime-neutral async calls, concurrent UDP disable, current/seed selection, callback ordering and filtering, RAII
registration/removal, reentrant/concurrent disconnect, and keepalive timeout unregistration, runtime-neutral async calls, concurrent registration/removal,
reasons. reentrant/concurrent disconnect, and keepalive timeout reasons.

View File

@@ -8,12 +8,27 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "android_system_properties"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]] [[package]]
name = "aws-lc-rs" name = "aws-lc-rs"
version = "1.18.0" version = "1.18.0"
@@ -73,6 +88,15 @@ dependencies = [
"hybrid-array", "hybrid-array",
] ]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.3" version = "3.20.3"
@@ -103,6 +127,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
version = "0.2.2" version = "0.2.2"
@@ -228,6 +258,16 @@ dependencies = [
"crypto-common 0.2.2", "crypto-common 0.2.2",
] ]
[[package]]
name = "dispatch2"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"objc2",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.7" version = "0.2.7"
@@ -606,7 +646,7 @@ dependencies = [
"jni-sys", "jni-sys",
"log", "log",
"simd_cesu8", "simd_cesu8",
"thiserror", "thiserror 2.0.20",
"walkdir", "walkdir",
"windows-link", "windows-link",
] ]
@@ -680,6 +720,8 @@ dependencies = [
"libremetaverse-imaging", "libremetaverse-imaging",
"libremetaverse-structured-data", "libremetaverse-structured-data",
"libremetaverse-types", "libremetaverse-types",
"mac_address2",
"os_info",
"reqwest", "reqwest",
"roxmltree", "roxmltree",
"tokio", "tokio",
@@ -809,6 +851,18 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "mac_address2"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc5e04aa39a4dc96076bca67bf60b63201ff06f9568e62f7904293d39de8cb0b"
dependencies = [
"nix 0.28.0",
"thiserror 1.0.69",
"widestring",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@@ -825,6 +879,15 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "metacrate-api-compile" name = "metacrate-api-compile"
version = "0.0.0" version = "0.0.0"
@@ -865,6 +928,190 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "nix"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases 0.1.1",
"libc",
"memoffset",
]
[[package]]
name = "nix"
version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases 0.2.2",
"libc",
]
[[package]]
name = "objc2"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
"objc2-encode",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags",
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-data"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
]
[[package]]
name = "objc2-core-graphics"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags",
"dispatch2",
"objc2",
"objc2-core-foundation",
"objc2-io-surface",
]
[[package]]
name = "objc2-core-image"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-location"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]]
name = "objc2-core-text"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
]
[[package]]
name = "objc2-encode"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
[[package]]
name = "objc2-foundation"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"block2",
"libc",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
]
[[package]]
name = "objc2-quartz-core"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
]
[[package]]
name = "objc2-ui-kit"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags",
"block2",
"objc2",
"objc2-cloud-kit",
"objc2-core-data",
"objc2-core-foundation",
"objc2-core-graphics",
"objc2-core-image",
"objc2-core-location",
"objc2-core-text",
"objc2-foundation",
"objc2-quartz-core",
"objc2-user-notifications",
]
[[package]]
name = "objc2-user-notifications"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
dependencies = [
"objc2",
"objc2-foundation",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.4" version = "1.21.4"
@@ -877,6 +1124,21 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "os_info"
version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b"
dependencies = [
"android_system_properties",
"log",
"nix 0.31.3",
"objc2",
"objc2-foundation",
"objc2-ui-kit",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"
@@ -920,14 +1182,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
dependencies = [ dependencies = [
"bytes", "bytes",
"cfg_aliases", "cfg_aliases 0.2.2",
"pin-project-lite", "pin-project-lite",
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2",
"thiserror", "thiserror 2.0.20",
"tokio", "tokio",
"tracing", "tracing",
"web-time", "web-time",
@@ -950,7 +1212,7 @@ dependencies = [
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
"thiserror", "thiserror 2.0.20",
"tinyvec", "tinyvec",
"tracing", "tracing",
"web-time", "web-time",
@@ -962,7 +1224,7 @@ version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
dependencies = [ dependencies = [
"cfg_aliases", "cfg_aliases 0.2.2",
"libc", "libc",
"once_cell", "once_cell",
"socket2", "socket2",
@@ -1383,13 +1645,33 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "2.0.20" version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 2.0.20",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
] ]
[[package]] [[package]]
@@ -1722,6 +2004,12 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "widestring"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.11" version = "0.1.11"

View File

@@ -7,14 +7,6 @@ use libremetaverse::{
}; };
use libremetaverse_types::compat::{CancellationToken, EventHandler, IProgress, Subscription, Uri}; use libremetaverse_types::compat::{CancellationToken, EventHandler, IProgress, Subscription, Uri};
fn member_id<T>(result: Result<T, libremetaverse::Error>) -> &'static str {
result
.err()
.expect("failure-only shim unexpectedly returned a value")
.csharp_member()
.expect("failure-only shim error")
}
async fn compile_login_shutdown_flows( async fn compile_login_shutdown_flows(
network: &NetworkManager, network: &NetworkManager,
limiter: &CapsRateLimiter, limiter: &CapsRateLimiter,
@@ -102,10 +94,7 @@ fn client_and_settings_construct_without_starting_runtime_work() {
libremetaverse::ClientLifecycleState::Disposed libremetaverse::ClientLifecycleState::Disposed
); );
assert_eq!( let _limiter = CapsRateLimiter::new_with_constructor().expect("CAPS rate limiter");
member_id(CapsRateLimiter::new_with_constructor()),
"M:LibreMetaverse.CapsRateLimiter.#ctor"
);
let udp_buffer = UDPPacketBuffer::new_with_constructor().expect("UDP packet buffer"); let udp_buffer = UDPPacketBuffer::new_with_constructor().expect("UDP packet buffer");
assert_eq!(udp_buffer.data.len(), 4096); assert_eq!(udp_buffer.data.len(), 4096);
assert_eq!(udp_buffer.data_length, 0); assert_eq!(udp_buffer.data_length, 0);

View File

@@ -38,6 +38,7 @@ TARGETS = {
# implementations. The generated module keeps catalog markers and re-exports # implementations. The generated module keeps catalog markers and re-exports
# the hand-written type so coverage remains deterministic. # the hand-written type so coverage remains deterministic.
NATIVE_TYPES = { NATIVE_TYPES = {
"T:LibreMetaverse.AccountLevelBenefits": "crate::login::AccountLevelBenefits",
"T:LibreMetaverse.AgentDisplayName": "crate::message_decoder::AgentDisplayName", "T:LibreMetaverse.AgentDisplayName": "crate::message_decoder::AgentDisplayName",
"T:LibreMetaverse.CapabilitiesReceivedEventArgs": "crate::caps::CapabilitiesReceivedEventArgs", "T:LibreMetaverse.CapabilitiesReceivedEventArgs": "crate::caps::CapabilitiesReceivedEventArgs",
"T:LibreMetaverse.Caps": "crate::caps::Caps", "T:LibreMetaverse.Caps": "crate::caps::Caps",
@@ -50,6 +51,13 @@ NATIVE_TYPES = {
"T:LibreMetaverse.Http.EventQueueClient.EventCallback": "crate::event_queue::EventQueueClientEventCallback", "T:LibreMetaverse.Http.EventQueueClient.EventCallback": "crate::event_queue::EventQueueClientEventCallback",
"T:LibreMetaverse.HttpCapsClient": "crate::caps_http::HttpCapsClient", "T:LibreMetaverse.HttpCapsClient": "crate::caps_http::HttpCapsClient",
"T:LibreMetaverse.HttpCapsClient.ProgressReport": "crate::caps_http::HttpCapsClientProgressReport", "T:LibreMetaverse.HttpCapsClient.ProgressReport": "crate::caps_http::HttpCapsClientProgressReport",
"T:LibreMetaverse.LoginCredential": "crate::login::LoginCredential",
"T:LibreMetaverse.LoginException": "crate::login::LoginException",
"T:LibreMetaverse.LoginParams": "crate::login::LoginParams",
"T:LibreMetaverse.LoginProgressEventArgs": "crate::login::LoginProgressEventArgs",
"T:LibreMetaverse.LoginResponseData": "crate::login::LoginResponseData",
"T:LibreMetaverse.LoginResponseData.ParseMessage": "crate::login::LoginResponseDataParseMessage",
"T:LibreMetaverse.LoginResponseData.ParseResult": "crate::login::LoginResponseDataParseResult",
"T:LibreMetaverse.MediaEntry": "crate::message_decoder::MediaEntry", "T:LibreMetaverse.MediaEntry": "crate::message_decoder::MediaEntry",
"T:LibreMetaverse.Caps.EventQueueCallback": "crate::network_manager::CapsEventQueueCallback", "T:LibreMetaverse.Caps.EventQueueCallback": "crate::network_manager::CapsEventQueueCallback",
"T:LibreMetaverse.CapsEventDictionary": "crate::network_manager::CapsEventDictionary", "T:LibreMetaverse.CapsEventDictionary": "crate::network_manager::CapsEventDictionary",
@@ -58,6 +66,7 @@ NATIVE_TYPES = {
"T:LibreMetaverse.GenericStreamingMessageEventArgs": "crate::network_manager::GenericStreamingMessageEventArgs", "T:LibreMetaverse.GenericStreamingMessageEventArgs": "crate::network_manager::GenericStreamingMessageEventArgs",
"T:LibreMetaverse.NetworkManager.IncomingPacket": "crate::network_manager::NetworkManagerIncomingPacket", "T:LibreMetaverse.NetworkManager.IncomingPacket": "crate::network_manager::NetworkManagerIncomingPacket",
"T:LibreMetaverse.NetworkManager.OutgoingPacket": "crate::network_manager::NetworkManagerOutgoingPacket", "T:LibreMetaverse.NetworkManager.OutgoingPacket": "crate::network_manager::NetworkManagerOutgoingPacket",
"T:LibreMetaverse.NetworkManager.LoginResponseCallback": "crate::login::NetworkManagerLoginResponseCallback",
"T:LibreMetaverse.PacketEventDictionary": "crate::network_manager::PacketEventDictionary", "T:LibreMetaverse.PacketEventDictionary": "crate::network_manager::PacketEventDictionary",
"T:LibreMetaverse.PacketReceivedEventArgs": "crate::network_manager::PacketReceivedEventArgs", "T:LibreMetaverse.PacketReceivedEventArgs": "crate::network_manager::PacketReceivedEventArgs",
"T:LibreMetaverse.PacketSentEventArgs": "crate::network_manager::PacketSentEventArgs", "T:LibreMetaverse.PacketSentEventArgs": "crate::network_manager::PacketSentEventArgs",
@@ -207,12 +216,18 @@ NATIVE_MEMBER_BODIES = {
"self.native_http_caps_client()", "self.native_http_caps_client()",
"P:LibreMetaverse.GridClient.HttpCapsClient#set": "P:LibreMetaverse.GridClient.HttpCapsClient#set":
"self.native_set_http_caps_client(value)", "self.native_set_http_caps_client(value)",
"P:LibreMetaverse.GridClient.Network":
"self.native_network().unwrap_or_else(|_| panic!(\"failed to construct NetworkManager\"))",
"P:LibreMetaverse.GridClient.Network#set":
"self.native_set_network(value)",
"E:LibreMetaverse.NetworkManager.Disconnected": "E:LibreMetaverse.NetworkManager.Disconnected":
"self.native_subscribe_disconnected(handler)", "self.native_subscribe_disconnected(handler)",
"E:LibreMetaverse.NetworkManager.EventQueueRunning": "E:LibreMetaverse.NetworkManager.EventQueueRunning":
"self.native_subscribe_event_queue_running(handler)", "self.native_subscribe_event_queue_running(handler)",
"E:LibreMetaverse.NetworkManager.GenericStreamingMessage": "E:LibreMetaverse.NetworkManager.GenericStreamingMessage":
"self.native_subscribe_generic_streaming_message(handler)", "self.native_subscribe_generic_streaming_message(handler)",
"E:LibreMetaverse.NetworkManager.LoginProgress":
"self.native_subscribe_login_progress(handler)",
"E:LibreMetaverse.NetworkManager.PacketSent": "E:LibreMetaverse.NetworkManager.PacketSent":
"self.native_subscribe_packet_sent(handler)", "self.native_subscribe_packet_sent(handler)",
"E:LibreMetaverse.NetworkManager.SimChanged": "E:LibreMetaverse.NetworkManager.SimChanged":
@@ -225,6 +240,10 @@ NATIVE_MEMBER_BODIES = {
"self.native_subscribe_sim_disconnected(handler)", "self.native_subscribe_sim_disconnected(handler)",
"M:LibreMetaverse.NetworkManager.#ctor(LibreMetaverse.GridClient)": "M:LibreMetaverse.NetworkManager.#ctor(LibreMetaverse.GridClient)":
"crate::network_manager::NetworkManager::native_new(client)", "crate::network_manager::NetworkManager::native_new(client)",
"M:LibreMetaverse.NetworkManager.AbortLogin":
"self.native_abort_login()",
"M:LibreMetaverse.NetworkManager.BeginLogin(LibreMetaverse.LoginParams)":
"self.native_begin_login(login_params)",
"M:LibreMetaverse.NetworkManager.Connect(System.Net.IPAddress,System.UInt16,System.UInt64,System.Boolean,System.Uri)": "M:LibreMetaverse.NetworkManager.Connect(System.Net.IPAddress,System.UInt16,System.UInt64,System.Boolean,System.Uri)":
"self.native_connect(std::net::SocketAddr::new(ip, port), handle, set_default, seedcaps, crate::network_manager::Simulator::DEFAULT_REGION_SIZE_X, crate::network_manager::Simulator::DEFAULT_REGION_SIZE_Y)", "self.native_connect(std::net::SocketAddr::new(ip, port), handle, set_default, seedcaps, crate::network_manager::Simulator::DEFAULT_REGION_SIZE_X, crate::network_manager::Simulator::DEFAULT_REGION_SIZE_Y)",
"M:LibreMetaverse.NetworkManager.Connect(System.Net.IPAddress,System.UInt16,System.UInt64,System.Boolean,System.Uri,System.UInt32,System.UInt32)": "M:LibreMetaverse.NetworkManager.Connect(System.Net.IPAddress,System.UInt16,System.UInt64,System.Boolean,System.Uri,System.UInt32,System.UInt32)":
@@ -261,6 +280,74 @@ NATIVE_MEMBER_BODIES = {
"crate::message_decoder::to_ip(osd)", "crate::message_decoder::to_ip(osd)",
"M:LibreMetaverse.NetworkManager.FindSimulator(System.UInt64)": "M:LibreMetaverse.NetworkManager.FindSimulator(System.UInt64)":
"Ok(self.native_find_handle(handle))", "Ok(self.native_find_handle(handle))",
"M:LibreMetaverse.NetworkManager.DefaultLoginParams(System.String,System.String,System.String,System.String,System.String)":
"self.native_default_login_params(first_name, last_name, password, channel, version)",
"M:LibreMetaverse.NetworkManager.GetHashedMAC":
"crate::login::hashed_mac()",
"M:LibreMetaverse.NetworkManager.GetMAC":
"Ok(crate::login::mac_address())",
"M:LibreMetaverse.NetworkManager.GetPlatform":
"Ok(crate::login::platform())",
"M:LibreMetaverse.NetworkManager.GetPlatformVersion":
"Ok(crate::login::platform_version())",
"M:LibreMetaverse.NetworkManager.RefreshMac":
"Ok(crate::login::refresh_mac())",
"M:LibreMetaverse.NetworkManager.Login(LibreMetaverse.LoginResponseData)":
"self.native_login_response(response)",
"M:LibreMetaverse.NetworkManager.LoginAsync(LibreMetaverse.LoginCredential,System.String,System.String,System.String,System.Threading.CancellationToken)":
"self.native_login_credential(credential, channel, Some(start), version, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginAsync(LibreMetaverse.LoginCredential,System.String,System.String,System.Threading.CancellationToken)":
"self.native_login_credential(credential, channel, None, version, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginAsync(LibreMetaverse.LoginParams,System.Threading.CancellationToken)":
"self.native_login(login_params, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginAsync(System.String,System.String,System.String,System.String,System.String,System.String,System.Threading.CancellationToken)":
"self.native_login_strings(first_name, last_name, password, channel, start, version, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginWithResponseAsync(LibreMetaverse.LoginCredential,System.String,System.String,System.String,System.Threading.CancellationToken)":
"self.native_login_credential_response(credential, channel, Some(start), version, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginWithResponseAsync(LibreMetaverse.LoginCredential,System.String,System.String,System.Threading.CancellationToken)":
"self.native_login_credential_response(credential, channel, None, version, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginWithResponseAsync(LibreMetaverse.LoginParams,System.Threading.CancellationToken)":
"self.native_login_response_async(login_params, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.LoginWithResponseAsync(System.String,System.String,System.String,System.String,System.String,System.String,System.Threading.CancellationToken)":
"self.native_login_strings_response(first_name, last_name, password, channel, start, version, cancellation_token).await",
"M:LibreMetaverse.NetworkManager.RegisterLoginResponseCallback(LibreMetaverse.NetworkManager.LoginResponseCallback)":
"self.native_register_login_response_callback(callback, None)",
"M:LibreMetaverse.NetworkManager.RegisterLoginResponseCallback(LibreMetaverse.NetworkManager.LoginResponseCallback,System.String[])":
"self.native_register_login_response_callback(callback, options)",
"M:LibreMetaverse.NetworkManager.StartLocation(System.String,System.Int32,System.Int32,System.Int32)":
"Ok(crate::login::start_location(&sim, x, y, z))",
"M:LibreMetaverse.NetworkManager.UnregisterLoginResponseCallback(LibreMetaverse.NetworkManager.LoginResponseCallback)":
"self.native_unregister_login_response_callback(&callback)",
"M:LibreMetaverse.InventoryFolder.#ctor(LibreMetaverse.UUID)":
"Ok(Self { base: crate::InventoryBase { native_uuid: uuid, native_owner_id: libremetaverse_types::UUID::zero(), native_parent_uuid: libremetaverse_types::UUID::zero(), native_name: String::new() }, native_descendent_count: 0, native_preferred_type: libremetaverse_types::FolderType::None, native_version: Self::VERSION_UNKNOWN })",
"P:LibreMetaverse.InventoryBase.Name":
"self.native_name.clone()",
"P:LibreMetaverse.InventoryBase.Name#set":
"self.native_name = value",
"P:LibreMetaverse.InventoryBase.OwnerID":
"self.native_owner_id",
"P:LibreMetaverse.InventoryBase.OwnerID#set":
"self.native_owner_id = value",
"P:LibreMetaverse.InventoryBase.ParentUUID":
"self.native_parent_uuid",
"P:LibreMetaverse.InventoryBase.ParentUUID#set":
"self.native_parent_uuid = value",
"P:LibreMetaverse.InventoryBase.UUID":
"self.native_uuid",
"P:LibreMetaverse.InventoryBase.UUID#set":
"self.native_uuid = value",
"P:LibreMetaverse.InventoryFolder.DescendentCount":
"self.native_descendent_count",
"P:LibreMetaverse.InventoryFolder.DescendentCount#set":
"self.native_descendent_count = value",
"P:LibreMetaverse.InventoryFolder.PreferredType":
"self.native_preferred_type",
"P:LibreMetaverse.InventoryFolder.PreferredType#set":
"self.native_preferred_type = value",
"P:LibreMetaverse.InventoryFolder.Version":
"self.native_version",
"P:LibreMetaverse.InventoryFolder.Version#set":
"self.native_version = value",
"M:LibreMetaverse.NetworkManager.RegisterCallback(LibreMetaverse.Packets.PacketType,System.EventHandler{LibreMetaverse.PacketReceivedEventArgs})": "M:LibreMetaverse.NetworkManager.RegisterCallback(LibreMetaverse.Packets.PacketType,System.EventHandler{LibreMetaverse.PacketReceivedEventArgs})":
"self.native_register_callback(type_, callback, true)", "self.native_register_callback(type_, callback, true)",
"M:LibreMetaverse.NetworkManager.RegisterCallback(LibreMetaverse.Packets.PacketType,System.EventHandler{LibreMetaverse.PacketReceivedEventArgs},System.Boolean)": "M:LibreMetaverse.NetworkManager.RegisterCallback(LibreMetaverse.Packets.PacketType,System.EventHandler{LibreMetaverse.PacketReceivedEventArgs},System.Boolean)":
@@ -295,6 +382,28 @@ NATIVE_MEMBER_BODIES = {
"self.native_set_current_sim(value)", "self.native_set_current_sim(value)",
"P:LibreMetaverse.NetworkManager.InboxCount": "P:LibreMetaverse.NetworkManager.InboxCount":
"self.native_inbox_count()", "self.native_inbox_count()",
"P:LibreMetaverse.NetworkManager.AccountLevelBenefits":
"self.native_account_level_benefits()",
"P:LibreMetaverse.NetworkManager.AgentAppearanceServiceURL":
"self.native_agent_appearance_service_url()",
"P:LibreMetaverse.NetworkManager.LoginErrorKey":
"self.native_login_error_key()",
"P:LibreMetaverse.NetworkManager.LoginErrorKey#set":
"self.native_set_login_error_key(value)",
"P:LibreMetaverse.NetworkManager.LoginMessage":
"self.native_login_message()",
"P:LibreMetaverse.NetworkManager.LoginMessage#set":
"self.native_set_login_message(value)",
"P:LibreMetaverse.NetworkManager.LoginSeedCapability":
"self.native_login_seed_capability()",
"P:LibreMetaverse.NetworkManager.LoginSeedCapability#set":
"self.native_set_login_seed_capability(value)",
"P:LibreMetaverse.NetworkManager.LoginStatusCode":
"self.native_login_status_code()",
"P:LibreMetaverse.NetworkManager.LoginStatusCode#set":
"self.native_set_login_status_code(value)",
"P:LibreMetaverse.NetworkManager.MaxAgentGroups":
"self.native_max_agent_groups()",
"P:LibreMetaverse.NetworkManager.OutboxCount": "P:LibreMetaverse.NetworkManager.OutboxCount":
"self.native_outbox_count()", "self.native_outbox_count()",
"M:LibreMetaverse.Simulator.#ctor(LibreMetaverse.GridClient,System.Net.IPEndPoint,System.UInt64,System.UInt32,System.UInt32)": "M:LibreMetaverse.Simulator.#ctor(LibreMetaverse.GridClient,System.Net.IPEndPoint,System.UInt64,System.UInt32,System.UInt32)":
@@ -516,6 +625,8 @@ CLIENT_CORE_NATIVE_OWNERS = (
) )
NETWORK_NATIVE_OWNERS = ( NETWORK_NATIVE_OWNERS = (
"InventoryBase",
"InventoryFolder",
"NetworkManager", "NetworkManager",
"Simulator", "Simulator",
) )
@@ -906,6 +1017,17 @@ PRIVATE_LAYOUTS = {
], ],
"T:LibreMetaverse.EmptyRemovalStrategy`1": [("keys", "Vec<TKey>")], "T:LibreMetaverse.EmptyRemovalStrategy`1": [("keys", "Vec<TKey>")],
"T:LibreMetaverse.ExpiringCache`2": [("entries", "std::collections::HashMap<TKey, TValue>")], "T:LibreMetaverse.ExpiringCache`2": [("entries", "std::collections::HashMap<TKey, TValue>")],
"T:LibreMetaverse.InventoryBase": [
("native_uuid", "libremetaverse_types::UUID"),
("native_owner_id", "libremetaverse_types::UUID"),
("native_parent_uuid", "libremetaverse_types::UUID"),
("native_name", "String"),
],
"T:LibreMetaverse.InventoryFolder": [
("native_descendent_count", "i32"),
("native_preferred_type", "libremetaverse_types::FolderType"),
("native_version", "i32"),
],
"T:LibreMetaverse.LruRemovalStrategy`1": [("keys", "Vec<TKey>")], "T:LibreMetaverse.LruRemovalStrategy`1": [("keys", "Vec<TKey>")],
"T:LibreMetaverse.MruRemovalStrategy`1": [("keys", "Vec<TKey>")], "T:LibreMetaverse.MruRemovalStrategy`1": [("keys", "Vec<TKey>")],
"T:LibreMetaverse.MultiValueDictionary`2": [("entries", "std::collections::HashMap<TKey, Vec<TValue>>")], "T:LibreMetaverse.MultiValueDictionary`2": [("entries", "std::collections::HashMap<TKey, Vec<TValue>>")],
@@ -926,8 +1048,12 @@ PRIVATE_LAYOUTS = {
VALUE_DERIVES = { VALUE_DERIVES = {
"T:LibreMetaverse.AgentSettings": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.AgentSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.AssetCacheSettings": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.AssetCacheSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.BuddyListEntry": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.ConnectionSettings": "Clone, Eq, PartialEq", "T:LibreMetaverse.ConnectionSettings": "Clone, Eq, PartialEq",
"T:LibreMetaverse.LoggingSettings": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.LoggingSettings": "Clone, Debug, Eq, PartialEq",
"T:LibreMetaverse.HomeInfo": "Clone, Copy, Debug, PartialEq",
"T:LibreMetaverse.InventoryBase": "Clone, Debug",
"T:LibreMetaverse.InventoryFolder": "Clone, Debug",
"T:LibreMetaverse.Matrix4": "Clone, Copy, Debug", "T:LibreMetaverse.Matrix4": "Clone, Copy, Debug",
"T:LibreMetaverse.Quaternion": "Clone, Copy, Debug", "T:LibreMetaverse.Quaternion": "Clone, Copy, Debug",
"T:LibreMetaverse.PacketSettings": "Clone, Debug, Eq, PartialEq", "T:LibreMetaverse.PacketSettings": "Clone, Debug, Eq, PartialEq",

View File

@@ -992,6 +992,7 @@ def validate_generated_shims() -> None:
"crate::genepool_catalog::", "crate::genepool_catalog::",
"crate::json_codec::", "crate::json_codec::",
"crate::message_codec::", "crate::message_codec::",
"crate::message_decoder::",
"crate::notation::", "crate::notation::",
"crate::packet_catalog::", "crate::packet_catalog::",
"crate::packet_wire::", "crate::packet_wire::",