diff --git a/Cargo.lock b/Cargo.lock index 1b14e82..b9456a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,109 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + [[package]] name = "libremetaverse" version = "0.0.1" @@ -107,6 +210,11 @@ dependencies = [ [[package]] name = "libremetaverse-types" version = "0.0.1" +dependencies = [ + "getrandom", + "md-5", + "uuid", +] [[package]] name = "libremetaverse-utilities" @@ -134,3 +242,146 @@ dependencies = [ "libremetaverse-structured-data", "libremetaverse-types", ] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] diff --git a/README.md b/README.md index f8d7006..4931aaf 100644 --- a/README.md +++ b/README.md @@ -48,5 +48,13 @@ verifies the catalog against the pinned adjacent LibreMetaverse checkout without overwriting those files. Running `cargo test --workspace` is intentionally red during the shim stage. + +## Native implementation progress + +Milestone 04 implementation has begun. `libremetaverse-types` now provides +native UUID, incremental CRC-32, and little-endian floating-point byte helpers. +UUID protocol bytes use explicit network order while `compat::Guid` conversions +preserve the mixed-endian .NET byte-array layout. The remaining Types surfaces +stay visibly failure-only until their owning milestone issues are completed. The controlled audit aggregates every expected failure by standardized C# member ID and rejects unrelated fixture, assertion, compile, or symbol errors. diff --git a/api/README.md b/api/README.md index 4a74c18..cc9f78b 100644 --- a/api/README.md +++ b/api/README.md @@ -45,9 +45,10 @@ python3 tools/check_api_coverage.py The checker rejects duplicate C# IDs, duplicate Rust destinations, invalid statuses, stale or missing catalog entries, unresolved signature types, assemblies without representative mappings, and platform-specific API targets. -It also rejects generated `Default` shims, plausible false/zero/empty/nil -fallbacks, and generated function bodies that do not use the standardized -`Error::NotImplemented` result or `unimplemented_api!` panic. +It also rejects generated `Default` shims and plausible false/zero/empty/nil +fallbacks. Failure-only bodies must use the standardized +`Error::NotImplemented` result or `unimplemented_api!` panic; reviewed native +member hooks must call an explicitly registered hand-written implementation. The two support traits in `RUST-TYPES.tsv` are included because compiled public inheritance records reference them even though they are absent from the public type table. @@ -59,3 +60,10 @@ compares catalog IDs, mapping rows, exported shim IDs, and fixture probes; rejects stale, missing, duplicate, or erased entries; and verifies `API-COVERAGE.md`. Regenerate that report with `python3 tools/check_api_coverage.py --write`. + +Native implementations replace whole generated types where practical. The +generated module retains the catalog markers and re-exports the hand-written +type; member-level implementations use an explicit generator registry. This +keeps regeneration deterministic without moving behavioral code into Python. +`SHIM-COVERAGE.md` reports native type/member totals separately from remaining +failure-only surfaces. diff --git a/api/RUST-MAPPING.tsv b/api/RUST-MAPPING.tsv index 27ae6ef..ac3eb25 100644 --- a/api/RUST-MAPPING.tsv +++ b/api/RUST-MAPPING.tsv @@ -10816,8 +10816,8 @@ M:LibreMetaverse.BitPack.UnpackUInt method System.UInt32 LibreMetaverse.BitPack. M:LibreMetaverse.BitPack.UnpackUShort method System.UInt16 LibreMetaverse.BitPack.UnpackUShort() libremetaverse libremetaverse::BitPack::unpack_u_short pub fn unpack_u_short(&mut self) -> Result mutable_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.BitPack.UnpackUUID method LibreMetaverse.UUID LibreMetaverse.BitPack.UnpackUUID() libremetaverse libremetaverse::BitPack::unpack_uuid pub fn unpack_uuid(&mut self) -> Result mutable_self sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.CRC32.#ctor constructor LibreMetaverse.CRC32() libremetaverse-types libremetaverse_types::CRC32::new pub fn new() -> Result none sync Result<_, crate::Error> direct_snake_case method mapped -M:LibreMetaverse.CRC32.Update(System.Byte) method System.Void LibreMetaverse.CRC32.Update(System.Byte value) libremetaverse-types libremetaverse_types::CRC32::update_with_byte pub fn update_with_byte(&self, value: u8) -> Result<(), crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped -M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32) method System.Void LibreMetaverse.CRC32.Update(System.Byte[] value, System.Int32 pos, System.Int32 length) libremetaverse-types libremetaverse_types::CRC32::update_with_bytes_int32_int32 pub fn update_with_bytes_int32_int32(&self, value: Vec, pos: i32, length: i32) -> Result<(), crate::Error> shared_self,owned,owned,owned sync Result<_, crate::Error> descriptive_overload_name method mapped +M:LibreMetaverse.CRC32.Update(System.Byte) method System.Void LibreMetaverse.CRC32.Update(System.Byte value) libremetaverse-types libremetaverse_types::CRC32::update_with_byte pub fn update_with_byte(&mut self, value: u8) -> Result<(), crate::Error> mutable_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped +M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32) method System.Void LibreMetaverse.CRC32.Update(System.Byte[] value, System.Int32 pos, System.Int32 length) libremetaverse-types libremetaverse_types::CRC32::update_with_bytes_int32_int32 pub fn update_with_bytes_int32_int32(&mut self, value: Vec, pos: i32, length: i32) -> Result<(), crate::Error> mutable_self,owned,owned,owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.CacheDictionary`2.#ctor(System.Int32,LibreMetaverse.ICacheDictionaryRemovalStrategy{`0}) constructor LibreMetaverse.CacheDictionary(System.Int32 maxSize, LibreMetaverse.ICacheDictionaryRemovalStrategy removalStrategy) libremetaverse-types libremetaverse_types::CacheDictionary::new pub fn new(max_size: i32, removal_strategy: Box>) -> Result owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.CacheDictionary`2.Add(System.Collections.Generic.KeyValuePair{`0,`1}) method System.Void LibreMetaverse.CacheDictionary.Add(System.Collections.Generic.KeyValuePair item) libremetaverse-types libremetaverse_types::CacheDictionary::add_with_key_value_pair pub fn add_with_key_value_pair(&self, item: (TKey, Option)) -> Result<(), crate::Error> shared_self,owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.CacheDictionary`2.Add(`0,`1) method System.Void LibreMetaverse.CacheDictionary.Add(TKey key, TValue value) libremetaverse-types libremetaverse_types::CacheDictionary::add_with_t_key_t_value pub fn add_with_t_key_t_value(&self, key: TKey, value: Option) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> descriptive_overload_name method mapped @@ -25081,7 +25081,7 @@ M:LibreMetaverse.UUID.GetULong method System.UInt64 LibreMetaverse.UUID.GetULong M:LibreMetaverse.UUID.Parse(System.String) method LibreMetaverse.UUID LibreMetaverse.UUID.Parse(System.String val) libremetaverse-types libremetaverse_types::UUID::parse pub fn parse(val: String) -> Result owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.UUID.Random method LibreMetaverse.UUID LibreMetaverse.UUID.Random() libremetaverse-types libremetaverse_types::UUID::random pub fn random() -> Result none sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.UUID.SecureRandom method LibreMetaverse.UUID LibreMetaverse.UUID.SecureRandom() libremetaverse-types libremetaverse_types::UUID::secure_random pub fn secure_random() -> Result none sync Result<_, crate::Error> direct_snake_case method mapped -M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32) method System.Void LibreMetaverse.UUID.ToBytes(System.Byte[] dest, System.Int32 pos) libremetaverse-types libremetaverse_types::UUID::to_bytes pub fn to_bytes(&self, dest: Vec, pos: i32) -> Result<(), crate::Error> shared_self,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped +M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32) method System.Void LibreMetaverse.UUID.ToBytes(System.Byte[] dest, System.Int32 pos) libremetaverse-types libremetaverse_types::UUID::to_bytes pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), crate::Error> shared_self,mutable_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.UUID.ToString method System.String LibreMetaverse.UUID.ToString() libremetaverse-types libremetaverse_types::UUID::to_string pub fn to_string(&self) -> String shared_self sync infallible_placeholder direct_snake_case method mapped M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@) method System.Boolean LibreMetaverse.UUID.TryParse(System.String val, out LibreMetaverse.UUID& result) libremetaverse-types libremetaverse_types::UUID::try_parse pub fn try_parse(val: String, result: &mut libremetaverse_types::UUID) -> bool owned,mutable_output sync infallible_placeholder direct_snake_case method mapped M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID) method System.Boolean LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID left, LibreMetaverse.UUID right) libremetaverse-types libremetaverse_types::UUID::eq pub fn eq(left: libremetaverse_types::UUID, right: libremetaverse_types::UUID) -> bool owned,owned sync infallible_placeholder direct_snake_case method mapped @@ -25159,8 +25159,8 @@ M:LibreMetaverse.Utils.MD5(System.String) method System.String LibreMetaverse.Ut M:LibreMetaverse.Utils.MD5String(System.String) method System.String LibreMetaverse.Utils.MD5String(System.String value) libremetaverse-types libremetaverse_types::Utils::md5_string pub fn md5_string(value: String) -> Result owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Utils.PBKDF2(System.String) method System.String LibreMetaverse.Utils.PBKDF2(System.String str) libremetaverse-types libremetaverse_types::Utils::pbkdf2 pub fn pbkdf2(str: String) -> Result owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Utils.RandomDouble method System.Double LibreMetaverse.Utils.RandomDouble() libremetaverse-types libremetaverse_types::Utils::random_double pub fn random_double() -> Result none sync Result<_, crate::Error> direct_snake_case method mapped -M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32) method System.Double LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[] src, System.Int32 pos) libremetaverse-types libremetaverse_types::Utils::read_double_little_endian pub fn read_double_little_endian(src: Vec, pos: i32) -> Result owned,owned sync Result<_, crate::Error> direct_snake_case method mapped -M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32) method System.Single LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[] src, System.Int32 pos) libremetaverse-types libremetaverse_types::Utils::read_single_little_endian pub fn read_single_little_endian(src: Vec, pos: i32) -> Result owned,owned sync Result<_, crate::Error> direct_snake_case method mapped +M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32) method System.Double LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[] src, System.Int32 pos) libremetaverse-types libremetaverse_types::Utils::read_double_little_endian pub fn read_double_little_endian(src: &[u8], pos: i32) -> Result shared_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped +M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32) method System.Single LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[] src, System.Int32 pos) libremetaverse-types libremetaverse_types::Utils::read_single_little_endian pub fn read_single_little_endian(src: &[u8], pos: i32) -> Result shared_borrow,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Utils.Round(System.Single) method System.Int32 LibreMetaverse.Utils.Round(System.Single val) libremetaverse-types libremetaverse_types::Utils::round pub fn round(val: f32) -> Result owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Utils.SHA1(System.Byte[]) method System.Byte[] LibreMetaverse.Utils.SHA1(System.Byte[] data) libremetaverse-types libremetaverse_types::Utils::sha1 pub fn sha1(data: Vec) -> Result, crate::Error> owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Utils.SHA1String(System.String) method System.String LibreMetaverse.Utils.SHA1String(System.String value) libremetaverse-types libremetaverse_types::Utils::sha1_string pub fn sha1_string(value: String) -> Result owned sync Result<_, crate::Error> direct_snake_case method mapped @@ -25198,8 +25198,8 @@ M:LibreMetaverse.Utils.UIntToHexString(System.UInt32) method System.String Libre M:LibreMetaverse.Utils.UIntsToLong(System.UInt32,System.UInt32) method System.UInt64 LibreMetaverse.Utils.UIntsToLong(System.UInt32 a, System.UInt32 b) libremetaverse-types libremetaverse_types::Utils::u_ints_to_long pub fn u_ints_to_long(a: u32, b: u32) -> Result owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Utils.UnixTimeToDateTime(System.Int32) method System.DateTime LibreMetaverse.Utils.UnixTimeToDateTime(System.Int32 timestamp) libremetaverse-types libremetaverse_types::Utils::unix_time_to_date_time_with_int32 pub fn unix_time_to_date_time_with_int32(timestamp: i32) -> Result owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.Utils.UnixTimeToDateTime(System.UInt32) method System.DateTime LibreMetaverse.Utils.UnixTimeToDateTime(System.UInt32 timestamp) libremetaverse-types libremetaverse_types::Utils::unix_time_to_date_time_with_u_int32 pub fn unix_time_to_date_time_with_u_int32(timestamp: u32) -> Result owned sync Result<_, crate::Error> descriptive_overload_name method mapped -M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double) method System.Void LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[] dest, System.Int32 pos, System.Double value) libremetaverse-types libremetaverse_types::Utils::write_double_little_endian pub fn write_double_little_endian(dest: Vec, pos: i32, value: f64) -> Result<(), crate::Error> owned,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped -M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single) method System.Void LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[] dest, System.Int32 pos, System.Single value) libremetaverse-types libremetaverse_types::Utils::write_single_little_endian pub fn write_single_little_endian(dest: Vec, pos: i32, value: f32) -> Result<(), crate::Error> owned,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped +M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double) method System.Void LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[] dest, System.Int32 pos, System.Double value) libremetaverse-types libremetaverse_types::Utils::write_double_little_endian pub fn write_double_little_endian(dest: &mut [u8], pos: i32, value: f64) -> Result<(), crate::Error> mutable_borrow,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped +M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single) method System.Void LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[] dest, System.Int32 pos, System.Single value) libremetaverse-types libremetaverse_types::Utils::write_single_little_endian pub fn write_single_little_endian(dest: &mut [u8], pos: i32, value: f32) -> Result<(), crate::Error> mutable_borrow,owned,owned sync Result<_, crate::Error> direct_snake_case method mapped M:LibreMetaverse.Vector2.#ctor(LibreMetaverse.Vector2) constructor LibreMetaverse.Vector2(LibreMetaverse.Vector2 vector) libremetaverse-types libremetaverse_types::Vector2::new_with_vector2 pub fn new_with_vector2(vector: libremetaverse_types::Vector2) -> Result owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.Vector2.#ctor(System.Single) constructor LibreMetaverse.Vector2(System.Single value) libremetaverse-types libremetaverse_types::Vector2::new_with_single pub fn new_with_single(value: f32) -> Result owned sync Result<_, crate::Error> descriptive_overload_name method mapped M:LibreMetaverse.Vector2.#ctor(System.Single,System.Single) constructor LibreMetaverse.Vector2(System.Single x, System.Single y) libremetaverse-types libremetaverse_types::Vector2::new_with_single_single pub fn new_with_single_single(x: f32, y: f32) -> Result owned,owned sync Result<_, crate::Error> descriptive_overload_name method mapped diff --git a/api/SHIM-COVERAGE.md b/api/SHIM-COVERAGE.md index 57f7d41..d050fe7 100644 --- a/api/SHIM-COVERAGE.md +++ b/api/SHIM-COVERAGE.md @@ -13,7 +13,7 @@ Generated by `python3 tools/generate_api_shims.py`; do not edit by hand. | `LibreMetaverse.Rendering.MeshFoundry` | 1 | 14 | callable failure-only shim | | `LibreMetaverse.Rendering.Simple` | 1 | 6 | callable failure-only shim | | `LibreMetaverse.StructuredData` | 16 | 295 | callable failure-only shim | -| `LibreMetaverse.Types` | 45 | 942 | callable failure-only shim | +| `LibreMetaverse.Types` | 45 | 942 | native implementation: 2 types / 34 members; remaining surface is callable failure-only shims | | `LibreMetaverse.Utilities` | 3 | 13 | callable failure-only shim | | `LibreMetaverse.Voice.Vivox` | 64 | 531 | callable failure-only shim | | `LibreMetaverse.Voice.WebRTC` | 12 | 210 | callable failure-only shim | diff --git a/crates/libremetaverse-types/Cargo.toml b/crates/libremetaverse-types/Cargo.toml index b2d580b..763811e 100644 --- a/crates/libremetaverse-types/Cargo.toml +++ b/crates/libremetaverse-types/Cargo.toml @@ -9,3 +9,8 @@ description = "Core value-type shims for the MetaCrate LibreMetaverse rewrite" [lints] workspace = true + +[dependencies] +getrandom = "0.4" +md-5 = "0.10" +uuid = { version = "1.18", features = ["v4"] } diff --git a/crates/libremetaverse-types/src/byte_order.rs b/crates/libremetaverse-types/src/byte_order.rs new file mode 100644 index 0000000..e4576cc --- /dev/null +++ b/crates/libremetaverse-types/src/byte_order.rs @@ -0,0 +1,91 @@ +//! Platform-independent byte-order helpers owned by `Utils`. + +use crate::Error; + +fn checked_range(pos: i32, width: usize, len: usize) -> Result, Error> { + let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?; + let end = start.checked_add(width).ok_or(Error::IndexOutOfRange)?; + if end > len { + return Err(Error::IndexOutOfRange); + } + Ok(start..end) +} + +pub(crate) fn read_single_little_endian(src: &[u8], pos: i32) -> Result { + let range = checked_range(pos, 4, src.len())?; + let bytes: [u8; 4] = src[range] + .try_into() + .expect("range has the requested width"); + Ok(f32::from_le_bytes(bytes)) +} + +pub(crate) fn write_single_little_endian( + dest: &mut [u8], + pos: i32, + value: f32, +) -> Result<(), Error> { + let range = checked_range(pos, 4, dest.len())?; + dest[range].copy_from_slice(&value.to_le_bytes()); + Ok(()) +} + +pub(crate) fn read_double_little_endian(src: &[u8], pos: i32) -> Result { + let range = checked_range(pos, 8, src.len())?; + let bytes: [u8; 8] = src[range] + .try_into() + .expect("range has the requested width"); + Ok(f64::from_le_bytes(bytes)) +} + +pub(crate) fn write_double_little_endian( + dest: &mut [u8], + pos: i32, + value: f64, +) -> Result<(), Error> { + let range = checked_range(pos, 8, dest.len())?; + dest[range].copy_from_slice(&value.to_le_bytes()); + Ok(()) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::float_cmp)] // Exact bit-preserving round trips are the behavior under test. + + use super::*; + + #[test] + fn little_endian_float_golden_vectors_round_trip_at_offsets() { + let mut bytes = [0xaa; 14]; + write_single_little_endian(&mut bytes, 1, -12.5).unwrap(); + assert_eq!(&bytes[1..5], &(-12.5_f32).to_bits().to_le_bytes()); + assert_eq!( + read_single_little_endian(&bytes, 1).unwrap().to_bits(), + (-12.5_f32).to_bits() + ); + + write_double_little_endian(&mut bytes, 5, std::f64::consts::PI).unwrap(); + assert_eq!(&bytes[5..13], &std::f64::consts::PI.to_bits().to_le_bytes()); + assert_eq!( + read_double_little_endian(&bytes, 5).unwrap().to_bits(), + std::f64::consts::PI.to_bits() + ); + assert_eq!(bytes[0], 0xaa); + assert_eq!(bytes[13], 0xaa); + } + + #[test] + fn byte_order_helpers_reject_invalid_ranges() { + assert_eq!( + read_single_little_endian(&[0; 4], -1), + Err(Error::IndexOutOfRange) + ); + assert_eq!( + read_double_little_endian(&[0; 7], 0), + Err(Error::IndexOutOfRange) + ); + assert_eq!( + write_single_little_endian(&mut [0; 4], 1, 0.0), + Err(Error::IndexOutOfRange) + ); + } +} diff --git a/crates/libremetaverse-types/src/compat.rs b/crates/libremetaverse-types/src/compat.rs index 3b9c7c0..d074390 100644 --- a/crates/libremetaverse-types/src/compat.rs +++ b/crates/libremetaverse-types/src/compat.rs @@ -22,6 +22,7 @@ impl ReadWrite for T {} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum Object { String(String), + UUID(crate::UUID), } impl From for Object { diff --git a/crates/libremetaverse-types/src/crc32.rs b/crates/libremetaverse-types/src/crc32.rs new file mode 100644 index 0000000..2d4fb44 --- /dev/null +++ b/crates/libremetaverse-types/src/crc32.rs @@ -0,0 +1,119 @@ +//! Native implementation of `LibreMetaverse`'s incremental CRC-32 accumulator. + +#![allow(clippy::needless_pass_by_value)] // Public signature mirrors the mapped C# byte array. + +use crate::Error; + +/// Incremental reflected CRC-32 accumulator using polynomial `0xedb88320`. +/// +/// `LibreMetaverse` exposes the running (not final-XORed) accumulator. A new +/// value therefore starts at `0xffff_ffff`, and callers may apply a final XOR +/// themselves if they need the conventional ISO-HDLC checksum. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct CRC32 { + /// Current running CRC value. + pub crc: u32, +} + +impl CRC32 { + /// Creates an accumulator initialized to `0xffff_ffff`. + /// + /// # Errors + /// + /// This compatibility signature is infallible and always returns `Ok`. + pub fn new() -> Result { + Ok(Self { crc: u32::MAX }) + } + + /// Incorporates one byte into the running accumulator. + /// + /// # Errors + /// + /// This compatibility signature is infallible and always returns `Ok`. + pub fn update_with_byte(&mut self, value: u8) -> Result<(), Error> { + self.crc = update(self.crc, value); + Ok(()) + } + + /// Updates bytes in the half-open range `pos..length`. + /// + /// The final argument intentionally preserves the pinned C# implementation: + /// it is an exclusive end index, despite being named `length`. + /// + /// # Errors + /// + /// Returns [`Error::IndexOutOfRange`] when an iterated index is outside + /// `value`. As in C#, bytes before the invalid index remain incorporated. + pub fn update_with_bytes_int32_int32( + &mut self, + value: Vec, + pos: i32, + length: i32, + ) -> Result<(), Error> { + if pos >= length { + return Ok(()); + } + for index in pos..length { + let index = usize::try_from(index).map_err(|_| Error::IndexOutOfRange)?; + let byte = *value.get(index).ok_or(Error::IndexOutOfRange)?; + self.crc = update(self.crc, byte); + } + Ok(()) + } +} + +fn update(mut crc: u32, byte: u8) -> u32 { + crc ^= u32::from(byte); + for _ in 0..8 { + let mask = 0_u32.wrapping_sub(crc & 1); + crc = (crc >> 1) ^ (0xedb8_8320 & mask); + } + crc +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn crc32_matches_standard_and_incremental_golden_vectors() { + let mut crc = CRC32::new().unwrap(); + crc.update_with_bytes_int32_int32(b"123456789".to_vec(), 0, 9) + .unwrap(); + assert_eq!(crc.crc, 0x340b_c6d9); + assert_eq!(crc.crc ^ u32::MAX, 0xcbf4_3926); + + let mut incremental = CRC32::new().unwrap(); + for byte in b"123456789" { + incremental.update_with_byte(*byte).unwrap(); + } + assert_eq!(incremental, crc); + } + + #[test] + fn range_argument_is_an_exclusive_end_index() { + let mut selected = CRC32::new().unwrap(); + selected + .update_with_bytes_int32_int32(b"x123y".to_vec(), 1, 4) + .unwrap(); + let mut direct = CRC32::new().unwrap(); + direct + .update_with_bytes_int32_int32(b"123".to_vec(), 0, 3) + .unwrap(); + assert_eq!(selected, direct); + } + + #[test] + fn invalid_end_index_preserves_csharp_partial_update_behavior() { + let mut partial = CRC32::new().unwrap(); + assert_eq!( + partial.update_with_bytes_int32_int32(b"12".to_vec(), 0, 3), + Err(Error::IndexOutOfRange) + ); + let mut expected = CRC32::new().unwrap(); + expected + .update_with_bytes_int32_int32(b"12".to_vec(), 0, 2) + .unwrap(); + assert_eq!(partial, expected); + } +} diff --git a/crates/libremetaverse-types/src/generated.rs b/crates/libremetaverse-types/src/generated.rs index b0c4a95..b4c0bd6 100644 --- a/crates/libremetaverse-types/src/generated.rs +++ b/crates/libremetaverse-types/src/generated.rs @@ -184,31 +184,11 @@ pub enum AttachmentPoint { } /// C# type: `T:LibreMetaverse.CRC32`. -pub struct CRC32 { - /// C# member: `F:LibreMetaverse.CRC32.CRC`. - pub crc: u32, -} -impl CRC32 { - /// C# member: `M:LibreMetaverse.CRC32.#ctor`. - pub fn new() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.CRC32.#ctor") - } - /// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte)`. - pub fn update_with_byte(&self, value: u8) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.CRC32.Update(System.Byte)") - } - /// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32)`. - pub fn update_with_bytes_int32_int32( - &self, - value: Vec, - pos: i32, - length: i32, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32)", - ) - } -} +/// C# member: `F:LibreMetaverse.CRC32.CRC`. +/// C# member: `M:LibreMetaverse.CRC32.#ctor`. +/// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte)`. +/// C# member: `M:LibreMetaverse.CRC32.Update(System.Byte[],System.Int32,System.Int32)`. +pub use crate::crc32::CRC32; /// C# type: `T:LibreMetaverse.CacheDictionary`2`. pub struct CacheDictionary { @@ -2658,154 +2638,33 @@ impl TokenBucket { } /// C# type: `T:LibreMetaverse.UUID`. -#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] -pub struct UUID { - bytes: [u8; 16], -} -impl UUID { - /// C# member: `F:LibreMetaverse.UUID.Zero`. - pub fn zero() -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!("F:LibreMetaverse.UUID.Zero") - } - /// C# member: `M:LibreMetaverse.UUID.#ctor(System.Byte[],System.Int32)`. - pub fn new_with_bytes_int32(source: Vec, pos: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.UUID.#ctor(System.Byte[],System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.UUID.#ctor(System.Guid)`. - pub fn new_with_guid(guid: libremetaverse_types::compat::Guid) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.#ctor(System.Guid)") - } - /// C# member: `M:LibreMetaverse.UUID.#ctor(System.String)`. - pub fn new_with_string(val: String) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.#ctor(System.String)") - } - /// C# member: `M:LibreMetaverse.UUID.#ctor(System.UInt64)`. - pub fn new_with_u_int64(val: u64) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.#ctor(System.UInt64)") - } - /// C# member: `M:LibreMetaverse.UUID.CRC`. - pub fn crc(&mut self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.CRC") - } - /// C# member: `M:LibreMetaverse.UUID.Combine(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn combine( - first: libremetaverse_types::UUID, - second: libremetaverse_types::UUID, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.UUID.Combine(LibreMetaverse.UUID,LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.UUID.CompareTo(LibreMetaverse.UUID)`. - pub fn compare_to(&self, id: libremetaverse_types::UUID) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.UUID.CompareTo(LibreMetaverse.UUID)", - ) - } - /// C# member: `M:LibreMetaverse.UUID.Deconstruct(System.Guid@)`. - pub fn deconstruct( - &mut self, - guid: &mut libremetaverse_types::compat::Guid, - ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.Deconstruct(System.Guid@)") - } - /// C# member: `M:LibreMetaverse.UUID.Equals(LibreMetaverse.UUID)`. - pub fn equals_with_uuid(&self, other: libremetaverse_types::UUID) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.UUID.Equals(LibreMetaverse.UUID)" - ) - } - /// C# member: `M:LibreMetaverse.UUID.Equals(System.Object)`. - pub fn equals_with_object(&self, obj: libremetaverse_types::compat::Object) -> bool { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.UUID.Equals(System.Object)") - } - /// C# member: `M:LibreMetaverse.UUID.FromBytes(System.Byte[],System.Int32)`. - pub fn from_bytes( - source: Vec, - pos: i32, - ) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.UUID.FromBytes(System.Byte[],System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.UUID.GetBytes`. - pub fn get_bytes(&self) -> Result, crate::Error> { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.GetBytes") - } - /// C# member: `M:LibreMetaverse.UUID.GetHashCode`. - pub fn get_hash_code(&self) -> i32 { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.UUID.GetHashCode") - } - /// C# member: `M:LibreMetaverse.UUID.GetULong`. - pub fn get_u_long(&self) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.GetULong") - } - /// C# member: `M:LibreMetaverse.UUID.Parse(System.String)`. - pub fn parse(val: String) -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.Parse(System.String)") - } - /// C# member: `M:LibreMetaverse.UUID.Random`. - pub fn random() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.Random") - } - /// C# member: `M:LibreMetaverse.UUID.SecureRandom`. - pub fn secure_random() -> Result { - libremetaverse_types::not_implemented("M:LibreMetaverse.UUID.SecureRandom") - } - /// C# member: `M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)`. - pub fn to_bytes(&self, dest: Vec, pos: i32) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)", - ) - } - /// C# member: `M:LibreMetaverse.UUID.ToString`. - pub fn to_string(&self) -> String { - libremetaverse_types::unimplemented_api!("M:LibreMetaverse.UUID.ToString") - } - /// C# member: `M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@)`. - pub fn try_parse(val: String, result: &mut libremetaverse_types::UUID) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@)" - ) - } - /// C# member: `M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn eq(left: libremetaverse_types::UUID, right: libremetaverse_types::UUID) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID)" - ) - } - /// C# member: `M:LibreMetaverse.UUID.op_ExclusiveOr(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn bitxor( - lhs: libremetaverse_types::UUID, - rhs: libremetaverse_types::UUID, - ) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.UUID.op_ExclusiveOr(LibreMetaverse.UUID,LibreMetaverse.UUID)" - ) - } - /// C# member: `M:LibreMetaverse.UUID.op_Explicit(System.String)~LibreMetaverse.UUID`. - pub fn try_from(val: String) -> libremetaverse_types::UUID { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.UUID.op_Explicit(System.String)~LibreMetaverse.UUID" - ) - } - /// C# member: `M:LibreMetaverse.UUID.op_Inequality(LibreMetaverse.UUID,LibreMetaverse.UUID)`. - pub fn ne(left: libremetaverse_types::UUID, right: libremetaverse_types::UUID) -> bool { - libremetaverse_types::unimplemented_api!( - "M:LibreMetaverse.UUID.op_Inequality(LibreMetaverse.UUID,LibreMetaverse.UUID)" - ) - } - /// C# member: `P:LibreMetaverse.UUID.Guid`. - pub fn guid(&self) -> libremetaverse_types::compat::Guid { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.UUID.Guid") - } - /// Setter for C# member: `P:LibreMetaverse.UUID.Guid`. - pub fn set_guid(&mut self, value: libremetaverse_types::compat::Guid) { - libremetaverse_types::unimplemented_api!("P:LibreMetaverse.UUID.Guid") - } -} +/// C# member: `F:LibreMetaverse.UUID.Zero`. +/// C# member: `M:LibreMetaverse.UUID.#ctor(System.Byte[],System.Int32)`. +/// C# member: `M:LibreMetaverse.UUID.#ctor(System.Guid)`. +/// C# member: `M:LibreMetaverse.UUID.#ctor(System.String)`. +/// C# member: `M:LibreMetaverse.UUID.#ctor(System.UInt64)`. +/// C# member: `M:LibreMetaverse.UUID.CRC`. +/// C# member: `M:LibreMetaverse.UUID.Combine(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.UUID.CompareTo(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.UUID.Deconstruct(System.Guid@)`. +/// C# member: `M:LibreMetaverse.UUID.Equals(LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.UUID.Equals(System.Object)`. +/// C# member: `M:LibreMetaverse.UUID.FromBytes(System.Byte[],System.Int32)`. +/// C# member: `M:LibreMetaverse.UUID.GetBytes`. +/// C# member: `M:LibreMetaverse.UUID.GetHashCode`. +/// C# member: `M:LibreMetaverse.UUID.GetULong`. +/// C# member: `M:LibreMetaverse.UUID.Parse(System.String)`. +/// C# member: `M:LibreMetaverse.UUID.Random`. +/// C# member: `M:LibreMetaverse.UUID.SecureRandom`. +/// C# member: `M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)`. +/// C# member: `M:LibreMetaverse.UUID.ToString`. +/// C# member: `M:LibreMetaverse.UUID.TryParse(System.String,LibreMetaverse.UUID@)`. +/// C# member: `M:LibreMetaverse.UUID.op_Equality(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.UUID.op_ExclusiveOr(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `M:LibreMetaverse.UUID.op_Explicit(System.String)~LibreMetaverse.UUID`. +/// C# member: `M:LibreMetaverse.UUID.op_Inequality(LibreMetaverse.UUID,LibreMetaverse.UUID)`. +/// C# member: `P:LibreMetaverse.UUID.Guid`. +pub use crate::uuid::UUID; /// C# type: `T:LibreMetaverse.Utils`. pub struct Utils; @@ -3252,16 +3111,12 @@ impl Utils { libremetaverse_types::not_implemented("M:LibreMetaverse.Utils.RandomDouble") } /// C# member: `M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)`. - pub fn read_double_little_endian(src: Vec, pos: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)", - ) + pub fn read_double_little_endian(src: &[u8], pos: i32) -> Result { + crate::byte_order::read_double_little_endian(src, pos) } /// C# member: `M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)`. - pub fn read_single_little_endian(src: Vec, pos: i32) -> Result { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)", - ) + pub fn read_single_little_endian(src: &[u8], pos: i32) -> Result { + crate::byte_order::read_single_little_endian(src, pos) } /// C# member: `M:LibreMetaverse.Utils.Round(System.Single)`. pub fn round(val: f32) -> Result { @@ -3510,23 +3365,19 @@ impl Utils { } /// C# member: `M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)`. pub fn write_double_little_endian( - dest: Vec, + dest: &mut [u8], pos: i32, value: f64, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)", - ) + crate::byte_order::write_double_little_endian(dest, pos, value) } /// C# member: `M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)`. pub fn write_single_little_endian( - dest: Vec, + dest: &mut [u8], pos: i32, value: f32, ) -> Result<(), crate::Error> { - libremetaverse_types::not_implemented( - "M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)", - ) + crate::byte_order::write_single_little_endian(dest, pos, value) } } diff --git a/crates/libremetaverse-types/src/lib.rs b/crates/libremetaverse-types/src/lib.rs index 6365119..c609a0e 100644 --- a/crates/libremetaverse-types/src/lib.rs +++ b/crates/libremetaverse-types/src/lib.rs @@ -2,9 +2,12 @@ extern crate self as libremetaverse_types; +mod byte_order; pub mod compat; +mod crc32; mod generated; pub mod shim; +mod uuid; pub use generated::*; pub use shim::{Error, NotImplemented, not_implemented}; diff --git a/crates/libremetaverse-types/src/uuid.rs b/crates/libremetaverse-types/src/uuid.rs new file mode 100644 index 0000000..f43d2d8 --- /dev/null +++ b/crates/libremetaverse-types/src/uuid.rs @@ -0,0 +1,448 @@ +//! Native, byte-order-explicit UUID compatibility type. + +#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the public compatibility map. +#![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters. + +use crate::Error; +use crate::compat::{Guid, Object}; +use md5::{Digest, Md5}; +use std::cmp::Ordering; +use std::fmt; + +/// A 128-bit UUID stored in RFC/network byte order. +/// +/// `System.Guid` uses a mixed-endian byte-array representation for its first +/// three fields. Conversions to and from [`Guid`] explicitly perform that +/// permutation; all protocol-facing byte APIs remain unambiguously big-endian. +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct UUID(uuid::Uuid); + +impl UUID { + #[must_use] + pub const fn zero() -> Self { + Self(uuid::Uuid::nil()) + } + + pub fn new_with_bytes_int32(source: Vec, pos: i32) -> Result { + Self::from_slice_at(&source, pos) + } + + pub fn new_with_guid(guid: Guid) -> Result { + Ok(Self(uuid::Uuid::from_bytes(guid_raw_to_network(guid.0)))) + } + + pub fn new_with_string(val: String) -> Result { + if val.is_empty() { + return Ok(Self::zero()); + } + parse_guid_text(&val).map(Self).ok_or(Error::Argument) + } + + pub fn new_with_u_int64(val: u64) -> Result { + let mut bytes = [0_u8; 16]; + bytes[8..].copy_from_slice(&val.to_le_bytes()); + Ok(Self(uuid::Uuid::from_bytes(bytes))) + } + + pub fn crc(&mut self) -> Result { + let bytes = self.0.as_bytes(); + Ok((0..4) + .map(|index| { + let offset = index * 4; + u32::from_be_bytes([ + bytes[offset], + bytes[offset + 1], + bytes[offset + 2], + bytes[offset + 3], + ]) + }) + .fold(0_u32, u32::wrapping_add)) + } + + pub fn combine(first: Self, second: Self) -> Result { + let mut digest = Md5::new(); + digest.update(first.0.as_bytes()); + digest.update(second.0.as_bytes()); + let bytes: [u8; 16] = digest.finalize().into(); + Ok(Self(uuid::Uuid::from_bytes(bytes))) + } + + pub fn compare_to(&self, id: Self) -> Result { + Ok(match self.cmp(&id) { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + }) + } + + pub fn deconstruct(&mut self, guid: &mut Guid) -> Result<(), Error> { + *guid = self.guid(); + Ok(()) + } + + #[must_use] + pub fn equals_with_uuid(&self, other: Self) -> bool { + *self == other + } + + #[must_use] + pub fn equals_with_object(&self, obj: Object) -> bool { + matches!(obj, Object::UUID(value) if *self == value) + } + + pub fn from_bytes(source: Vec, pos: i32) -> Result { + Self::from_slice_at(&source, pos) + } + + pub fn get_bytes(&self) -> Result, Error> { + Ok(self.0.as_bytes().to_vec()) + } + + /// Returns the hash produced by `System.Guid.GetHashCode` for the wrapped + /// Guid value. This is deliberately distinct from Rust's randomized + /// [`Hash`](std::hash::Hash) use in collections. + #[must_use] + pub fn get_hash_code(&self) -> i32 { + let raw = network_to_guid_raw(*self.0.as_bytes()); + let a = i32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]); + let b = i32::from(i16::from_le_bytes([raw[4], raw[5]])); + let c = u16::from_le_bytes([raw[6], raw[7]]); + a ^ ((b << 16) | i32::from(c)) ^ ((i32::from(raw[10])) << 24 | i32::from(raw[15])) + } + + pub fn get_u_long(&self) -> Result { + let bytes = self.0.as_bytes(); + Ok(u64::from_le_bytes([ + bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], + ])) + } + + pub fn parse(val: String) -> Result { + Self::new_with_string(val) + } + + pub fn random() -> Result { + Ok(Self(uuid::Uuid::new_v4())) + } + + pub fn secure_random() -> Result { + // Match the pinned implementation exactly: random bytes are first + // interpreted in Guid.ToByteArray layout, then its raw indices 6 and 8 + // receive the version/variant masks. + let mut raw = [0_u8; 16]; + getrandom::fill(&mut raw).map_err(|_| Error::InvalidOperation)?; + raw[6] = (raw[6] & 0x0f) | 0x40; + raw[8] = (raw[8] & 0x3f) | 0x80; + Ok(Self(uuid::Uuid::from_bytes(guid_raw_to_network(raw)))) + } + + pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), Error> { + let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?; + let end = start.checked_add(16).ok_or(Error::IndexOutOfRange)?; + let target = dest.get_mut(start..end).ok_or(Error::IndexOutOfRange)?; + target.copy_from_slice(self.0.as_bytes()); + Ok(()) + } + + #[must_use] + #[allow(clippy::inherent_to_string_shadow_display)] // Required mapped C# member. + pub fn to_string(&self) -> String { + self.0.hyphenated().to_string() + } + + pub fn try_parse(val: String, result: &mut Self) -> bool { + if val.is_empty() { + *result = Self::zero(); + return false; + } + if let Some(value) = parse_guid_text(&val) { + *result = Self(value); + true + } else { + *result = Self::zero(); + false + } + } + + #[must_use] + #[allow(clippy::should_implement_trait)] // Required mapped operator name. + pub fn eq(left: Self, right: Self) -> bool { + left == right + } + + #[must_use] + #[allow(clippy::should_implement_trait)] // Required mapped operator name. + pub fn bitxor(lhs: Self, rhs: Self) -> Self { + let mut bytes = [0_u8; 16]; + for ((output, left), right) in bytes.iter_mut().zip(lhs.0.as_bytes()).zip(rhs.0.as_bytes()) + { + *output = left ^ right; + } + Self(uuid::Uuid::from_bytes(bytes)) + } + + /// Preserves the mapped explicit conversion. Like the C# operator, invalid + /// input cannot be represented in its return type and therefore panics. + /// + /// # Panics + /// + /// Panics when `val` is not accepted by `System.Guid` parsing rules. + #[must_use] + pub fn try_from(val: String) -> Self { + Self::new_with_string(val).expect("invalid UUID string in explicit conversion") + } + + #[must_use] + pub fn ne(left: Self, right: Self) -> bool { + left != right + } + + #[must_use] + pub fn guid(&self) -> Guid { + Guid(network_to_guid_raw(*self.0.as_bytes())) + } + + pub fn set_guid(&mut self, value: Guid) { + self.0 = uuid::Uuid::from_bytes(guid_raw_to_network(value.0)); + } + + fn from_slice_at(source: &[u8], pos: i32) -> Result { + let start = usize::try_from(pos).map_err(|_| Error::IndexOutOfRange)?; + let end = start.checked_add(16).ok_or(Error::IndexOutOfRange)?; + let bytes: [u8; 16] = source + .get(start..end) + .ok_or(Error::IndexOutOfRange)? + .try_into() + .expect("UUID range has sixteen bytes"); + Ok(Self(uuid::Uuid::from_bytes(bytes))) + } +} + +impl fmt::Display for UUID { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.hyphenated().fmt(formatter) + } +} + +impl From for Object { + fn from(value: UUID) -> Self { + Self::UUID(value) + } +} + +fn guid_raw_to_network(raw: [u8; 16]) -> [u8; 16] { + [ + raw[3], raw[2], raw[1], raw[0], raw[5], raw[4], raw[7], raw[6], raw[8], raw[9], raw[10], + raw[11], raw[12], raw[13], raw[14], raw[15], + ] +} + +fn network_to_guid_raw(network: [u8; 16]) -> [u8; 16] { + [ + network[3], + network[2], + network[1], + network[0], + network[5], + network[4], + network[7], + network[6], + network[8], + network[9], + network[10], + network[11], + network[12], + network[13], + network[14], + network[15], + ] +} + +fn parse_guid_text(value: &str) -> Option { + let value = value.trim(); + if let Ok(parsed) = uuid::Uuid::parse_str(value) { + return Some(parsed); + } + if let Some(inner) = value + .strip_prefix('(') + .and_then(|inner| inner.strip_suffix(')')) + .or_else(|| { + value + .strip_prefix('{') + .and_then(|inner| inner.strip_suffix('}')) + }) + && let Ok(parsed) = uuid::Uuid::parse_str(inner) + { + return Some(parsed); + } + parse_guid_x_format(value) +} + +fn parse_guid_x_format(value: &str) -> Option { + let compact: String = value + .chars() + .filter(|character| !character.is_whitespace()) + .collect(); + let outer = compact.strip_prefix('{')?.strip_suffix('}')?; + let (leading, trailing) = outer.split_once(",{")?; + let trailing = trailing.strip_suffix('}')?; + let fields: Vec<&str> = leading.split(',').chain(trailing.split(',')).collect(); + if fields.len() != 11 { + return None; + } + let a = u32::from_str_radix(strip_hex_prefix(fields[0])?, 16).ok()?; + let b = u16::from_str_radix(strip_hex_prefix(fields[1])?, 16).ok()?; + let c = u16::from_str_radix(strip_hex_prefix(fields[2])?, 16).ok()?; + let mut bytes = [0_u8; 16]; + bytes[..4].copy_from_slice(&a.to_be_bytes()); + bytes[4..6].copy_from_slice(&b.to_be_bytes()); + bytes[6..8].copy_from_slice(&c.to_be_bytes()); + for (output, field) in bytes[8..].iter_mut().zip(&fields[3..]) { + *output = u8::from_str_radix(strip_hex_prefix(field)?, 16).ok()?; + } + Some(uuid::Uuid::from_bytes(bytes)) +} + +fn strip_hex_prefix(field: &str) -> Option<&str> { + field + .strip_prefix("0x") + .or_else(|| field.strip_prefix("0X")) +} + +#[cfg(test)] +mod tests { + use super::*; + + const NETWORK: [u8; 16] = [ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, + ]; + const TEXT: &str = "00010203-0405-0607-0809-0a0b0c0d0e0f"; + + #[test] + fn network_bytes_string_and_guid_raw_bytes_are_unambiguous() { + let id = UUID::new_with_bytes_int32(NETWORK.to_vec(), 0).unwrap(); + assert_eq!(id.to_string(), TEXT); + assert_eq!(id.get_bytes().unwrap(), NETWORK); + assert_eq!( + id.guid().0, + [ + 0x03, 0x02, 0x01, 0x00, 0x05, 0x04, 0x07, 0x06, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ] + ); + assert_eq!(UUID::new_with_guid(id.guid()).unwrap(), id); + } + + #[test] + fn parsing_offsets_and_malformed_inputs_match_contract() { + assert_eq!(UUID::new_with_string(String::new()).unwrap(), UUID::zero()); + assert_eq!( + UUID::parse(TEXT.replace('-', "")).unwrap().to_string(), + TEXT + ); + assert_eq!(UUID::parse(TEXT.to_uppercase()).unwrap().to_string(), TEXT); + assert_eq!( + UUID::parse(format!(" {TEXT}\t")).unwrap().to_string(), + TEXT + ); + assert_eq!(UUID::parse(format!("({TEXT})")).unwrap().to_string(), TEXT); + assert_eq!( + UUID::parse(format!("{{{TEXT}}}")).unwrap().to_string(), + TEXT + ); + assert_eq!( + UUID::parse( + "{0x00010203,0x0405,0x0607,{0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f}}".to_owned() + ) + .unwrap() + .to_string(), + TEXT + ); + assert_eq!(UUID::parse("bad".to_owned()), Err(Error::Argument)); + + let mut output = UUID::parse(TEXT.to_owned()).unwrap(); + assert!(!UUID::try_parse(String::new(), &mut output)); + assert_eq!(output, UUID::zero()); + assert!(!UUID::try_parse("bad".to_owned(), &mut output)); + assert_eq!(output, UUID::zero()); + + let mut with_offset = vec![0xff; 20]; + with_offset[2..18].copy_from_slice(&NETWORK); + assert_eq!(UUID::from_bytes(with_offset, 2).unwrap().to_string(), TEXT); + assert_eq!( + UUID::from_bytes(vec![0; 15], 0), + Err(Error::IndexOutOfRange) + ); + } + + #[test] + fn crc_ulong_xor_and_combine_match_golden_values() { + let mut id = UUID::parse(TEXT.to_owned()).unwrap(); + assert_eq!(id.crc().unwrap(), 0x181c_2024); + assert_eq!(id.get_u_long().unwrap(), 0x0f0e_0d0c_0b0a_0908); + assert_eq!(UUID::bitxor(id, id), UUID::zero()); + + let second = UUID::parse("ffffffff-ffff-ffff-ffff-ffffffffffff".to_owned()).unwrap(); + assert_eq!( + UUID::combine(id, second).unwrap().to_string(), + "1145ee5c-de6d-592f-027e-3daf0424653e" + ); + } + + #[test] + fn destination_write_and_u64_constructor_preserve_exact_bytes() { + let id = UUID::parse(TEXT.to_owned()).unwrap(); + let mut destination = [0xff; 20]; + id.to_bytes(&mut destination, 2).unwrap(); + assert_eq!(&destination[2..18], &NETWORK); + assert_eq!(destination[0], 0xff); + assert_eq!(destination[19], 0xff); + assert_eq!( + id.to_bytes(&mut destination, 5), + Err(Error::IndexOutOfRange) + ); + + let from_u64 = UUID::new_with_u_int64(0x0102_0304_0506_0708).unwrap(); + assert_eq!( + from_u64.get_bytes().unwrap(), + [0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1] + ); + } + + #[test] + fn generated_values_have_expected_uniqueness_and_bit_layout() { + let random = UUID::random().unwrap(); + assert_ne!(random, UUID::zero()); + assert_eq!(random.0.get_version_num(), 4); + assert_eq!(random.0.as_bytes()[8] & 0xc0, 0x80); + + let secure = UUID::secure_random().unwrap(); + let raw = secure.guid().0; + assert_eq!(raw[6] & 0xf0, 0x40); + assert_eq!(raw[8] & 0xc0, 0x80); + } + + #[test] + fn comparison_hash_guid_mutation_and_mapped_operators_are_compatible() { + let id = UUID::parse(TEXT.to_owned()).unwrap(); + let greater = UUID::parse("00010203-0405-0607-0809-0a0b0c0d0e10".to_owned()).unwrap(); + assert_eq!(id.compare_to(id).unwrap(), 0); + assert_eq!(id.compare_to(greater).unwrap(), -1); + assert_eq!(greater.compare_to(id).unwrap(), 1); + assert_eq!(id.get_hash_code(), 0x0e04_040b); + + assert!(UUID::eq(id, id)); + assert!(!UUID::ne(id, id)); + assert!(id.equals_with_object(Object::from(id))); + assert!(!id.equals_with_object(Object::from(TEXT))); + assert_eq!(UUID::try_from(TEXT.to_owned()), id); + + let mut deconstructed = Guid([0xff; 16]); + let mut mutable = id; + mutable.deconstruct(&mut deconstructed).unwrap(); + assert_eq!(deconstructed, id.guid()); + mutable.set_guid(greater.guid()); + assert_eq!(mutable, greater); + } +} diff --git a/tests/api-compile/Cargo.lock b/tests/api-compile/Cargo.lock index 5cdfc8f..ecfc3cf 100644 --- a/tests/api-compile/Cargo.lock +++ b/tests/api-compile/Cargo.lock @@ -2,6 +2,109 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + [[package]] name = "libremetaverse" version = "0.0.1" @@ -78,6 +181,11 @@ dependencies = [ [[package]] name = "libremetaverse-types" version = "0.0.1" +dependencies = [ + "getrandom", + "md-5", + "uuid", +] [[package]] name = "libremetaverse-utilities" @@ -106,6 +214,16 @@ dependencies = [ "libremetaverse-types", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "metacrate-api-compile" version = "0.0.0" @@ -124,3 +242,136 @@ dependencies = [ "libremetaverse-voice-vivox", "libremetaverse-voice-webrtc", ] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] diff --git a/tools/generate_api_shims.py b/tools/generate_api_shims.py index ffa8990..7d4ca13 100644 --- a/tools/generate_api_shims.py +++ b/tools/generate_api_shims.py @@ -34,6 +34,25 @@ TARGETS = { "LibreMetaverse.Voice.WebRTC": ROOT / "crates/libremetaverse-voice-webrtc/src/generated.rs", } +# Types whose failure-only generated shells have been replaced by native Rust +# implementations. The generated module keeps catalog markers and re-exports +# the hand-written type so coverage remains deterministic. +NATIVE_TYPES = { + "T:LibreMetaverse.CRC32": "crate::crc32::CRC32", + "T:LibreMetaverse.UUID": "crate::uuid::UUID", +} + +NATIVE_MEMBER_BODIES = { + "M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)": + "crate::byte_order::read_single_little_endian(src, pos)", + "M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)": + "crate::byte_order::write_single_little_endian(dest, pos, value)", + "M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)": + "crate::byte_order::read_double_little_endian(src, pos)", + "M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)": + "crate::byte_order::write_double_little_endian(dest, pos, value)", +} + MAIN_TARGET = ROOT / "crates/libremetaverse/src/generated.rs" WIRE_NAMESPACES = ( "LibreMetaverse.Assets", @@ -539,6 +558,8 @@ def render_enum(item: dict, rust_name: str) -> list[str]: def render_body(signature: str, member_id: str, error_model: str, asyncness: str, trait: bool) -> str: if trait: signature = signature.removeprefix("pub ") + if member_id in NATIVE_MEMBER_BODIES: + return f" {signature} {{ {NATIVE_MEMBER_BODIES[member_id]} }}" failure = ( f"libremetaverse_types::not_implemented({json.dumps(member_id)})" if error_model.startswith("Result") @@ -564,6 +585,11 @@ def rust_constant_value(item: dict) -> str: def render_type(item: dict, type_row: dict, member_rows: dict[str, dict[str, str]], mapper: mapping.Mapper) -> str: rust_name = type_name(type_row) + if native_path := NATIVE_TYPES.get(item["doc_id"]): + lines = [f"/// C# type: `{item['doc_id']}`."] + lines.extend(f"/// C# member: `{member['doc_id']}`." for member in item["members"]) + lines.append(f"pub use {native_path} as {rust_name};") + return "\n".join(lines) if item["kind"] == "enum": return "\n".join(render_enum(item, rust_name)) names = generic_names(item) @@ -803,7 +829,22 @@ def coverage_report(catalog: dict, coverage: dict[str, tuple[int, int, bool]]) - got_types, got_members, complete = coverage[name] if complete and (types, members) != (got_types, got_members): raise ValueError(f"incomplete generated slice for {name}") - status = "callable failure-only shim" if complete else f"partial callable shim ({got_types:,} types / {got_members:,} members)" + native_type_ids = { + item["doc_id"] for item in assembly["types"] if item["doc_id"] in NATIVE_TYPES + } + native_member_ids = { + member["doc_id"] + for item in assembly["types"] + for member in item["members"] + if item["doc_id"] in native_type_ids or member["doc_id"] in NATIVE_MEMBER_BODIES + } + if native_type_ids or native_member_ids: + status = ( + f"native implementation: {len(native_type_ids):,} types / " + f"{len(native_member_ids):,} members; remaining surface is callable failure-only shims" + ) + else: + status = "callable failure-only shim" if complete else f"partial callable shim ({got_types:,} types / {got_members:,} members)" total_types += got_types total_members += got_members else: diff --git a/tools/generate_rust_mapping.py b/tools/generate_rust_mapping.py index 29881b9..537a3cb 100644 --- a/tools/generate_rust_mapping.py +++ b/tools/generate_rust_mapping.py @@ -189,6 +189,26 @@ MEMBER_TYPE_OVERRIDES = { ), } MEMBER_SIGNATURE_OVERRIDES = { + "M:LibreMetaverse.Utils.ReadSingleLittleEndian(System.Byte[],System.Int32)": ( + "pub fn read_single_little_endian(src: &[u8], pos: i32) -> Result", + "shared_borrow,owned", + ), + "M:LibreMetaverse.Utils.WriteSingleLittleEndian(System.Byte[],System.Int32,System.Single)": ( + "pub fn write_single_little_endian(dest: &mut [u8], pos: i32, value: f32) -> Result<(), crate::Error>", + "mutable_borrow,owned,owned", + ), + "M:LibreMetaverse.Utils.ReadDoubleLittleEndian(System.Byte[],System.Int32)": ( + "pub fn read_double_little_endian(src: &[u8], pos: i32) -> Result", + "shared_borrow,owned", + ), + "M:LibreMetaverse.Utils.WriteDoubleLittleEndian(System.Byte[],System.Int32,System.Double)": ( + "pub fn write_double_little_endian(dest: &mut [u8], pos: i32, value: f64) -> Result<(), crate::Error>", + "mutable_borrow,owned,owned", + ), + "M:LibreMetaverse.UUID.ToBytes(System.Byte[],System.Int32)": ( + "pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), crate::Error>", + "shared_self,mutable_borrow,owned", + ), "M:LibreMetaverse.AppearanceManager.#ctor(LibreMetaverse.GridClient)": ( "pub fn new(client: Option>) " "-> Result", @@ -853,7 +873,15 @@ def validate_generated_shims() -> None: for body in function_bodies(text): if FAKE_BODY.search(body): raise ValueError(f"generated shim returns a plausible fallback: {path.relative_to(ROOT)}") - if not any(marker in body for marker in ("unimplemented_api!", "not_implemented(", "NotImplemented::new(")): + if not any( + marker in body + for marker in ( + "unimplemented_api!", + "not_implemented(", + "NotImplemented::new(", + "crate::byte_order::", + ) + ): raise ValueError(f"generated shim function does not use the standardized failure: {path.relative_to(ROOT)}") @@ -983,6 +1011,8 @@ def generic_names(item: dict, owner: dict) -> set[str]: def method_receiver(owner: dict, item: dict) -> tuple[str, str]: if item.get("static") or item["kind"] == "constructor": return "", "none" + if owner["doc_id"] == "T:LibreMetaverse.CRC32" and item["name"] == "Update": + return "&mut self", "mutable_self" if item["name"] == "Deserialize": return "&mut self", "mutable_self" if owner["doc_id"] == "T:LibreMetaverse.Assets.AssetMaterial" and item["name"].startswith(