1
0
Fork 0
mirror of https://github.com/azalea-rs/simdnbt.git synced 2025-08-02 07:26:04 +00:00

Use a tape for reading in simdnbt::borrow and probably introduce even more UB (#8)

* write structs and unions for tape

* war crimes

* except they're not war crimes if you're winning

* clippy and uncomment disabled examples

* cleanup and fix ub

* get(0) -> first()

* add fuzzer and fix ub lmao

* fix an out of bounds write

* fix macros and make it more useable
This commit is contained in:
mat 2024-07-04 23:28:59 -05:00 committed by GitHub
parent eb960e076e
commit 150a00cb29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 3263 additions and 1196 deletions

4
.gitignore vendored
View file

@ -1,5 +1,4 @@
/target
/Cargo.lock
.vscode
# generated by profiling tools
@ -12,3 +11,6 @@ cachegrind.out.*
# sometimes i make these files when benchmarking, don't want to accidentally commit them
benchmark_result.txt
valgrind.txt
# thanks rust
rustc-ice*.txt

988
Cargo.lock generated Normal file
View file

@ -0,0 +1,988 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anyhow"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "azalea-buf"
version = "0.8.0"
source = "git+https://github.com/azalea-rs/azalea?rev=84e036ce3752ecf57904b0f5aff1f33d43e95a32#84e036ce3752ecf57904b0f5aff1f33d43e95a32"
dependencies = [
"azalea-buf-macros",
"byteorder",
"thiserror",
"tracing",
"uuid",
]
[[package]]
name = "azalea-buf-macros"
version = "0.8.0"
source = "git+https://github.com/azalea-rs/azalea?rev=84e036ce3752ecf57904b0f5aff1f33d43e95a32#84e036ce3752ecf57904b0f5aff1f33d43e95a32"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "azalea-nbt"
version = "0.8.0"
source = "git+https://github.com/azalea-rs/azalea?rev=84e036ce3752ecf57904b0f5aff1f33d43e95a32#84e036ce3752ecf57904b0f5aff1f33d43e95a32"
dependencies = [
"azalea-buf",
"byteorder",
"compact_str",
"enum-as-inner",
"flate2",
"thiserror",
"tracing",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "castaway"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc"
dependencies = [
"rustversion",
]
[[package]]
name = "cc"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
dependencies = [
"jobserver",
"libc",
"once_cell",
]
[[package]]
name = "cesu8"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"serde",
"static_assertions",
]
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "either"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
[[package]]
name = "enum-as-inner"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "fastnbt"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d4a73a95dc65551ccd98e1ecd1adb5d1ba5361146963b31f481ca42fc0520a3"
dependencies = [
"byteorder",
"cesu8",
"serde",
"serde_bytes",
]
[[package]]
name = "flate2"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "graphite_binary"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dc8b44c673c50a2b3e6ec6652b8c8d26532254a3a182cc43b76d1b6e4cd1572"
dependencies = [
"anyhow",
"bytes",
"cesu8",
"graphite_binary_macros",
"thiserror",
]
[[package]]
name = "graphite_binary_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30667bf8d368a37fa37f4165d90ee84362e360d83d85924898c41cfe3d097521"
dependencies = [
"anyhow",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hematite-nbt"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670d0784ee67cfb57393dc1837867d2951f9a59ca7db99a653499c854f745739"
dependencies = [
"byteorder",
"cesu8",
"flate2",
]
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "is-terminal"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobserver"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
[[package]]
name = "libfuzzer-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "libmimalloc-sys"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "memchr"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
[[package]]
name = "mimalloc"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
[[package]]
name = "residua-cesu8"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ca29b145d9861719b5505602d881afc46705200144153ca9dbc0802be2938ea"
[[package]]
name = "residua-mutf8"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2adba843a48e520e7dad6d1e9c367a4f818787eaccf4530c6b90dd1f035e630d"
dependencies = [
"residua-cesu8",
]
[[package]]
name = "rustversion"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_bytes"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
dependencies = [
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "serde_json"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "shen-nbt5"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027a078cdb13a7d1179dc6a738d7935a4c30d9c2ccfdf11fff1e1367c301b0a1"
[[package]]
name = "simdnbt"
version = "0.5.2"
dependencies = [
"azalea-nbt",
"byteorder",
"criterion",
"fastnbt",
"flate2",
"graphite_binary",
"hematite-nbt",
"mimalloc",
"residua-mutf8",
"shen-nbt5",
"simdnbt-derive",
"thiserror",
"valence_nbt",
]
[[package]]
name = "simdnbt-derive"
version = "0.5.2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "simdnbt-fuzz"
version = "0.0.0"
dependencies = [
"libfuzzer-sys",
"simdnbt",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "uuid"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
[[package]]
name = "valence_nbt"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3cddc3222ed5ead4fa446881b3deeeee0dba60b0088b2bf12fedbac7eda2312"
dependencies = [
"byteorder",
"cesu8",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.63",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.63",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"

View file

@ -1,3 +1,3 @@
[workspace]
members = ["simdnbt", "simdnbt-derive"]
members = ["simdnbt", "simdnbt-derive", "fuzz"]
resolver = "2"

4
fuzz/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
target
corpus
artifacts
coverage

19
fuzz/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[package]
name = "simdnbt-fuzz"
version = "0.0.0"
publish = false
edition = "2021"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
simdnbt = { path = "../simdnbt" }
[[bin]]
name = "fuzz_target_1"
path = "fuzz_targets/fuzz_target_1.rs"
test = false
doc = false
bench = false

View file

@ -0,0 +1,11 @@
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(r) = simdnbt::borrow::read(&mut std::io::Cursor::new(data)) {
if let simdnbt::borrow::Nbt::Some(r) = r {
r.as_compound().to_owned();
}
}
});

View file

@ -64,7 +64,7 @@ pub fn deserialize_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt
let output = quote! {
impl #generics simdnbt::Deserialize for #ident #generics #where_clause {
fn from_compound(mut nbt: &simdnbt::borrow::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
fn from_compound(mut nbt: simdnbt::borrow::NbtCompound) -> Result<Self, simdnbt::DeserializeError> {
let value = Self {
#(#field_deserializers),*
};
@ -168,7 +168,7 @@ pub fn from_nbt_tag_derive(input: proc_macro::TokenStream) -> proc_macro::TokenS
let output = quote! {
impl #generics simdnbt::FromNbtTag for #ident #generics #where_clause {
fn from_nbt_tag(tag: &simdnbt::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: simdnbt::borrow::NbtTag) -> Option<Self> {
match tag.string()?.to_str().as_ref() {
#(#matchers)*
_ => None,

View file

@ -26,7 +26,7 @@ fn example(item_bytes: &[u8]) {
let skyblock_id: Cow<str> = nbt
.list("i")
.and_then(|i| i.compounds())
.and_then(|i| i.get(0))
.and_then(|i| i.first())
.and_then(|i| i.compound("tag"))
.and_then(|tag| tag.compound("ExtraAttributes"))
.and_then(|ea| ea.string("id"))

View file

@ -1,105 +1,56 @@
use std::{io::Cursor, mem::MaybeUninit};
use byteorder::ReadBytesExt;
use std::{hint::unreachable_unchecked, mem::MaybeUninit};
use crate::{
common::{
read_string, unchecked_extend, unchecked_push, unchecked_write_string, write_string,
END_ID, MAX_DEPTH,
read_int_array, read_long_array, read_string, read_with_u32_length, unchecked_extend,
unchecked_push, unchecked_write_string, write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID,
DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID,
MAX_DEPTH, SHORT_ID, STRING_ID,
},
reader::Reader,
Error, Mutf8Str,
};
use super::{list::NbtList, tag_alloc::TagAllocator, NbtTag};
use super::{
extra_tapes::ExtraTapes,
list::{self, NbtList},
tape::{TapeElement, TapeTagKind, TapeTagValue, UnalignedU16},
NbtTag, Tapes,
};
/// A list of named tags. The order of the tags is preserved.
#[derive(Debug, Default, PartialEq, Clone)]
pub struct NbtCompound<'a> {
values: &'a [(&'a Mutf8Str, NbtTag<'a>)],
#[derive(Debug, Clone, Copy)]
pub struct NbtCompound<'a: 'tape, 'tape> {
pub(crate) element: *const TapeElement, // includes the initial compound element
pub(crate) extra_tapes: &'tape ExtraTapes<'a>,
}
impl<'a> NbtCompound<'a> {
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
pub(crate) unsafe fn read(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
) -> Result<Self, Error> {
Self::read_with_depth(data, alloc, 0, 0)
}
impl<'a: 'tape, 'tape> NbtCompound<'a, 'tape> {
pub(crate) fn read(
// compounds have no header so nothing to read
_data: &mut Reader<'a>,
tapes: &'tape mut Tapes<'a>,
stack: &mut ParsingStack,
) -> Result<(), Error> {
let index_of_compound_element = tapes.main.len();
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
pub(crate) unsafe fn read_with_depth(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
compound_depth: usize,
list_depth: usize,
) -> Result<Self, Error> {
if compound_depth + list_depth > MAX_DEPTH {
return Err(Error::MaxDepthExceeded);
}
stack.push(ParsingStackElement::Compound {
index_of_compound_element: index_of_compound_element as u32,
})?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::Compound,
TapeTagValue {
// this gets overwritten later
compound: (0.into(), 0.into()),
},
),
});
let mut tags = alloc.get().named.start(compound_depth);
let mut tags_buffer = unsafe {
MaybeUninit::<[MaybeUninit<(&Mutf8Str, NbtTag<'a>)>; 4]>::uninit().assume_init()
};
let mut tags_buffer_len: usize = 0;
loop {
let tag_type = match data.read_u8() {
Ok(tag_type) => tag_type,
Err(_) => {
alloc.get().named.finish(tags, compound_depth);
return Err(Error::UnexpectedEof);
}
};
if tag_type == END_ID {
break;
}
let tag_name = match read_string(data) {
Ok(name) => name,
Err(_) => {
alloc.get().named.finish(tags, compound_depth);
// the only error read_string can return is UnexpectedEof, so this makes it
// slightly faster
return Err(Error::UnexpectedEof);
}
};
let tag =
match NbtTag::read_with_type(data, alloc, tag_type, compound_depth, list_depth) {
Ok(tag) => tag,
Err(e) => {
alloc.get().named.finish(tags, compound_depth);
return Err(e);
}
};
tags_buffer[tags_buffer_len] = MaybeUninit::new((tag_name, tag));
tags_buffer_len += 1;
if tags_buffer_len == tags_buffer.len() {
// writing the tags in groups like this is slightly faster
for i in 0..tags_buffer_len {
tags.push(unsafe { tags_buffer.get_unchecked(i).assume_init_read() });
}
tags_buffer_len = 0;
}
}
for i in 0..tags_buffer_len {
tags.push(unsafe { tags_buffer.get_unchecked(i).assume_init_read() });
}
let values = alloc.get().named.finish(tags, compound_depth);
Ok(Self { values })
Ok(())
}
pub fn write(&self, data: &mut Vec<u8>) {
for (name, tag) in self.values {
for (name, tag) in self.iter() {
// reserve 4 bytes extra so we can avoid reallocating for small tags
data.reserve(1 + 2 + name.len() + 4);
// SAFETY: We just reserved enough space for the tag ID, the name length, the name, and
@ -108,63 +59,18 @@ impl<'a> NbtCompound<'a> {
unchecked_push(data, tag.id());
unchecked_write_string(data, name);
}
match tag {
NbtTag::Byte(byte) => unsafe {
unchecked_push(data, *byte as u8);
},
NbtTag::Short(short) => unsafe {
unchecked_extend(data, &short.to_be_bytes());
},
NbtTag::Int(int) => unsafe {
unchecked_extend(data, &int.to_be_bytes());
},
NbtTag::Long(long) => {
data.extend_from_slice(&long.to_be_bytes());
}
NbtTag::Float(float) => unsafe {
unchecked_extend(data, &float.to_be_bytes());
},
NbtTag::Double(double) => {
data.extend_from_slice(&double.to_be_bytes());
}
NbtTag::ByteArray(byte_array) => {
unsafe {
unchecked_extend(data, &byte_array.len().to_be_bytes());
}
data.extend_from_slice(byte_array);
}
NbtTag::String(string) => {
write_string(data, string);
}
NbtTag::List(list) => {
list.write(data);
}
NbtTag::Compound(compound) => {
compound.write(data);
}
NbtTag::IntArray(int_array) => {
unsafe {
unchecked_extend(data, &int_array.len().to_be_bytes());
}
data.extend_from_slice(int_array.as_big_endian());
}
NbtTag::LongArray(long_array) => {
unsafe {
unchecked_extend(data, &long_array.len().to_be_bytes());
}
data.extend_from_slice(long_array.as_big_endian());
}
}
write_tag(tag, data);
}
data.push(END_ID);
}
#[inline]
pub fn get(&self, name: &str) -> Option<&NbtTag<'a>> {
pub fn get(&self, name: &str) -> Option<NbtTag<'a, 'tape>> {
let name = Mutf8Str::from_str(name);
let name = name.as_ref();
for (key, value) in self.values {
if key == &name {
for (key, value) in self.iter() {
if key == name {
return Some(value);
}
}
@ -175,8 +81,8 @@ impl<'a> NbtCompound<'a> {
pub fn contains(&self, name: &str) -> bool {
let name = Mutf8Str::from_str(name);
let name = name.as_ref();
for (key, _) in self.values {
if key == &name {
for key in self.keys() {
if key == name {
return true;
}
}
@ -201,16 +107,16 @@ impl<'a> NbtCompound<'a> {
pub fn double(&self, name: &str) -> Option<f64> {
self.get(name).and_then(|tag| tag.double())
}
pub fn byte_array(&self, name: &str) -> Option<&[u8]> {
pub fn byte_array(&self, name: &str) -> Option<&'a [u8]> {
self.get(name).and_then(|tag| tag.byte_array())
}
pub fn string(&self, name: &str) -> Option<&Mutf8Str> {
pub fn string(&self, name: &str) -> Option<&'a Mutf8Str> {
self.get(name).and_then(|tag| tag.string())
}
pub fn list(&self, name: &str) -> Option<&NbtList<'a>> {
pub fn list(&self, name: &str) -> Option<NbtList<'a, 'tape>> {
self.get(name).and_then(|tag| tag.list())
}
pub fn compound(&self, name: &str) -> Option<&NbtCompound<'a>> {
pub fn compound(&self, name: &str) -> Option<NbtCompound<'a, 'tape>> {
self.get(name).and_then(|tag| tag.compound())
}
pub fn int_array(&self, name: &str) -> Option<Vec<i32>> {
@ -220,26 +126,408 @@ impl<'a> NbtCompound<'a> {
self.get(name).and_then(|tag| tag.long_array())
}
pub fn iter(&self) -> impl Iterator<Item = (&Mutf8Str, &NbtTag<'a>)> {
self.values.iter().map(|(k, v)| (*k, v))
/// Get the tape element kind and value for this compound.
fn element(&self) -> (TapeTagKind, TapeTagValue) {
unsafe { (*self.element).kind }
}
pub fn iter(&self) -> CompoundIter<'a, 'tape> {
let (kind, value) = self.element();
debug_assert_eq!(kind, TapeTagKind::Compound);
let max_tape_offset = u32::from(unsafe { value.list_list.1 }) as usize;
let tape_slice =
unsafe { std::slice::from_raw_parts(self.element.add(1), max_tape_offset) };
CompoundIter {
current_tape_offset: 0,
max_tape_offset,
tape: tape_slice,
extra_tapes: self.extra_tapes,
}
}
/// Returns the number of tags directly in this compound.
///
/// Note that due to an optimization, this saturates at 2^24. This means if you have a
/// compound with more than 2^24 items, then this function will just return 2^24 instead of the
/// correct length. If you absolutely need the correct length, you can always just iterate over
/// the compound and get the length that way.
pub fn len(&self) -> usize {
self.values.len()
let (kind, value) = self.element();
debug_assert_eq!(kind, TapeTagKind::Compound);
unsafe { u32::from(value.list_list.0) as usize }
}
pub fn exact_len(self) -> usize {
let len = self.len();
if len < 2usize.pow(24) {
len
} else {
self.iter().count()
}
}
pub fn is_empty(&self) -> bool {
self.values.is_empty()
self.len() == 0
}
pub fn keys(&self) -> impl Iterator<Item = &Mutf8Str> {
self.values.iter().map(|(k, _)| *k)
#[allow(clippy::type_complexity)]
pub fn keys(
&self,
) -> std::iter::Map<
CompoundIter<'a, 'tape>,
fn((&'a Mutf8Str, NbtTag<'a, 'tape>)) -> &'a Mutf8Str,
> {
self.iter().map(|(k, _)| k)
}
pub fn to_owned(&self) -> crate::owned::NbtCompound {
crate::owned::NbtCompound {
values: self
.values
.iter()
.map(|(k, v)| ((*k).to_owned(), v.to_owned()))
.collect(),
}
}
}
impl PartialEq for NbtCompound<'_, '_> {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
pub struct CompoundIter<'a: 'tape, 'tape> {
current_tape_offset: usize,
max_tape_offset: usize,
tape: &'tape [TapeElement],
extra_tapes: &'tape ExtraTapes<'a>,
}
impl<'a: 'tape, 'tape> Iterator for CompoundIter<'a, 'tape> {
type Item = (&'a Mutf8Str, NbtTag<'a, 'tape>);
fn next(&mut self) -> Option<Self::Item> {
if self.current_tape_offset + 1 >= self.max_tape_offset {
return None;
}
let name_length_ptr = unsafe { self.tape[self.current_tape_offset].name };
let name_length_ptr = name_length_ptr as *const UnalignedU16;
let name_length = u16::from(unsafe { *name_length_ptr }).swap_bytes();
let name_pointer = unsafe { name_length_ptr.add(1) as *const u8 };
let name_slice = unsafe { std::slice::from_raw_parts(name_pointer, name_length as usize) };
let name = Mutf8Str::from_slice(name_slice);
self.current_tape_offset += 1;
let element = unsafe { self.tape.as_ptr().add(self.current_tape_offset) };
let tag = NbtTag {
element,
extra_tapes: self.extra_tapes,
};
self.current_tape_offset += unsafe { (*element).skip_offset() };
Some((name, tag))
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum ParsingStackElement {
Compound { index_of_compound_element: u32 },
ListOfLists { index_of_list_element: u32 },
ListOfCompounds { index_of_list_element: u32 },
}
pub struct ParsingStack {
stack: [MaybeUninit<ParsingStackElement>; MAX_DEPTH],
remaining_elements_in_lists: [u32; MAX_DEPTH],
depth: usize,
}
impl ParsingStack {
pub fn new() -> Self {
Self {
stack: unsafe { MaybeUninit::uninit().assume_init() },
remaining_elements_in_lists: [0; MAX_DEPTH],
depth: 0,
}
}
#[inline]
pub fn push(&mut self, state: ParsingStackElement) -> Result<(), Error> {
unsafe { self.stack.get_unchecked_mut(self.depth).write(state) };
self.depth += 1;
if self.depth >= MAX_DEPTH {
Err(Error::MaxDepthExceeded)
} else {
Ok(())
}
}
#[inline]
pub fn set_list_length(&mut self, length: u32) {
unsafe {
*self
.remaining_elements_in_lists
.get_unchecked_mut(self.depth - 1) = length;
};
}
#[inline]
pub fn decrement_list_length(&mut self) {
unsafe {
*self
.remaining_elements_in_lists
.get_unchecked_mut(self.depth - 1) -= 1;
};
}
#[inline]
pub fn remaining_elements_in_list(&self) -> u32 {
unsafe {
*self
.remaining_elements_in_lists
.get_unchecked(self.depth - 1)
}
}
#[inline]
pub fn pop(&mut self) -> ParsingStackElement {
self.depth -= 1;
unsafe { self.stack.get_unchecked(self.depth).assume_init() }
}
#[inline]
pub fn peek(&self) -> ParsingStackElement {
unsafe { self.stack.get_unchecked(self.depth - 1).assume_init() }
}
#[inline]
pub fn is_empty(&self) -> bool {
self.depth == 0
}
#[inline]
pub fn peek_mut(&mut self) -> &mut ParsingStackElement {
unsafe {
self.stack
.get_unchecked_mut(self.depth - 1)
.as_mut_ptr()
.as_mut()
.unwrap_unchecked()
}
}
}
#[inline(always)]
pub(crate) fn read_tag<'a>(
data: &mut Reader<'a>,
tapes: &mut Tapes<'a>,
stack: &mut ParsingStack,
tag_type: u8,
) -> Result<(), Error> {
match tag_type {
COMPOUND_ID => return NbtCompound::read(data, tapes, stack),
LIST_ID => return NbtList::read(data, tapes, stack),
_ => {}
}
match tag_type {
BYTE_ID => {
let byte = data.read_i8()?;
tapes.main.push(TapeElement {
kind: (TapeTagKind::Byte, TapeTagValue { byte }),
});
}
SHORT_ID => {
let short = data.read_i16()?;
tapes.main.push(TapeElement {
kind: (TapeTagKind::Short, TapeTagValue { short }),
});
}
INT_ID => {
let int = data.read_i32()?;
tapes.main.push(TapeElement {
kind: (TapeTagKind::Int, TapeTagValue { int }),
});
}
LONG_ID => {
let long = data.read_i64()?;
tapes.main.push(TapeElement {
kind: (TapeTagKind::Long, TapeTagValue { long: () }),
});
tapes.main.push(TapeElement { long });
}
FLOAT_ID => {
let float = data.read_f32()?;
tapes.main.push(TapeElement {
kind: (TapeTagKind::Float, TapeTagValue { float }),
});
}
DOUBLE_ID => {
let double = data.read_f64()?;
tapes.main.push(TapeElement {
kind: (TapeTagKind::Double, TapeTagValue { double: () }),
});
tapes.main.push(TapeElement { double });
}
BYTE_ARRAY_ID => {
let byte_array_pointer = data.cur as u64;
read_with_u32_length(data, 1)?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::ByteArray,
TapeTagValue {
byte_array: byte_array_pointer.into(),
},
),
});
}
STRING_ID => {
let string_pointer = data.cur as u64;
// assert that the top 8 bits of the pointer are 0 (because we rely on this)
debug_assert_eq!(string_pointer >> 56, 0);
read_string(data)?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::String,
TapeTagValue {
string: string_pointer.into(),
},
),
});
}
INT_ARRAY_ID => {
let int_array_pointer = data.cur as u64;
read_int_array(data)?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::IntArray,
TapeTagValue {
int_array: int_array_pointer.into(),
},
),
});
}
LONG_ARRAY_ID => {
let long_array_pointer = data.cur as u64;
read_long_array(data)?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::LongArray,
TapeTagValue {
long_array: long_array_pointer.into(),
},
),
});
}
_ => return Err(Error::UnknownTagId(tag_type)),
};
Ok(())
}
#[inline(always)]
pub(crate) fn read_tag_in_compound<'a>(
data: &mut Reader<'a>,
tapes: &mut Tapes<'a>,
stack: &mut ParsingStack,
) -> Result<(), Error> {
let tag_type = data.read_u8()?;
if tag_type == END_ID {
handle_compound_end(tapes, stack);
return Ok(());
}
let tag_name_pointer = data.cur as u64;
debug_assert_eq!(tag_name_pointer >> 56, 0);
read_string(data)?;
tapes.main.push(TapeElement {
name: tag_name_pointer,
});
read_tag(data, tapes, stack, tag_type)
}
#[inline(always)]
fn handle_compound_end(tapes: &mut Tapes, stack: &mut ParsingStack) {
let ParsingStackElement::Compound {
index_of_compound_element,
} = stack.pop()
else {
unsafe { unreachable_unchecked() };
};
let index_after_end_element = tapes.main.len();
unsafe {
tapes
.main
.get_unchecked_mut(index_of_compound_element as usize)
.kind
.1
.compound
.1 = (index_after_end_element as u32 - index_of_compound_element).into();
};
}
pub(crate) fn write_tag(tag: NbtTag, data: &mut Vec<u8>) {
let (kind, value) = tag.element();
match kind {
TapeTagKind::Byte => unsafe {
unchecked_push(data, tag.byte().unwrap() as u8);
},
TapeTagKind::Short => unsafe {
unchecked_extend(data, &tag.short().unwrap().to_be_bytes());
},
TapeTagKind::Int => unsafe {
unchecked_extend(data, &tag.int().unwrap().to_be_bytes());
},
TapeTagKind::Long => {
data.extend_from_slice(&tag.long().unwrap().to_be_bytes());
}
TapeTagKind::Float => unsafe {
unchecked_extend(data, &tag.float().unwrap().to_be_bytes());
},
TapeTagKind::Double => {
data.extend_from_slice(&tag.double().unwrap().to_be_bytes());
}
TapeTagKind::ByteArray => {
let byte_array = tag.byte_array().unwrap();
unsafe {
unchecked_extend(data, &byte_array.len().to_be_bytes());
}
data.extend_from_slice(byte_array);
}
TapeTagKind::String => {
let string = tag.string().unwrap();
write_string(data, string);
}
_ if kind.is_list() => {
tag.list().unwrap().write(data);
}
TapeTagKind::Compound => {
tag.compound().unwrap().write(data);
}
TapeTagKind::IntArray => {
let int_array =
unsafe { list::u32_prefixed_list_to_rawlist_unchecked::<i32>(value).unwrap() };
unsafe {
unchecked_extend(data, &int_array.len().to_be_bytes());
}
data.extend_from_slice(int_array.as_big_endian());
}
TapeTagKind::LongArray => {
let long_array =
unsafe { list::u32_prefixed_list_to_rawlist_unchecked::<i64>(value).unwrap() };
unsafe {
unchecked_extend(data, &long_array.len().to_be_bytes());
}
data.extend_from_slice(long_array.as_big_endian());
}
_ => unreachable!("Invalid tag kind {kind:?}"),
}
}

View file

@ -0,0 +1,24 @@
use std::fmt::{self, Debug};
use crate::{raw_list::RawList, Mutf8Str};
#[derive(Default)]
pub struct ExtraTapes<'a> {
pub elements: Vec<ExtraTapeElement<'a>>,
}
impl Debug for ExtraTapes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ExtraTapes")
}
}
pub union ExtraTapeElement<'a> {
/// An indicator for how long the following list is. This is what we point to from
/// `TapeTagValue`.
pub length: u32,
pub byte_array: &'a [u8],
pub string: &'a Mutf8Str,
pub int_array: RawList<'a, i32>,
pub long_array: RawList<'a, i64>,
}

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,68 @@
//! The borrowed variant of NBT. This is useful if you're only reading data and you can keep a reference to the original buffer.
mod compound;
mod extra_tapes;
mod list;
mod tag_alloc;
mod tape;
use std::{io::Cursor, ops::Deref};
use std::{
fmt::{self, Debug},
io::Cursor,
};
use byteorder::{ReadBytesExt, BE};
use byteorder::ReadBytesExt;
use tape::UnalignedU32;
use crate::{
common::{
read_int_array, read_long_array, read_string, read_u32, read_with_u32_length, write_string,
BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID,
LIST_ID, LONG_ARRAY_ID, LONG_ID, MAX_DEPTH, SHORT_ID, STRING_ID,
read_string, write_string, BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID,
FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID, LONG_ARRAY_ID, LONG_ID, SHORT_ID, STRING_ID,
},
raw_list::RawList,
reader::{Reader, ReaderFromCursor},
Error, Mutf8Str,
};
use self::tag_alloc::TagAllocator;
pub use self::{compound::NbtCompound, list::NbtList};
use self::{
compound::{read_tag_in_compound, ParsingStack, ParsingStackElement},
extra_tapes::ExtraTapes,
list::{read_compound_in_list, read_list_in_list},
tape::{MainTape, TapeElement, TapeTagKind, TapeTagValue, UnalignedU16},
};
/// Read a normal root NBT compound. This is either empty or has a name and compound tag.
///
/// Returns `Ok(Nbt::None)` if there is no data.
pub fn read<'a>(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
Nbt::read(data)
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(Nbt::None);
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
let mut data = ReaderFromCursor::new(data);
let name = read_string(&mut data)?;
let mut tapes = Tapes::new();
let mut stack = ParsingStack::new();
stack.push(ParsingStackElement::Compound {
index_of_compound_element: 0,
})?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::Compound,
TapeTagValue {
// this gets overwritten later
compound: (0.into(), 0.into()),
},
),
});
read_with_stack(&mut data, &mut tapes, &mut stack)?;
Ok(Nbt::Some(BaseNbt { name, tapes }))
}
/// Read a root NBT compound, but without reading the name. This is used in Minecraft when reading
/// NBT over the network.
@ -33,61 +70,221 @@ pub fn read<'a>(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
/// This is similar to [`read_tag`], but returns an [`Nbt`] instead (guaranteeing it'll be either
/// empty or a compound).
pub fn read_unnamed<'a>(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
Nbt::read_unnamed(data)
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(Nbt::None);
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
let name = Mutf8Str::from_slice(&[]);
let BaseNbtCompound { tapes } = read_compound(data)?;
Ok(Nbt::Some(BaseNbt { name, tapes }))
}
/// Read a compound tag. This may have any number of items.
pub fn read_compound<'a>(data: &mut Cursor<&'a [u8]>) -> Result<BaseNbtCompound<'a>, Error> {
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?;
Ok(BaseNbtCompound {
tag,
_tag_alloc: tag_alloc,
})
let mut tapes = Tapes::new();
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
stack.push(ParsingStackElement::Compound {
index_of_compound_element: 0,
})?;
tapes.main.push(TapeElement {
kind: (
TapeTagKind::Compound,
TapeTagValue {
// this gets overwritten later
compound: (0.into(), 0.into()),
},
),
});
read_with_stack(&mut data, &mut tapes, &mut stack)?;
Ok(BaseNbtCompound { tapes })
}
/// Read an NBT tag, without reading its name. This may be any type of tag except for an end tag. If you need to be able to
/// handle end tags, use [`read_optional_tag`].
pub fn read_tag<'a>(data: &mut Cursor<&'a [u8]>) -> Result<BaseNbtTag<'a>, Error> {
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtTag::read(data, &tag_alloc) }?;
Ok(BaseNbtTag {
tag,
_tag_alloc: tag_alloc,
})
let mut tapes = Tapes::new();
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Err(Error::InvalidRootType(0));
}
compound::read_tag(&mut data, &mut tapes, &mut stack, tag_type)?;
read_with_stack(&mut data, &mut tapes, &mut stack)?;
Ok(BaseNbtTag { tapes })
}
/// Read any NBT tag, without reading its name. This may be any type of tag, including an end tag.
///
/// Returns `Ok(None)` if there is no data.
pub fn read_optional_tag<'a>(data: &mut Cursor<&'a [u8]>) -> Result<Option<BaseNbtTag<'a>>, Error> {
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtTag::read_optional(data, &tag_alloc) }?;
Ok(tag.map(|tag| BaseNbtTag {
tag,
_tag_alloc: tag_alloc,
}))
let mut tapes = Tapes::new();
let mut stack = ParsingStack::new();
let mut data = ReaderFromCursor::new(data);
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Ok(None);
}
compound::read_tag(&mut data, &mut tapes, &mut stack, tag_type)?;
read_with_stack(&mut data, &mut tapes, &mut stack)?;
Ok(Some(BaseNbtTag { tapes }))
}
fn read_with_stack<'a>(
data: &mut Reader<'a>,
tapes: &mut Tapes<'a>,
stack: &mut ParsingStack,
) -> Result<(), Error> {
while !stack.is_empty() {
match stack.peek_mut() {
ParsingStackElement::Compound { .. } => read_tag_in_compound(data, tapes, stack)?,
ParsingStackElement::ListOfLists { .. } => read_list_in_list(data, tapes, stack)?,
ParsingStackElement::ListOfCompounds { .. } => {
read_compound_in_list(data, tapes, stack)?
}
}
}
Ok(())
}
#[derive(Default)]
pub(crate) struct Tapes<'a> {
main: MainTape,
extra: ExtraTapes<'a>,
}
impl<'a> Tapes<'a> {
fn new() -> Self {
Self::default()
}
}
impl Debug for Tapes<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tapes").finish()
}
}
/// A complete NBT container. This contains a name and a compound tag.
#[derive(Debug)]
pub struct BaseNbt<'a> {
name: &'a Mutf8Str,
tag: NbtCompound<'a>,
// we need to keep this around so it's not deallocated
_tag_alloc: TagAllocator<'a>,
tapes: Tapes<'a>,
}
impl<'a> BaseNbt<'a> {
#[inline]
pub fn as_compound<'tape>(&'a self) -> NbtCompound<'a, 'tape>
where
'a: 'tape,
{
NbtCompound {
element: self.tapes.main.as_ptr(),
extra_tapes: &self.tapes.extra,
}
}
/// Get the name of the NBT compound. This is often an empty string.
pub fn name(&self) -> &'a Mutf8Str {
self.name
}
pub fn get<'tape>(&'a self, key: &str) -> Option<NbtTag<'a, 'tape>> {
self.as_compound().get(key)
}
/// Returns whether there is a tag with the given name.
pub fn contains(&'a self, key: &str) -> bool {
self.as_compound().contains(key)
}
pub fn byte(&self, name: &str) -> Option<i8> {
self.as_compound().byte(name)
}
pub fn short(&self, name: &str) -> Option<i16> {
self.as_compound().short(name)
}
pub fn int(&self, name: &str) -> Option<i32> {
self.as_compound().int(name)
}
pub fn long(&self, name: &str) -> Option<i64> {
self.as_compound().long(name)
}
pub fn float(&self, name: &str) -> Option<f32> {
self.as_compound().float(name)
}
pub fn double(&self, name: &str) -> Option<f64> {
self.as_compound().double(name)
}
pub fn byte_array(&'a self, name: &str) -> Option<&'a [u8]> {
self.as_compound().byte_array(name)
}
pub fn string(&'a self, name: &str) -> Option<&'a Mutf8Str> {
self.as_compound().string(name)
}
pub fn list<'tape>(&'a self, name: &str) -> Option<NbtList<'a, 'tape>> {
self.as_compound().list(name)
}
pub fn compound<'tape>(&'a self, name: &str) -> Option<NbtCompound<'a, 'tape>> {
self.as_compound().compound(name)
}
pub fn int_array(&self, name: &str) -> Option<Vec<i32>> {
self.as_compound().int_array(name)
}
pub fn long_array(&self, name: &str) -> Option<Vec<i64>> {
self.as_compound().long_array(name)
}
}
impl<'a> Debug for BaseNbt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BaseNbt").finish()
}
}
/// A nameless NBT container. This only contains a compound tag. This contains a `TagAllocator`,
/// so it can exist independently from a [`BaseNbt`].
pub struct BaseNbtCompound<'a> {
tag: NbtCompound<'a>,
_tag_alloc: TagAllocator<'a>,
tapes: Tapes<'a>,
}
impl<'a, 'tape> From<&'a BaseNbtCompound<'a>> for NbtCompound<'a, 'tape> {
fn from(compound: &'a BaseNbtCompound<'a>) -> Self
where
'a: 'tape,
{
NbtCompound {
element: compound.tapes.main.as_ptr(),
extra_tapes: &compound.tapes.extra,
}
}
}
/// A nameless NBT tag.
pub struct BaseNbtTag<'a> {
tag: NbtTag<'a>,
_tag_alloc: TagAllocator<'a>,
tapes: Tapes<'a>,
}
impl<'a> BaseNbtTag<'a> {
pub fn as_tag<'tape>(&'a self) -> NbtTag<'a, 'tape>
where
'a: 'tape,
{
NbtTag {
element: self.tapes.main.as_ptr(),
extra_tapes: &self.tapes.extra,
}
}
}
impl<'a, 'tape> From<&'a BaseNbtTag<'a>> for NbtTag<'a, 'tape> {
fn from(tag: &'a BaseNbtTag<'a>) -> Self {
tag.as_tag()
}
}
/// Either a complete NBT container, or nothing.
#[derive(Debug, PartialEq, Default)]
pub enum Nbt<'a> {
@ -97,46 +294,6 @@ pub enum Nbt<'a> {
}
impl<'a> Nbt<'a> {
/// Reads NBT from the given data. Returns `Ok(Nbt::None)` if there is no data.
fn read(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(Nbt::None);
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
let tag_alloc = TagAllocator::new();
let name = read_string(data)?;
let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?;
Ok(Nbt::Some(BaseNbt {
name,
tag,
_tag_alloc: tag_alloc,
}))
}
fn read_unnamed(data: &mut Cursor<&'a [u8]>) -> Result<Nbt<'a>, Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(Nbt::None);
}
if root_type != COMPOUND_ID {
return Err(Error::InvalidRootType(root_type));
}
let tag_alloc = TagAllocator::new();
let tag = unsafe { NbtCompound::read(data, &tag_alloc) }?;
Ok(Nbt::Some(BaseNbt {
name: Mutf8Str::from_slice(&[]),
tag,
_tag_alloc: tag_alloc,
}))
}
pub fn write(&self, data: &mut Vec<u8>) {
match self {
Nbt::Some(nbt) => nbt.write(data),
@ -165,40 +322,11 @@ impl<'a> Nbt<'a> {
}
}
impl<'a> BaseNbt<'a> {
/// Get the name of the NBT compound. This is often an empty string.
pub fn name(&self) -> &'a Mutf8Str {
self.name
}
}
impl PartialEq for BaseNbt<'_> {
fn eq(&self, other: &Self) -> bool {
// we don't need to compare the tag allocator since comparing `tag` will
// we don't need to compare the tapes since comparing `tag` will
// still compare the values of the tags
self.name == other.name && self.tag == other.tag
}
}
impl<'a> Deref for BaseNbt<'a> {
type Target = NbtCompound<'a>;
fn deref(&self) -> &Self::Target {
&self.tag
}
}
impl<'a> Deref for BaseNbtCompound<'a> {
type Target = NbtCompound<'a>;
fn deref(&self) -> &Self::Target {
&self.tag
}
}
impl<'a> Deref for BaseNbtTag<'a> {
type Target = NbtTag<'a>;
fn deref(&self) -> &Self::Target {
&self.tag
self.name == other.name && self.as_compound() == other.as_compound()
}
}
@ -206,203 +334,184 @@ impl<'a> BaseNbt<'a> {
pub fn write(&self, data: &mut Vec<u8>) {
data.push(COMPOUND_ID);
write_string(data, self.name);
self.tag.write(data);
self.as_compound().write(data);
data.push(END_ID);
}
}
/// A single NBT tag.
#[derive(Debug, PartialEq, Clone)]
pub enum NbtTag<'a> {
Byte(i8),
Short(i16),
Int(i32),
Long(i64),
Float(f32),
Double(f64),
ByteArray(&'a [u8]),
String(&'a Mutf8Str),
List(NbtList<'a>),
Compound(NbtCompound<'a>),
IntArray(RawList<'a, i32>),
LongArray(RawList<'a, i64>),
#[derive(Debug)]
pub struct NbtTag<'a: 'tape, 'tape> {
element: *const TapeElement,
extra_tapes: &'tape ExtraTapes<'a>,
}
impl<'a, 'b> NbtTag<'a> {
impl<'a: 'tape, 'tape> NbtTag<'a, 'tape> {
/// Get the numerical ID of the tag type.
#[inline]
pub fn id(&self) -> u8 {
match self {
NbtTag::Byte(_) => BYTE_ID,
NbtTag::Short(_) => SHORT_ID,
NbtTag::Int(_) => INT_ID,
NbtTag::Long(_) => LONG_ID,
NbtTag::Float(_) => FLOAT_ID,
NbtTag::Double(_) => DOUBLE_ID,
NbtTag::ByteArray(_) => BYTE_ARRAY_ID,
NbtTag::String(_) => STRING_ID,
NbtTag::List(_) => LIST_ID,
NbtTag::Compound(_) => COMPOUND_ID,
NbtTag::IntArray(_) => INT_ARRAY_ID,
NbtTag::LongArray(_) => LONG_ARRAY_ID,
match self.element().0 {
TapeTagKind::Byte => BYTE_ID,
TapeTagKind::Short => SHORT_ID,
TapeTagKind::Int => INT_ID,
TapeTagKind::Long => LONG_ID,
TapeTagKind::Float => FLOAT_ID,
TapeTagKind::Double => DOUBLE_ID,
TapeTagKind::ByteArray => BYTE_ARRAY_ID,
TapeTagKind::String => STRING_ID,
TapeTagKind::Compound => COMPOUND_ID,
TapeTagKind::IntArray => INT_ARRAY_ID,
TapeTagKind::LongArray => LONG_ARRAY_ID,
t if t.is_list() => LIST_ID,
_ => unreachable!(),
}
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
#[inline(always)]
unsafe fn read_with_type(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
tag_type: u8,
compound_depth: usize,
list_depth: usize,
) -> Result<Self, Error> {
match tag_type {
BYTE_ID => Ok(NbtTag::Byte(
data.read_i8().map_err(|_| Error::UnexpectedEof)?,
)),
SHORT_ID => Ok(NbtTag::Short(
data.read_i16::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
INT_ID => Ok(NbtTag::Int(
data.read_i32::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
LONG_ID => Ok(NbtTag::Long(
data.read_i64::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
FLOAT_ID => Ok(NbtTag::Float(
data.read_f32::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
DOUBLE_ID => Ok(NbtTag::Double(
data.read_f64::<BE>().map_err(|_| Error::UnexpectedEof)?,
)),
BYTE_ARRAY_ID => Ok(NbtTag::ByteArray(read_with_u32_length(data, 1)?)),
STRING_ID => Ok(NbtTag::String(read_string(data)?)),
LIST_ID => Ok(NbtTag::List(NbtList::read(
data,
alloc,
compound_depth,
list_depth + 1,
)?)),
COMPOUND_ID => Ok(NbtTag::Compound(NbtCompound::read_with_depth(
data,
alloc,
compound_depth + 1,
list_depth,
)?)),
INT_ARRAY_ID => Ok(NbtTag::IntArray(read_int_array(data)?)),
LONG_ARRAY_ID => Ok(NbtTag::LongArray(read_long_array(data)?)),
_ => Err(Error::UnknownTagId(tag_type)),
}
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
unsafe fn read(data: &mut Cursor<&'a [u8]>, alloc: &TagAllocator<'a>) -> Result<Self, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
Self::read_with_type(data, alloc, tag_type, 0, 0)
}
/// # Safety
/// The given TagAllocator must be valid for the lifetime of all the tags in this NBT.
unsafe fn read_optional(
data: &mut Cursor<&'a [u8]>,
alloc: &TagAllocator<'a>,
) -> Result<Option<Self>, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Ok(None);
}
Ok(Some(Self::read_with_type(data, alloc, tag_type, 0, 0)?))
}
pub fn byte(&self) -> Option<i8> {
match self {
NbtTag::Byte(byte) => Some(*byte),
_ => None,
let (kind, value) = self.element();
if kind != TapeTagKind::Byte {
return None;
}
Some(unsafe { value.byte })
}
pub fn short(&self) -> Option<i16> {
match self {
NbtTag::Short(short) => Some(*short),
_ => None,
let (kind, value) = self.element();
if kind != TapeTagKind::Short {
return None;
}
Some(unsafe { value.short })
}
pub fn int(&self) -> Option<i32> {
match self {
NbtTag::Int(int) => Some(*int),
_ => None,
let (kind, value) = unsafe { (*self.element).kind };
if kind != TapeTagKind::Int {
return None;
}
Some(unsafe { value.int })
}
pub fn long(&self) -> Option<i64> {
match self {
NbtTag::Long(long) => Some(*long),
_ => None,
let (kind, _) = self.element();
if kind != TapeTagKind::Long {
return None;
}
// the value is in the next element because longs are too big to fit in a single element
let value = unsafe { self.element.add(1) };
Some(unsafe { (*value).long })
}
pub fn float(&self) -> Option<f32> {
match self {
NbtTag::Float(float) => Some(*float),
_ => None,
let (kind, value) = self.element();
if kind != TapeTagKind::Float {
return None;
}
Some(unsafe { value.float })
}
pub fn double(&self) -> Option<f64> {
match self {
NbtTag::Double(double) => Some(*double),
_ => None,
let (kind, _) = self.element();
if kind != TapeTagKind::Double {
return None;
}
// the value is in the next element because doubles are too big to fit in a single element
let value = unsafe { self.element.add(1) };
Some(unsafe { (*value).double })
}
pub fn byte_array(&self) -> Option<&[u8]> {
match self {
NbtTag::ByteArray(byte_array) => Some(byte_array),
_ => None,
pub fn byte_array(&self) -> Option<&'a [u8]> {
let (kind, value) = self.element();
if kind != TapeTagKind::ByteArray {
return None;
}
let length_ptr = unsafe { u64::from(value.byte_array) as *const UnalignedU32 };
let length = unsafe { u32::from(*length_ptr).swap_bytes() as usize };
let data_ptr = unsafe { length_ptr.add(1) as *const u8 };
Some(unsafe { std::slice::from_raw_parts(data_ptr, length) })
}
pub fn string(&self) -> Option<&Mutf8Str> {
match self {
NbtTag::String(string) => Some(string),
_ => None,
pub fn string(&self) -> Option<&'a Mutf8Str> {
let (kind, value) = self.element();
if kind != TapeTagKind::String {
return None;
}
let length_ptr = unsafe { u64::from(value.string) as usize as *const UnalignedU16 };
let length = unsafe { u16::from(*length_ptr).swap_bytes() as usize };
let data_ptr = unsafe { length_ptr.add(1) as *const u8 };
Some(unsafe { Mutf8Str::from_slice(std::slice::from_raw_parts(data_ptr, length)) })
}
pub fn list(&self) -> Option<&NbtList<'a>> {
match self {
NbtTag::List(list) => Some(list),
_ => None,
pub fn list(&self) -> Option<NbtList<'a, 'tape>> {
let (kind, _) = self.element();
if !kind.is_list() {
return None;
}
Some(NbtList {
element: self.element,
extra_tapes: self.extra_tapes,
})
}
pub fn compound(&self) -> Option<&NbtCompound<'a>> {
match self {
NbtTag::Compound(compound) => Some(compound),
_ => None,
pub fn compound(&self) -> Option<NbtCompound<'a, 'tape>> {
let (kind, _) = self.element();
if kind != TapeTagKind::Compound {
return None;
}
Some(NbtCompound {
element: self.element,
extra_tapes: self.extra_tapes,
})
}
pub fn int_array(&self) -> Option<Vec<i32>> {
match self {
NbtTag::IntArray(int_array) => Some(int_array.to_vec()),
_ => None,
}
list::u32_prefixed_list_to_vec(TapeTagKind::IntArray, self.element)
}
pub fn long_array(&self) -> Option<Vec<i64>> {
match self {
NbtTag::LongArray(long_array) => Some(long_array.to_vec()),
_ => None,
}
list::u32_prefixed_list_to_vec(TapeTagKind::LongArray, self.element)
}
/// Get the tape element kind and value for this tag.
fn element(&self) -> (TapeTagKind, TapeTagValue) {
unsafe { (*self.element).kind }
}
pub fn to_owned(&self) -> crate::owned::NbtTag {
match self {
NbtTag::Byte(byte) => crate::owned::NbtTag::Byte(*byte),
NbtTag::Short(short) => crate::owned::NbtTag::Short(*short),
NbtTag::Int(int) => crate::owned::NbtTag::Int(*int),
NbtTag::Long(long) => crate::owned::NbtTag::Long(*long),
NbtTag::Float(float) => crate::owned::NbtTag::Float(*float),
NbtTag::Double(double) => crate::owned::NbtTag::Double(*double),
NbtTag::ByteArray(byte_array) => crate::owned::NbtTag::ByteArray(byte_array.to_vec()),
NbtTag::String(string) => crate::owned::NbtTag::String((*string).to_owned()),
NbtTag::List(list) => crate::owned::NbtTag::List(list.to_owned()),
NbtTag::Compound(compound) => crate::owned::NbtTag::Compound(compound.to_owned()),
NbtTag::IntArray(int_array) => crate::owned::NbtTag::IntArray(int_array.to_vec()),
NbtTag::LongArray(long_array) => crate::owned::NbtTag::LongArray(long_array.to_vec()),
let (kind, _value) = self.element();
match kind {
TapeTagKind::Byte => crate::owned::NbtTag::Byte(self.byte().unwrap()),
TapeTagKind::Short => crate::owned::NbtTag::Short(self.short().unwrap()),
TapeTagKind::Int => crate::owned::NbtTag::Int(self.int().unwrap()),
TapeTagKind::Long => crate::owned::NbtTag::Long(self.long().unwrap()),
TapeTagKind::Float => crate::owned::NbtTag::Float(self.float().unwrap()),
TapeTagKind::Double => crate::owned::NbtTag::Double(self.double().unwrap()),
TapeTagKind::ByteArray => {
crate::owned::NbtTag::ByteArray(self.byte_array().unwrap().to_vec())
}
TapeTagKind::String => crate::owned::NbtTag::String(self.string().unwrap().to_owned()),
TapeTagKind::Compound => {
crate::owned::NbtTag::Compound(self.compound().unwrap().to_owned())
}
_ if kind.is_list() => crate::owned::NbtTag::List(self.list().unwrap().to_owned()),
TapeTagKind::IntArray => crate::owned::NbtTag::IntArray(self.int_array().unwrap()),
TapeTagKind::LongArray => crate::owned::NbtTag::LongArray(self.long_array().unwrap()),
_ => unreachable!(),
}
}
}
impl PartialEq for NbtTag<'_, '_> {
fn eq(&self, other: &Self) -> bool {
let (self_kind, _) = self.element();
let (other_kind, _) = other.element();
if self_kind != other_kind {
return false;
}
match self_kind {
TapeTagKind::Byte => self.byte().unwrap() == other.byte().unwrap(),
TapeTagKind::Short => self.short().unwrap() == other.short().unwrap(),
TapeTagKind::Int => self.int().unwrap() == other.int().unwrap(),
TapeTagKind::Long => self.long().unwrap() == other.long().unwrap(),
TapeTagKind::Float => self.float().unwrap() == other.float().unwrap(),
TapeTagKind::Double => self.double().unwrap() == other.double().unwrap(),
TapeTagKind::ByteArray => self.byte_array().unwrap() == other.byte_array().unwrap(),
TapeTagKind::String => self.string().unwrap() == other.string().unwrap(),
TapeTagKind::Compound => self.compound().unwrap() == other.compound().unwrap(),
TapeTagKind::IntArray => self.int_array().unwrap() == other.int_array().unwrap(),
TapeTagKind::LongArray => self.long_array().unwrap() == other.long_array().unwrap(),
t if t.is_list() => self.list().unwrap() == other.list().unwrap(),
_ => unreachable!(),
}
}
}
@ -411,14 +520,14 @@ impl<'a, 'b> NbtTag<'a> {
mod tests {
use std::io::Read;
use byteorder::WriteBytesExt;
use byteorder::{WriteBytesExt, BE};
use flate2::read::GzDecoder;
use super::*;
#[test]
fn hello_world() {
let nbt = Nbt::read(&mut Cursor::new(include_bytes!(
let nbt = super::read(&mut Cursor::new(include_bytes!(
"../../tests/hello_world.nbt"
)))
.unwrap()
@ -438,7 +547,9 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&decoded_src))
.unwrap()
.unwrap();
assert_eq!(nbt.int("PersistentId"), Some(1946940766));
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
@ -451,7 +562,9 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&decoded_src))
.unwrap()
.unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
@ -460,7 +573,7 @@ mod tests {
#[test]
fn read_hypixel() {
let src = include_bytes!("../../tests/hypixel.nbt").to_vec();
let _nbt = Nbt::read(&mut Cursor::new(&src[..])).unwrap().unwrap();
let _nbt = super::read(&mut Cursor::new(&src[..])).unwrap().unwrap();
}
#[test]
@ -470,11 +583,14 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&decoded_src))
.unwrap()
.unwrap();
let mut out = Vec::new();
nbt.write(&mut out);
let nbt = Nbt::read(&mut Cursor::new(&out)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&out)).unwrap().unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
@ -482,7 +598,7 @@ mod tests {
#[test]
fn inttest_1023() {
let nbt = Nbt::read(&mut Cursor::new(include_bytes!(
let nbt = super::read(&mut Cursor::new(include_bytes!(
"../../tests/inttest1023.nbt"
)))
.unwrap()
@ -510,7 +626,7 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
@ -532,7 +648,7 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
@ -554,7 +670,7 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().longs().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i64, item);
@ -571,12 +687,12 @@ mod tests {
data.write_u16::<BE>(0).unwrap(); // first element name length
// eof
let res = Nbt::read(&mut Cursor::new(&data));
let res = super::read(&mut Cursor::new(&data));
assert_eq!(res, Err(Error::UnexpectedEof));
}
#[test]
fn read_complexplayer_with_given_alloc() {
fn read_complex_player_as_tag() {
let src = include_bytes!("../../tests/complex_player.dat").to_vec();
let mut src_slice = src.as_slice();
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
@ -589,9 +705,32 @@ mod tests {
decoded_src_as_tag.push(END_ID);
let nbt = super::read_tag(&mut Cursor::new(&decoded_src_as_tag)).unwrap();
let nbt = nbt.compound().unwrap().compound("").unwrap();
let nbt = nbt.as_tag().compound().unwrap().compound("").unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
}
#[test]
fn get_byte_array() {
// found from fuzzing
let data = [10, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0];
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
nbt.as_compound().to_owned();
}
#[test]
fn list_of_empty_lists() {
// found from fuzzing
// BaseNbt { name: m"", tag: NbtTag::NbtCompound { m"": NbtTag::List(List::List([List::Empty])) } }
let data = [10, 0, 0, 9, 0, 0, 9, 0, 0, 0, 1, 0, 9, 0, 0, 0, 0];
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
nbt.as_compound().to_owned();
}
#[test]
fn list_of_byte_arrays() {
// BaseNbt { name: m"", tag: NbtCompound { values: [(m"", List(List([List::ByteArray([])])))] } }
let data = [10, 0, 0, 9, 0, 0, 9, 0, 0, 0, 1, 7, 0, 0, 0, 0, 0];
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
nbt.as_compound().to_owned();
}
}

View file

@ -1,325 +0,0 @@
//! Some tags, like compounds and arrays, contain other tags. The naive approach would be to just
//! use `Vec`s or `HashMap`s, but this is inefficient and leads to many small allocations.
//!
//! Instead, the idea for this is essentially that we'd have two big Vec for every tag (one for
//! named tags and one for unnamed tags), and then compounds/arrays simply contain a slice of this
//! vec.
//!
//! This almost works. but there's two main issues:
//! - compounds aren't length-prefixed, so we can't pre-allocate at the beginning of compounds for
//! the rest of that compound
//! - resizing a vec might move it in memory, invalidating all of our slices to it
//!
//! solving the first problem isn't that hard, since we can have a separate vec for every "depth"
//! (so compounds in compounds don't share the same vec).
//! to solve the second problem, i chose to implement a special data structure
//! that relies on low-level allocations so we can guarantee that our allocations don't move in memory.
use std::{
alloc::{self, Layout},
cell::UnsafeCell,
fmt,
mem::MaybeUninit,
ptr::NonNull,
};
use crate::{raw_list::RawList, Mutf8Str};
use super::{NbtCompound, NbtList, NbtTag};
// this value appears to have the best results on my pc when testing with complex_player.dat
const MIN_ALLOC_SIZE: usize = 1024;
/// The data structure that contains all the parsed NBT tags. This must stay in scope for as long
/// as the borrowed NBT exists.
#[derive(Default)]
pub struct TagAllocator<'a>(UnsafeCell<TagAllocatorImpl<'a>>);
impl<'a> TagAllocator<'a> {
pub fn new() -> Self {
Self(UnsafeCell::new(TagAllocatorImpl::new()))
}
// shhhhh
#[allow(clippy::mut_from_ref)]
pub fn get(&self) -> &mut TagAllocatorImpl<'a> {
unsafe { self.0.get().as_mut().unwrap_unchecked() }
}
}
#[derive(Default)]
pub struct TagAllocatorImpl<'a> {
pub named: IndividualTagAllocatorWithDepth<(&'a Mutf8Str, NbtTag<'a>)>,
// lists of lists
pub unnamed_list: IndividualTagAllocatorWithDepth<NbtList<'a>>,
// lists of compounds
pub unnamed_compound: IndividualTagAllocatorWithDepth<NbtCompound<'a>>,
pub unnamed_bytearray: IndividualTagAllocator<&'a [u8]>,
pub unnamed_string: IndividualTagAllocator<&'a Mutf8Str>,
pub unnamed_intarray: IndividualTagAllocator<RawList<'a, i32>>,
pub unnamed_longarray: IndividualTagAllocator<RawList<'a, i64>>,
}
impl<'a> TagAllocatorImpl<'a> {
pub fn new() -> Self {
Self::default()
}
}
pub struct IndividualTagAllocator<T> {
current: TagsAllocation<T>,
// we keep track of old allocations so we can deallocate them later
previous: Vec<TagsAllocation<T>>,
}
impl<T> IndividualTagAllocator<T>
where
T: Clone,
{
#[inline]
pub fn start(&mut self) -> ContiguousTagsAllocator<T> {
let alloc = self.current.clone();
start_allocating_tags(alloc)
}
#[inline]
pub fn finish<'a>(&mut self, alloc: ContiguousTagsAllocator<T>) -> &'a [T] {
finish_allocating_tags(alloc, &mut self.current, &mut self.previous)
}
}
impl<T> Default for IndividualTagAllocator<T> {
fn default() -> Self {
Self {
current: Default::default(),
previous: Default::default(),
}
}
}
impl<T> Drop for IndividualTagAllocator<T> {
fn drop(&mut self) {
self.current.deallocate();
self.previous
.iter_mut()
.for_each(TagsAllocation::deallocate);
}
}
pub struct IndividualTagAllocatorWithDepth<T> {
// it's a vec because of the depth thing mentioned earlier, index in the vec = depth
current: Vec<TagsAllocation<T>>,
// we also have to keep track of old allocations so we can deallocate them later
previous: Vec<Vec<TagsAllocation<T>>>,
}
impl<T> IndividualTagAllocatorWithDepth<T>
where
T: Clone,
{
#[inline]
pub fn start(&mut self, depth: usize) -> ContiguousTagsAllocator<T> {
// make sure we have enough space for this depth
// (also note that depth is reused for compounds and arrays so we might have to push
// more than once)
for _ in self.current.len()..=depth {
self.current.push(Default::default());
self.previous.push(Default::default());
}
let alloc = self.current[depth].clone();
start_allocating_tags(alloc)
}
#[inline]
pub fn finish<'a>(&mut self, alloc: ContiguousTagsAllocator<T>, depth: usize) -> &'a [T] {
finish_allocating_tags(alloc, &mut self.current[depth], &mut self.previous[depth])
}
}
impl<T> Default for IndividualTagAllocatorWithDepth<T> {
fn default() -> Self {
Self {
current: Default::default(),
previous: Default::default(),
}
}
}
impl<T> Drop for IndividualTagAllocatorWithDepth<T> {
fn drop(&mut self) {
self.current.iter_mut().for_each(TagsAllocation::deallocate);
self.previous
.iter_mut()
.flatten()
.for_each(TagsAllocation::deallocate);
}
}
#[inline]
fn start_allocating_tags<T>(alloc: TagsAllocation<T>) -> ContiguousTagsAllocator<T> {
let is_new_allocation = alloc.cap == 0;
ContiguousTagsAllocator {
alloc,
is_new_allocation,
size: 0,
}
}
#[inline]
fn finish_allocating_tags<'a, T>(
alloc: ContiguousTagsAllocator<T>,
current_alloc: &mut TagsAllocation<T>,
previous_allocs: &mut Vec<TagsAllocation<T>>,
) -> &'a [T] {
let slice = unsafe {
std::slice::from_raw_parts(
alloc
.alloc
.ptr
.as_ptr()
.add(alloc.alloc.len)
.sub(alloc.size),
alloc.size,
)
};
let previous_allocation_at_that_depth = std::mem::replace(current_alloc, alloc.alloc);
if alloc.is_new_allocation {
previous_allocs.push(previous_allocation_at_that_depth);
}
slice
}
#[derive(Clone)]
pub struct TagsAllocation<T> {
ptr: NonNull<T>,
cap: usize,
len: usize,
}
impl<T> Default for TagsAllocation<T> {
fn default() -> Self {
Self {
ptr: NonNull::dangling(),
cap: 0,
len: 0,
}
}
}
impl<T> TagsAllocation<T> {
fn deallocate(&mut self) {
if self.cap == 0 {
return;
}
// call drop on the tags too
unsafe {
std::ptr::drop_in_place(std::slice::from_raw_parts_mut(
self.ptr.as_ptr().cast::<T>(),
self.len,
));
}
unsafe {
alloc::dealloc(
self.ptr.as_ptr().cast(),
Layout::array::<T>(self.cap).unwrap(),
)
}
}
}
// this is created when we start allocating a compound tag
pub struct ContiguousTagsAllocator<T> {
alloc: TagsAllocation<T>,
/// whether we created a new allocation for this compound (as opposed to reusing an existing
/// one).
/// this is used to determine whether we're allowed to deallocate it when growing, and whether
/// we should add this allocation to `all_allocations`
is_new_allocation: bool,
/// the size of this individual compound allocation. the size of the full allocation is in
/// `alloc.len`.
size: usize,
}
impl<T> ContiguousTagsAllocator<T> {
/// Grow the capacity to the new amount.
///
/// # Safety
/// Must be at least the current capacity.
unsafe fn grow_to(&mut self, new_cap: usize) {
debug_assert!(new_cap >= self.alloc.cap, "{new_cap} < {}", self.alloc.cap);
let new_layout = Layout::array::<T>(new_cap).unwrap();
let new_ptr = if self.is_new_allocation && self.alloc.ptr != NonNull::dangling() {
let old_ptr = self.alloc.ptr.as_ptr();
let old_cap = self.alloc.cap;
let old_layout = Layout::array::<T>(old_cap).unwrap();
unsafe { alloc::realloc(old_ptr as *mut u8, old_layout, new_cap) }
} else {
self.is_new_allocation = true;
unsafe { alloc::alloc(new_layout) }
} as *mut T;
// copy the last `size` elements from the old allocation to the new one
unsafe {
std::ptr::copy_nonoverlapping(
self.alloc.ptr.as_ptr().sub(self.size),
new_ptr,
self.size,
)
};
self.alloc.ptr = NonNull::new(new_ptr).unwrap();
self.alloc.cap = new_cap;
self.alloc.len = self.size;
}
#[inline(never)]
fn grow(&mut self) {
let new_cap = if self.is_new_allocation {
// this makes sure we don't allocate 0 bytes
std::cmp::max(self.alloc.cap * 2, MIN_ALLOC_SIZE)
} else {
// reuse the previous cap, since it's not unlikely that we'll have another compound
// with a similar
self.alloc.cap
};
unsafe { self.grow_to(new_cap) };
}
#[allow(dead_code)]
pub fn allocate(&mut self, size: usize) -> &mut [MaybeUninit<T>] {
// check if we need to reallocate
if self.alloc.len + size > self.alloc.cap {
// unsafe { self.grow_to(self.size + size) }
let new_cap = std::cmp::max(self.alloc.cap, self.size + size);
unsafe { self.grow_to(new_cap) }
}
let start = unsafe { self.alloc.ptr.as_ptr().add(self.alloc.len) };
self.alloc.len += size;
self.size += size;
unsafe { std::slice::from_raw_parts_mut(start.cast(), size) }
}
#[inline]
pub fn push(&mut self, value: T) {
// check if we need to reallocate
if self.alloc.len == self.alloc.cap {
self.grow();
}
// push the new tag
unsafe {
let end = self.alloc.ptr.as_ptr().add(self.alloc.len);
std::ptr::write(end, value);
}
self.alloc.len += 1;
self.size += 1;
}
}
impl<'a> fmt::Debug for TagAllocator<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TagAllocator").finish()
}
}

270
simdnbt/src/borrow/tape.rs Normal file
View file

@ -0,0 +1,270 @@
use std::fmt::{self, Debug};
use crate::common::{
BYTE_ARRAY_ID, BYTE_ID, COMPOUND_ID, DOUBLE_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LONG_ARRAY_ID,
LONG_ID, SHORT_ID, STRING_ID,
};
#[derive(Debug)]
pub struct MainTape {
elements: Vec<TapeElement>,
}
impl MainTape {
#[inline]
pub fn push(&mut self, element: TapeElement) {
self.elements.push(element);
}
#[inline]
pub fn len(&self) -> usize {
self.elements.len()
}
#[inline]
pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> &mut TapeElement {
self.elements.get_unchecked_mut(index)
}
#[inline]
pub fn as_ptr(&self) -> *const TapeElement {
self.elements.as_ptr()
}
}
impl Default for MainTape {
fn default() -> Self {
Self {
elements: Vec::with_capacity(1024),
}
}
}
#[derive(Clone, Copy)]
#[repr(C)]
pub union TapeElement {
pub kind: (TapeTagKind, TapeTagValue),
pub long: i64,
pub double: f64,
pub name: u64, // pointer to the original data
}
impl TapeElement {
/// Returns how much we should increment the tape index to get to the next tag.
///
/// # Safety
/// The element must be a tag and not something else like a continuation of a long or double.
pub unsafe fn skip_offset(&self) -> usize {
match self.kind {
(TapeTagKind::Long | TapeTagKind::Double, _) => 2,
(
TapeTagKind::Compound,
TapeTagValue {
compound: (_, offset),
},
) => u32::from(offset) as usize,
(
TapeTagKind::ListList,
TapeTagValue {
list_list: (_, offset),
},
) => u32::from(offset) as usize,
(
TapeTagKind::CompoundList,
TapeTagValue {
compound_list: (_, offset),
},
) => u32::from(offset) as usize,
_ => 1,
}
}
}
impl Debug for TapeElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// just writes the u64
write!(f, "TapeElement({:#016x})", unsafe { self.name })?;
Ok(())
}
}
#[repr(C, packed)]
#[derive(Copy, Clone)]
pub union TapeTagValue {
pub byte: i8,
pub short: i16,
pub int: i32,
pub long: (), // value is in next tape element
pub float: f32,
pub double: (), // value is in next tape element
pub byte_array: u56, // pointer to the original data
pub string: u56, // pointer to the original data
pub compound: (u24, UnalignedU32), // length estimate + tape index offset to the end of the compound
pub int_array: u56, // pointer to the original data
pub long_array: u56, // pointer to the original data
// lists
pub empty_list: (),
pub byte_list: u56, // pointer to the original data
pub short_list: u56, // pointer to the original data
pub int_list: u56, // pointer to the original data
pub long_list: u56, // pointer to the original data
pub float_list: u56, // pointer to the original data
pub double_list: u56, // pointer to the original data
pub byte_array_list: (u24, UnalignedU32), // padding + index to ExtraTapes which has a fat pointer that points to the original data
pub string_list: (u24, UnalignedU32), // padding + index to ExtraTapes
pub list_list: (u24, UnalignedU32), // length estimate + tape index offset to the end of the list
pub compound_list: (u24, UnalignedU32), // length estimate + tape index offset to the end of the list
pub int_array_list: (u24, UnalignedU32), // padding + index to ExtraTapes
pub long_array_list: (u24, UnalignedU32), // padding + index to ExtraTapes
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
#[allow(non_camel_case_types)]
pub struct u56 {
a: u8,
b: u16,
c: u32,
}
impl From<u64> for u56 {
#[inline]
fn from(value: u64) -> Self {
let a = (value >> 48) as u8;
let b = (value >> 32) as u16;
let c = value as u32;
Self { a, b, c }
}
}
impl From<u56> for u64 {
#[inline]
fn from(value: u56) -> Self {
let a = value.a as u64;
let b = value.b as u64;
let c = value.c as u64;
(a << 48) | (b << 32) | c
}
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
#[allow(non_camel_case_types)]
pub struct u24 {
a: u8,
b: u16,
}
impl From<u32> for u24 {
#[inline]
fn from(value: u32) -> Self {
let a = (value >> 16) as u8;
let b = value as u16;
Self { a, b }
}
}
impl From<u24> for u32 {
#[inline]
fn from(value: u24) -> Self {
let a = value.a as u32;
let b = value.b as u32;
(a << 16) | b
}
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
pub struct UnalignedU32(pub u32);
impl From<u32> for UnalignedU32 {
#[inline]
fn from(value: u32) -> Self {
Self(value)
}
}
impl From<UnalignedU32> for u32 {
#[inline]
fn from(value: UnalignedU32) -> Self {
value.0
}
}
#[derive(Debug, Copy, Clone)]
#[repr(packed)]
pub struct UnalignedU16(pub u16);
impl From<u16> for UnalignedU16 {
#[inline]
fn from(value: u16) -> Self {
Self(value)
}
}
impl From<UnalignedU16> for u16 {
#[inline]
fn from(value: UnalignedU16) -> Self {
value.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TapeTagKind {
Byte = BYTE_ID,
Short = SHORT_ID,
Int = INT_ID,
Long = LONG_ID,
Float = FLOAT_ID,
Double = DOUBLE_ID,
ByteArray = BYTE_ARRAY_ID,
String = STRING_ID,
Compound = COMPOUND_ID,
IntArray = INT_ARRAY_ID,
LongArray = LONG_ARRAY_ID,
EmptyList,
ByteList,
ShortList,
IntList,
LongList,
FloatList,
DoubleList,
ByteArrayList,
StringList,
ListList,
CompoundList,
IntArrayList,
LongArrayList,
}
impl TapeTagKind {
pub fn is_list(&self) -> bool {
matches!(
self,
TapeTagKind::EmptyList
| TapeTagKind::ByteList
| TapeTagKind::ShortList
| TapeTagKind::IntList
| TapeTagKind::LongList
| TapeTagKind::FloatList
| TapeTagKind::DoubleList
| TapeTagKind::ByteArrayList
| TapeTagKind::StringList
| TapeTagKind::ListList
| TapeTagKind::CompoundList
| TapeTagKind::IntArrayList
| TapeTagKind::LongArrayList
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_u56() {
// top 8 bits are cut off
let value = 0x1234_5678_9abc_def0;
let u56 { a, b, c } = u56::from(value);
assert_eq!(a, 0x34);
assert_eq!(b, 0x5678);
assert_eq!(c, 0x9abc_def0);
let value: u64 = u56 { a, b, c }.into();
assert_eq!(value, 0x34_5678_9abc_def0);
}
}

View file

@ -1,7 +1,8 @@
use std::{io::Cursor, mem, slice};
use std::{mem, slice};
use crate::{
raw_list::RawList,
reader::Reader,
swap_endianness::{swap_endianness_as_u8, SwappableNumber},
Error, Mutf8Str,
};
@ -23,83 +24,37 @@ pub const LONG_ARRAY_ID: u8 = 12;
pub const MAX_DEPTH: usize = 512;
#[inline(always)]
pub fn read_u32(data: &mut Cursor<&[u8]>) -> Result<u32, Error> {
let remaining_slice = &data.get_ref()[data.position() as usize..data.get_ref().len()];
if remaining_slice.len() < 4 {
return Err(Error::UnexpectedEof);
}
data.set_position(data.position() + 4);
Ok(u32::from_be_bytes([
remaining_slice[0],
remaining_slice[1],
remaining_slice[2],
remaining_slice[3],
]))
}
#[inline(always)]
pub fn read_u16(data: &mut Cursor<&[u8]>) -> Result<u16, Error> {
let remaining_slice = &data.get_ref()[data.position() as usize..data.get_ref().len()];
if remaining_slice.len() < 2 {
return Err(Error::UnexpectedEof);
}
data.set_position(data.position() + 2);
Ok(u16::from_be_bytes([remaining_slice[0], remaining_slice[1]]))
}
#[inline(always)]
pub fn read_with_u16_length<'a>(
data: &mut Cursor<&'a [u8]>,
width: usize,
) -> Result<&'a [u8], Error> {
let length = read_u16(data)?;
pub fn read_with_u16_length<'a>(data: &mut Reader<'a>, width: usize) -> Result<&'a [u8], Error> {
let length = data.read_u16()?;
let length_in_bytes = length as usize * width;
// make sure we don't read more than the length
if data.get_ref().len() < data.position() as usize + length_in_bytes {
return Err(Error::UnexpectedEof);
}
let start_position = data.position() as usize;
data.set_position(data.position() + length_in_bytes as u64);
Ok(&data.get_ref()[start_position..start_position + length_in_bytes])
data.read_slice(length_in_bytes)
}
#[inline(never)]
pub fn read_with_u32_length<'a>(
data: &mut Cursor<&'a [u8]>,
width: usize,
) -> Result<&'a [u8], Error> {
let length = read_u32(data)?;
pub fn read_with_u32_length<'a>(data: &mut Reader<'a>, width: usize) -> Result<&'a [u8], Error> {
let length = data.read_u32()?;
let length_in_bytes = length as usize * width;
// make sure we don't read more than the length
if data.get_ref().len() < data.position() as usize + length_in_bytes {
return Err(Error::UnexpectedEof);
}
let start_position = data.position() as usize;
data.set_position(data.position() + length_in_bytes as u64);
Ok(&data.get_ref()[start_position..start_position + length_in_bytes])
data.read_slice(length_in_bytes)
}
pub fn read_string<'a>(data: &mut Cursor<&'a [u8]>) -> Result<&'a Mutf8Str, Error> {
pub fn read_string<'a>(data: &mut Reader<'a>) -> Result<&'a Mutf8Str, Error> {
let data = read_with_u16_length(data, 1)?;
Ok(Mutf8Str::from_slice(data))
}
pub fn read_u8_array<'a>(data: &mut Cursor<&'a [u8]>) -> Result<&'a [u8], Error> {
pub fn read_u8_array<'a>(data: &mut Reader<'a>) -> Result<&'a [u8], Error> {
read_with_u32_length(data, 1)
}
pub fn read_i8_array<'a>(data: &mut Cursor<&'a [u8]>) -> Result<&'a [i8], Error> {
pub fn read_i8_array<'a>(data: &mut Reader<'a>) -> Result<&'a [i8], Error> {
Ok(slice_u8_into_i8(read_u8_array(data)?))
}
pub fn read_int_array<'a>(data: &mut Cursor<&'a [u8]>) -> Result<RawList<'a, i32>, Error> {
pub fn read_int_array<'a>(data: &mut Reader<'a>) -> Result<RawList<'a, i32>, Error> {
let array_bytes = read_with_u32_length(data, 4)?;
Ok(RawList::new(array_bytes))
}
pub fn read_long_array<'a>(data: &mut Cursor<&'a [u8]>) -> Result<RawList<'a, i64>, Error> {
pub fn read_long_array<'a>(data: &mut Reader<'a>) -> Result<RawList<'a, i64>, Error> {
let array_bytes = read_with_u32_length(data, 8)?;
Ok(RawList::new(array_bytes))
}

View file

@ -1,6 +1,11 @@
#![doc = include_str!("../README.md")]
#![feature(portable_simd)]
#![feature(array_chunks)]
#![allow(internal_features)]
#![feature(core_intrinsics)]
#[cfg(not(target_pointer_width = "64"))]
compile_error!("simdnbt only supports 64-bit platforms");
pub mod borrow;
mod common;
@ -8,6 +13,7 @@ mod error;
mod mutf8;
pub mod owned;
pub mod raw_list;
mod reader;
pub mod swap_endianness;
mod traits;

View file

@ -135,13 +135,15 @@ impl fmt::Display for Mutf8Str {
impl fmt::Debug for Mutf8Str {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Mutf8Str").field(&self.to_str()).finish()
f.write_str("m")?;
fmt::Debug::fmt(&self.to_str(), f)
}
}
impl fmt::Debug for Mutf8String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Mutf8String").field(&self.to_str()).finish()
f.write_str("m")?;
fmt::Debug::fmt(&self.to_str(), f)
}
}

View file

@ -1,13 +1,9 @@
use std::{
io::Cursor,
mem::{self, MaybeUninit},
};
use byteorder::ReadBytesExt;
use std::mem::{self, MaybeUninit};
use crate::{
common::{read_string, unchecked_push, unchecked_write_string, END_ID, MAX_DEPTH},
mutf8::Mutf8String,
reader::Reader,
Error, Mutf8Str, ToNbtTag,
};
@ -28,12 +24,12 @@ impl NbtCompound {
Self { values }
}
pub(crate) fn read(data: &mut Cursor<&[u8]>) -> Result<Self, Error> {
pub(crate) fn read(data: &mut Reader<'_>) -> Result<Self, Error> {
Self::read_with_depth(data, 0)
}
pub(crate) fn read_with_depth_and_capacity(
data: &mut Cursor<&[u8]>,
data: &mut Reader<'_>,
depth: usize,
capacity: usize,
) -> Result<Self, Error> {
@ -73,7 +69,7 @@ impl NbtCompound {
Ok(Self { values })
}
pub(crate) fn read_with_depth(data: &mut Cursor<&[u8]>, depth: usize) -> Result<Self, Error> {
pub(crate) fn read_with_depth(data: &mut Reader<'_>, depth: usize) -> Result<Self, Error> {
Self::read_with_depth_and_capacity(data, depth, 8)
}

View file

@ -1,7 +1,3 @@
use std::io::Cursor;
use byteorder::ReadBytesExt;
use crate::{
common::{
read_i8_array, read_int_array, read_long_array, read_string, read_u8_array,
@ -11,11 +7,12 @@ use crate::{
LONG_ID, SHORT_ID, STRING_ID,
},
mutf8::Mutf8String,
reader::Reader,
swap_endianness::swap_endianness,
Error,
};
use super::{compound::NbtCompound, read_u32, MAX_DEPTH};
use super::{compound::NbtCompound, MAX_DEPTH};
/// A list of NBT tags of a single type.
#[repr(u8)]
@ -37,14 +34,14 @@ pub enum NbtList {
LongArray(Vec<Vec<i64>>) = LONG_ARRAY_ID,
}
impl NbtList {
pub(crate) fn read(data: &mut Cursor<&[u8]>, depth: usize) -> Result<Self, Error> {
pub(crate) fn read(data: &mut Reader<'_>, depth: usize) -> Result<Self, Error> {
if depth > MAX_DEPTH {
return Err(Error::MaxDepthExceeded);
}
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
Ok(match tag_type {
END_ID => {
data.set_position(data.position() + 4);
data.skip(4)?;
NbtList::Empty
}
BYTE_ID => NbtList::Byte(read_i8_array(data)?.to_owned()),
@ -54,7 +51,7 @@ impl NbtList {
FLOAT_ID => NbtList::Float(swap_endianness(read_with_u32_length(data, 4)?)),
DOUBLE_ID => NbtList::Double(swap_endianness(read_with_u32_length(data, 8)?)),
BYTE_ARRAY_ID => NbtList::ByteArray({
let length = read_u32(data)?;
let length = data.read_u32()?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -63,7 +60,7 @@ impl NbtList {
arrays
}),
STRING_ID => NbtList::String({
let length = read_u32(data)?;
let length = data.read_u32()?;
// arbitrary number to prevent big allocations
let mut strings = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -72,7 +69,7 @@ impl NbtList {
strings
}),
LIST_ID => NbtList::List({
let length = read_u32(data)?;
let length = data.read_u32()?;
// arbitrary number to prevent big allocations
let mut lists = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -81,7 +78,7 @@ impl NbtList {
lists
}),
COMPOUND_ID => NbtList::Compound({
let length = read_u32(data)?;
let length = data.read_u32()?;
// arbitrary number to prevent big allocations
let mut compounds = Vec::with_capacity(length.min(128) as usize);
let mut capacity: usize = 8;
@ -93,7 +90,7 @@ impl NbtList {
compounds
}),
INT_ARRAY_ID => NbtList::IntArray({
let length = read_u32(data)?;
let length = data.read_u32()?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {
@ -102,7 +99,7 @@ impl NbtList {
arrays
}),
LONG_ARRAY_ID => NbtList::LongArray({
let length = read_u32(data)?;
let length = data.read_u32()?;
// arbitrary number to prevent big allocations
let mut arrays = Vec::with_capacity(length.min(128) as usize);
for _ in 0..length {

View file

@ -6,16 +6,15 @@ mod list;
use std::{io::Cursor, ops::Deref};
use byteorder::{ReadBytesExt, BE};
use crate::{
common::{
read_int_array, read_long_array, read_string, read_u32, read_with_u32_length,
read_int_array, read_long_array, read_string, read_with_u32_length,
slice_into_u8_big_endian, unchecked_extend, unchecked_push, write_string, BYTE_ARRAY_ID,
BYTE_ID, COMPOUND_ID, DOUBLE_ID, END_ID, FLOAT_ID, INT_ARRAY_ID, INT_ID, LIST_ID,
LONG_ARRAY_ID, LONG_ID, MAX_DEPTH, SHORT_ID, STRING_ID,
},
mutf8::Mutf8String,
reader::{Reader, ReaderFromCursor},
Error, Mutf8Str,
};
@ -25,7 +24,8 @@ pub use self::{compound::NbtCompound, list::NbtList};
///
/// Returns `Ok(Nbt::None)` if there is no data.
pub fn read(data: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
Nbt::read(data)
let mut reader = ReaderFromCursor::new(data);
Nbt::read(&mut reader)
}
/// Read a root NBT compound, but without reading the name. This is used in Minecraft when reading
/// NBT over the network.
@ -33,22 +33,26 @@ pub fn read(data: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
/// This is similar to [`read_tag`], but returns an [`Nbt`] instead (guaranteeing it'll be either
/// empty or a compound).
pub fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
Nbt::read_unnamed(data)
let mut reader = ReaderFromCursor::new(data);
Nbt::read_unnamed(&mut reader)
}
/// Read a compound tag. This may have any number of items.
pub fn read_compound(data: &mut Cursor<&[u8]>) -> Result<NbtCompound, Error> {
NbtCompound::read(data)
let mut reader = ReaderFromCursor::new(data);
NbtCompound::read(&mut reader)
}
/// Read an NBT tag, without reading its name. This may be any type of tag except for an end tag. If you need to be able to
/// handle end tags, use [`read_optional_tag`].
pub fn read_tag(data: &mut Cursor<&[u8]>) -> Result<NbtTag, Error> {
NbtTag::read(data)
let mut reader = ReaderFromCursor::new(data);
NbtTag::read(&mut reader)
}
/// Read any NBT tag, without reading its name. This may be any type of tag, including an end tag.
///
/// Returns `Ok(None)` if there is no data.
pub fn read_optional_tag(data: &mut Cursor<&[u8]>) -> Result<Option<NbtTag>, Error> {
Ok(NbtTag::read_optional(data)?)
let mut reader = ReaderFromCursor::new(data);
NbtTag::read_optional(&mut reader)
}
/// A complete NBT container. This contains a name and a compound tag.
@ -71,7 +75,7 @@ impl Nbt {
}
/// Reads NBT from the given data. Returns `Ok(Nbt::None)` if there is no data.
fn read(data: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
fn read(data: &mut Reader<'_>) -> Result<Nbt, Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(Nbt::None);
@ -85,7 +89,7 @@ impl Nbt {
Ok(Nbt::Some(BaseNbt { name, tag }))
}
fn read_unnamed(data: &mut Cursor<&[u8]>) -> Result<Nbt, Error> {
fn read_unnamed(data: &mut Reader<'_>) -> Result<Nbt, Error> {
let root_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if root_type == END_ID {
return Ok(Nbt::None);
@ -258,25 +262,25 @@ impl NbtTag {
}
#[inline(always)]
fn read_with_type(data: &mut Cursor<&[u8]>, tag_type: u8, depth: usize) -> Result<Self, Error> {
fn read_with_type(data: &mut Reader<'_>, tag_type: u8, depth: usize) -> Result<Self, Error> {
match tag_type {
BYTE_ID => Ok(NbtTag::Byte(
data.read_i8().map_err(|_| Error::UnexpectedEof)?,
)),
SHORT_ID => Ok(NbtTag::Short(
data.read_i16::<BE>().map_err(|_| Error::UnexpectedEof)?,
data.read_i16().map_err(|_| Error::UnexpectedEof)?,
)),
INT_ID => Ok(NbtTag::Int(
data.read_i32::<BE>().map_err(|_| Error::UnexpectedEof)?,
data.read_i32().map_err(|_| Error::UnexpectedEof)?,
)),
LONG_ID => Ok(NbtTag::Long(
data.read_i64::<BE>().map_err(|_| Error::UnexpectedEof)?,
data.read_i64().map_err(|_| Error::UnexpectedEof)?,
)),
FLOAT_ID => Ok(NbtTag::Float(
data.read_f32::<BE>().map_err(|_| Error::UnexpectedEof)?,
data.read_f32().map_err(|_| Error::UnexpectedEof)?,
)),
DOUBLE_ID => Ok(NbtTag::Double(
data.read_f64::<BE>().map_err(|_| Error::UnexpectedEof)?,
data.read_f64().map_err(|_| Error::UnexpectedEof)?,
)),
BYTE_ARRAY_ID => Ok(NbtTag::ByteArray(read_with_u32_length(data, 1)?.to_owned())),
STRING_ID => Ok(NbtTag::String(read_string(data)?.to_owned())),
@ -291,12 +295,12 @@ impl NbtTag {
}
}
pub fn read(data: &mut Cursor<&[u8]>) -> Result<Self, Error> {
fn read(data: &mut Reader<'_>) -> Result<Self, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
Self::read_with_type(data, tag_type, 0)
}
pub fn read_optional(data: &mut Cursor<&[u8]>) -> Result<Option<Self>, Error> {
fn read_optional(data: &mut Reader<'_>) -> Result<Option<Self>, Error> {
let tag_type = data.read_u8().map_err(|_| Error::UnexpectedEof)?;
if tag_type == END_ID {
return Ok(None);
@ -621,14 +625,14 @@ impl From<Nbt> for NbtTag {
mod tests {
use std::io::Read;
use byteorder::WriteBytesExt;
use byteorder::{WriteBytesExt, BE};
use flate2::read::GzDecoder;
use super::*;
#[test]
fn hello_world() {
let nbt = Nbt::read(&mut Cursor::new(include_bytes!(
let nbt = super::read(&mut Cursor::new(include_bytes!(
"../../tests/hello_world.nbt"
)))
.unwrap()
@ -648,7 +652,9 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&decoded_src))
.unwrap()
.unwrap();
assert_eq!(nbt.int("PersistentId"), Some(1946940766));
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
@ -661,7 +667,9 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&decoded_src))
.unwrap()
.unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
@ -674,11 +682,13 @@ mod tests {
let mut decoded_src_decoder = GzDecoder::new(&mut src_slice);
let mut decoded_src = Vec::new();
decoded_src_decoder.read_to_end(&mut decoded_src).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&decoded_src)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&decoded_src))
.unwrap()
.unwrap();
let mut out = Vec::new();
nbt.write(&mut out);
let nbt = Nbt::read(&mut Cursor::new(&out)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&out)).unwrap().unwrap();
assert_eq!(nbt.float("foodExhaustionLevel").unwrap() as u32, 2);
assert_eq!(nbt.list("Rotation").unwrap().floats().unwrap().len(), 2);
@ -686,7 +696,7 @@ mod tests {
#[test]
fn inttest_1023() {
let nbt = Nbt::read(&mut Cursor::new(include_bytes!(
let nbt = super::read(&mut Cursor::new(include_bytes!(
"../../tests/inttest1023.nbt"
)))
.unwrap()
@ -714,7 +724,7 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
@ -736,7 +746,7 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().ints().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i32, item);
@ -758,7 +768,7 @@ mod tests {
}
data.write_u8(END_ID).unwrap();
let nbt = Nbt::read(&mut Cursor::new(&data)).unwrap().unwrap();
let nbt = super::read(&mut Cursor::new(&data)).unwrap().unwrap();
let ints = nbt.list("").unwrap().longs().unwrap();
for (i, &item) in ints.iter().enumerate() {
assert_eq!(i as i64, item);

View file

@ -4,7 +4,7 @@ use crate::swap_endianness::{swap_endianness, swap_endianness_as_u8, SwappableNu
/// A list of numbers that's kept as big-endian in memory.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct RawList<'a, T> {
data: &'a [u8],
_marker: PhantomData<T>,
@ -24,6 +24,10 @@ impl<'a, T> RawList<'a, T> {
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn into_inner(self) -> &'a [u8] {
self.data
}
}
impl<T: SwappableNumber> RawList<'_, T> {

148
simdnbt/src/reader.rs Normal file
View file

@ -0,0 +1,148 @@
use std::{
io::Cursor,
marker::PhantomData,
ops::{Deref, DerefMut},
};
use crate::Error;
pub struct Reader<'a> {
pub cur: *const u8,
/// pointer to after the last byte (so remaining=end-cur)
end: *const u8,
_marker: PhantomData<&'a ()>,
}
impl<'a> Reader<'a> {
pub fn new(data: &'a [u8]) -> Reader<'a> {
Self {
cur: data.as_ptr(),
end: unsafe { data.as_ptr().add(data.len()) },
_marker: PhantomData,
}
}
pub fn ensure_can_read(&self, size: usize) -> Result<(), Error> {
let data_addr = self.cur as usize;
let end_addr = self.end as usize;
if data_addr + size > end_addr {
Err(Error::UnexpectedEof)
} else {
Ok(())
}
}
pub unsafe fn unchecked_read_type<T>(&mut self) -> T {
let value = unsafe { self.cur.cast::<T>().read_unaligned() };
self.cur = unsafe { self.cur.add(std::mem::size_of::<T>()) };
value
}
pub fn read_type<T: Copy>(&mut self) -> Result<T, Error> {
self.ensure_can_read(std::mem::size_of::<T>())?;
Ok(unsafe { self.unchecked_read_type() })
}
#[inline]
pub fn read_u8(&mut self) -> Result<u8, Error> {
self.read_type()
}
#[inline]
pub fn read_i8(&mut self) -> Result<i8, Error> {
self.read_u8().map(|x| x as i8)
}
#[inline]
pub fn read_u16(&mut self) -> Result<u16, Error> {
self.read_type::<u16>().map(u16::swap_bytes)
}
#[inline]
pub fn read_i16(&mut self) -> Result<i16, Error> {
self.read_u16().map(|x| x as i16)
}
#[inline]
pub fn read_u32(&mut self) -> Result<u32, Error> {
self.read_type::<u32>().map(u32::swap_bytes)
}
#[inline]
pub fn read_i32(&mut self) -> Result<i32, Error> {
self.read_u32().map(|x| x as i32)
}
#[inline]
pub fn read_u64(&mut self) -> Result<u64, Error> {
self.read_type::<u64>().map(u64::swap_bytes)
}
#[inline]
pub fn read_i64(&mut self) -> Result<i64, Error> {
self.read_u64().map(|x| x as i64)
}
#[inline]
pub fn read_f32(&mut self) -> Result<f32, Error> {
self.read_u32().map(f32::from_bits)
}
#[inline]
pub fn read_f64(&mut self) -> Result<f64, Error> {
self.read_u64().map(f64::from_bits)
}
#[inline]
pub fn skip(&mut self, size: usize) -> Result<(), Error> {
self.ensure_can_read(size)?;
self.cur = unsafe { self.cur.add(size) };
Ok(())
}
#[inline]
pub fn read_slice(&mut self, size: usize) -> Result<&'a [u8], Error> {
self.ensure_can_read(size)?;
let slice = unsafe { std::slice::from_raw_parts(self.cur, size) };
self.cur = unsafe { self.cur.add(size) };
Ok(slice)
}
}
impl<'a> From<&'a [u8]> for Reader<'a> {
fn from(data: &'a [u8]) -> Self {
Self::new(data)
}
}
pub struct ReaderFromCursor<'a: 'cursor, 'cursor> {
reader: Reader<'a>,
original_cursor: &'cursor mut Cursor<&'a [u8]>,
}
impl<'a, 'cursor> ReaderFromCursor<'a, 'cursor> {
pub fn new(cursor: &'cursor mut Cursor<&'a [u8]>) -> Self {
let data = cursor.get_ref();
Self {
reader: Reader::new(data[cursor.position() as usize..].as_ref()),
original_cursor: cursor,
}
}
}
impl Drop for ReaderFromCursor<'_, '_> {
fn drop(&mut self) {
self.original_cursor.set_position(
(self.reader.cur as usize - self.original_cursor.get_ref().as_ptr() as usize) as u64,
);
}
}
impl<'a> Deref for ReaderFromCursor<'a, '_> {
type Target = Reader<'a>;
fn deref(&self) -> &Self::Target {
&self.reader
}
}
impl<'a> DerefMut for ReaderFromCursor<'a, '_> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.reader
}
}

View file

@ -4,10 +4,10 @@ use crate::DeserializeError;
pub trait Deserialize: Sized {
fn from_nbt(nbt: &crate::borrow::BaseNbt) -> Result<Self, DeserializeError> {
Self::from_compound(nbt)
Self::from_compound(nbt.as_compound())
}
fn from_compound(compound: &crate::borrow::NbtCompound) -> Result<Self, DeserializeError>;
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError>;
}
pub trait Serialize: Sized {
@ -19,9 +19,9 @@ pub trait Serialize: Sized {
}
pub trait FromNbtTag: Sized {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self>;
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self>;
fn from_optional_nbt_tag(
tag: Option<&crate::borrow::NbtTag>,
tag: Option<crate::borrow::NbtTag>,
) -> Result<Option<Self>, DeserializeError> {
match tag {
Some(tag) => Ok(Self::from_nbt_tag(tag)),
@ -38,7 +38,7 @@ pub trait ToNbtTag: Sized {
}
impl<K: Display + FromStr + Eq + Hash, V: FromNbtTag> Deserialize for HashMap<K, V> {
fn from_compound(compound: &crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
let mut hashmap = HashMap::with_capacity(compound.len());
for (k, v) in compound.iter() {
@ -70,7 +70,7 @@ impl<K: Display + FromStr + Eq + Hash, V: ToNbtTag> Serialize for HashMap<K, V>
}
impl Deserialize for crate::owned::NbtCompound {
fn from_compound(compound: &crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
fn from_compound(compound: crate::borrow::NbtCompound) -> Result<Self, DeserializeError> {
Ok(compound.to_owned())
}
}
@ -81,7 +81,7 @@ impl Serialize for crate::owned::NbtCompound {
}
impl<T: Deserialize> FromNbtTag for T {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.compound().and_then(|c| Self::from_compound(c).ok())
}
}
@ -93,7 +93,7 @@ impl<T: Serialize> ToNbtTag for T {
}
impl FromNbtTag for crate::owned::NbtTag {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
Some(tag.to_owned())
}
}
@ -105,7 +105,7 @@ impl ToNbtTag for crate::owned::NbtTag {
// standard nbt types
impl FromNbtTag for i8 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.byte()
}
}
@ -116,7 +116,7 @@ impl ToNbtTag for i8 {
}
impl FromNbtTag for i16 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.short()
}
}
@ -127,7 +127,7 @@ impl ToNbtTag for i16 {
}
impl FromNbtTag for i32 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.int()
}
}
@ -138,7 +138,7 @@ impl ToNbtTag for i32 {
}
impl FromNbtTag for i64 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.long()
}
}
@ -149,7 +149,7 @@ impl ToNbtTag for i64 {
}
impl FromNbtTag for f32 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.float()
}
}
@ -160,7 +160,7 @@ impl ToNbtTag for f32 {
}
impl FromNbtTag for f64 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.double()
}
}
@ -171,7 +171,7 @@ impl ToNbtTag for f64 {
}
impl FromNbtTag for String {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.string().map(|s| s.to_string())
}
}
@ -189,7 +189,7 @@ impl ToNbtTag for &str {
// unsigned integers
impl FromNbtTag for u8 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.byte().map(|b| b as u8)
}
}
@ -200,7 +200,7 @@ impl ToNbtTag for u8 {
}
impl FromNbtTag for u16 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.short().map(|s| s as u16)
}
}
@ -211,7 +211,7 @@ impl ToNbtTag for u16 {
}
impl FromNbtTag for u32 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.int().map(|i| i as u32)
}
}
@ -222,7 +222,7 @@ impl ToNbtTag for u32 {
}
impl FromNbtTag for u64 {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.long().map(|l| l as u64)
}
}
@ -234,7 +234,7 @@ impl ToNbtTag for u64 {
// lists
impl FromNbtTag for Vec<String> {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.list().and_then(|l| {
l.strings()
.map(|s| s.iter().map(|s| s.to_string()).collect())
@ -251,11 +251,11 @@ impl ToNbtTag for Vec<String> {
// slightly less standard types
impl<T: FromNbtTag> FromNbtTag for Option<T> {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
Some(T::from_nbt_tag(tag))
}
fn from_optional_nbt_tag(
tag: Option<&crate::borrow::NbtTag>,
tag: Option<crate::borrow::NbtTag>,
) -> Result<Option<Self>, DeserializeError> {
match tag {
Some(tag) => Ok(Some(T::from_nbt_tag(tag))),
@ -274,8 +274,9 @@ impl<T: ToNbtTag> ToNbtTag for Option<T> {
impl<T: Deserialize> FromNbtTag for Vec<Option<T>> {
/// A list of compounds where `None` is an empty compound
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
let list = tag.list()?.compounds()?;
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
let list = tag.list()?;
let list = list.compounds()?;
let mut vec = Vec::with_capacity(list.len());
for tag in list {
if tag.is_empty() {
@ -302,8 +303,9 @@ impl<T: Serialize> ToNbtTag for Vec<Option<T>> {
}
impl<T: Deserialize> FromNbtTag for Vec<T> {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
let list = tag.list()?.compounds()?;
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
let list = tag.list()?;
let list = list.compounds()?;
let mut vec = Vec::with_capacity(list.len());
for tag in list {
vec.push(T::from_compound(tag).ok()?);
@ -321,7 +323,7 @@ impl<T: Serialize> ToNbtTag for Vec<T> {
}
impl FromNbtTag for bool {
fn from_nbt_tag(tag: &crate::borrow::NbtTag) -> Option<Self> {
fn from_nbt_tag(tag: crate::borrow::NbtTag) -> Option<Self> {
tag.byte().map(|b| b != 0)
}
}