Add benchmark
This commit is contained in:
Generated
+445
@@ -11,6 +11,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anes"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -61,6 +67,57 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cast"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.1"
|
version = "4.6.1"
|
||||||
@@ -107,6 +164,79 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[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.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
|
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.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -130,18 +260,85 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "half"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crunchy",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.2"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[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.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jiff"
|
name = "jiff"
|
||||||
version = "0.2.24"
|
version = "0.2.24"
|
||||||
@@ -166,6 +363,24 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.97"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.186"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.29"
|
||||||
@@ -178,12 +393,67 @@ version = "2.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[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.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell_polyfill"
|
name = "once_cell_polyfill"
|
||||||
version = "1.70.2"
|
version = "1.70.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oorandom"
|
||||||
|
version = "11.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"plotters-backend",
|
||||||
|
"plotters-svg",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters-backend"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plotters-svg"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||||
|
dependencies = [
|
||||||
|
"plotters-backend",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.13.1"
|
version = "1.13.1"
|
||||||
@@ -217,6 +487,26 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"rayon-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayon-core"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.3"
|
||||||
@@ -251,10 +541,36 @@ name = "roto"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"criterion",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||||
|
|
||||||
|
[[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.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -275,6 +591,25 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.149"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@@ -292,6 +627,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinytemplate"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
@@ -304,6 +649,80 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[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.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.120"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.97"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -318,3 +737,29 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.48"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ edition = "2024"
|
|||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "hackers_bench"
|
||||||
|
harness = false
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ returning a slice of the bytes written.
|
|||||||
`CodeGeneratorResponse` to stdout. `protoc` then writes those `.rs` files to disk. The generated
|
`CodeGeneratorResponse` to stdout. `protoc` then writes those `.rs` files to disk. The generated
|
||||||
files are included directly in the crate that uses the protobuffers.
|
files are included directly in the crate that uses the protobuffers.
|
||||||
|
|
||||||
|
Sample usage:
|
||||||
|
|
||||||
|
```
|
||||||
|
protoc -Iproto/ proto/hackers.proto --plugin=./target/debug/protoc-gen-roto --roto_out=src/
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate a file, src/hackers.rs.
|
||||||
|
|
||||||
## Generated code
|
## Generated code
|
||||||
|
|
||||||
For each protobuf message roto generates two types:
|
For each protobuf message roto generates two types:
|
||||||
@@ -117,6 +125,81 @@ using `FieldIterator` and records the byte offset of each field's tag. Subsequen
|
|||||||
call `ProtoAccessor::get_value_at(offset)` — no re-scanning. For repeated fields, the start and
|
call `ProtoAccessor::get_value_at(offset)` — no re-scanning. For repeated fields, the start and
|
||||||
end offsets of the field range are recorded to bound iteration efficiently.
|
end offsets of the field range are recorded to bound iteration efficiently.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Two benchmark suites share the same binary data files and the same four
|
||||||
|
measurement groups:
|
||||||
|
|
||||||
|
| Group | What is timed |
|
||||||
|
| --------------- | ------------------------------------------------------- |
|
||||||
|
| `shallow_parse` | Become ready to read any field (one scan / full decode) |
|
||||||
|
| `deep_parse` | Walk the full tree: Campaign → Operations → Hackers |
|
||||||
|
| `field_access` | Read individual fields on an already-parsed message |
|
||||||
|
| `iterate` | Count top-level and nested repeated fields |
|
||||||
|
|
||||||
|
### 1 — Generate the shared data files (do this once)
|
||||||
|
|
||||||
|
Data files are written to `data/bench/`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --release --bin gen_bench_data -- --preset tiny
|
||||||
|
cargo run --release --bin gen_bench_data -- --preset small
|
||||||
|
cargo run --release --bin gen_bench_data -- --preset medium
|
||||||
|
cargo run --release --bin gen_bench_data -- --preset large
|
||||||
|
```
|
||||||
|
|
||||||
|
For even larger inputs use `--preset huge` (~500 MB) or set the knobs
|
||||||
|
directly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# ~50 MB: 500 operations × 100 KB stolen_data each
|
||||||
|
cargo run --release --bin gen_bench_data -- --ops 500 --stolen-kb 100 --output data/bench/50mb.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2 — Rust benchmark (criterion)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo bench --bench hackers_bench
|
||||||
|
```
|
||||||
|
|
||||||
|
HTML reports are written to `target/criterion/`. Run a single group:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo bench --bench hackers_bench -- shallow_parse
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3 — C / upb benchmark
|
||||||
|
|
||||||
|
Requires protobuf ≥ 21 with `protoc-gen-upb` (ships with modern `protoc`).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd upb_test
|
||||||
|
make # compiles hackers_bench from the pre-generated upb files
|
||||||
|
./hackers_bench
|
||||||
|
```
|
||||||
|
|
||||||
|
To regenerate the upb C files from `proto/hackers.proto`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd upb_test && make regen
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interpreting the comparison
|
||||||
|
|
||||||
|
The two libraries have fundamentally different models:
|
||||||
|
|
||||||
|
- **roto `shallow_parse`** does one linear scan recording byte offsets — no
|
||||||
|
allocation, no field decoding. Subsequent field reads decode on demand at
|
||||||
|
the stored offset.
|
||||||
|
- **upb `Campaign_parse`** fully decodes the entire message tree into
|
||||||
|
arena-allocated structs upfront. Subsequent field reads are direct struct
|
||||||
|
member lookups (~1 ns).
|
||||||
|
|
||||||
|
The result: roto's parse is faster and allocation-free; upb's field access
|
||||||
|
after parsing is faster. For workloads that read every field the costs
|
||||||
|
invert; for workloads that read a handful of fields from large messages roto
|
||||||
|
wins.
|
||||||
|
|
||||||
## Literature
|
## Literature
|
||||||
|
|
||||||
https://protobuf.dev/programming-guides/encoding/
|
https://protobuf.dev/programming-guides/encoding/
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
//! Benchmark suite for roto — themed after the 1995 film *Hackers*.
|
||||||
|
//!
|
||||||
|
//! Proto schema: `proto/hackers.proto`
|
||||||
|
//! Generated types: `src/hackers.rs` (via `protoc-gen-roto`)
|
||||||
|
//!
|
||||||
|
//! # Setup
|
||||||
|
//!
|
||||||
|
//! Generate the data files once before running benchmarks:
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset tiny
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset small
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset medium
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset large
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Then run:
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! cargo bench --bench hackers_bench
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Benchmark groups:
|
||||||
|
//! - `shallow_parse` — `Campaign::new(data)`, one scan of the whole blob
|
||||||
|
//! - `deep_parse` — Campaign → Operations → Hackers (a `::new()` per level)
|
||||||
|
//! - `field_access` — individual field reads on pre-parsed messages (O(1))
|
||||||
|
//! - `iterate` — counting repeated fields at different nesting depths
|
||||||
|
|
||||||
|
use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
|
||||||
|
use roto::hackers::{Campaign, Hacker, Operation, Worm};
|
||||||
|
use std::hint::black_box;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Data loading
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// Load a pre-generated data file from `data/bench/<name>.pb`.
|
||||||
|
/// Returns `None` (and prints a hint) if the file does not exist.
|
||||||
|
fn load(name: &str) -> Option<Vec<u8>> {
|
||||||
|
let path = format!("data/bench/{name}.pb");
|
||||||
|
match std::fs::read(&path) {
|
||||||
|
Ok(data) => Some(data),
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!(
|
||||||
|
"[skip] {path} not found — \
|
||||||
|
run `cargo run --release --bin gen_bench_data -- --preset {name}` first"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Benchmarks
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/// `Campaign::new()` — one linear scan to record field offsets, no allocation.
|
||||||
|
/// Throughput reported in MB/s so different sizes are directly comparable.
|
||||||
|
fn bench_shallow_parse(c: &mut Criterion) {
|
||||||
|
let cases = [
|
||||||
|
("tiny", load("tiny")),
|
||||||
|
("small", load("small")),
|
||||||
|
("medium", load("medium")),
|
||||||
|
("large", load("large")),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut group = c.benchmark_group("shallow_parse");
|
||||||
|
for (label, maybe_data) in &cases {
|
||||||
|
let Some(data) = maybe_data else { continue };
|
||||||
|
group.throughput(Throughput::Bytes(data.len() as u64));
|
||||||
|
group.bench_with_input(BenchmarkId::new("Campaign::new", label), data, |b, data| {
|
||||||
|
b.iter(|| Campaign::new(black_box(data)).unwrap())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Walk every level of the tree: Campaign → Operations → Hackers.
|
||||||
|
/// Each `::new()` is an additional linear scan of that sub-message's bytes.
|
||||||
|
fn bench_deep_parse(c: &mut Criterion) {
|
||||||
|
let cases = [
|
||||||
|
("tiny", load("tiny")),
|
||||||
|
("small", load("small")),
|
||||||
|
("medium", load("medium")),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut group = c.benchmark_group("deep_parse");
|
||||||
|
for (label, maybe_data) in &cases {
|
||||||
|
let Some(data) = maybe_data else { continue };
|
||||||
|
group.throughput(Throughput::Bytes(data.len() as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("Campaign+Ops+Hackers", label),
|
||||||
|
data,
|
||||||
|
|b, data| {
|
||||||
|
b.iter(|| {
|
||||||
|
let campaign = Campaign::new(data).unwrap();
|
||||||
|
let mut hacker_count = 0usize;
|
||||||
|
for op_res in campaign.operations() {
|
||||||
|
let (op_bytes, _) = op_res.unwrap();
|
||||||
|
let op = Operation::new(op_bytes).unwrap();
|
||||||
|
for crew_res in op.crew() {
|
||||||
|
let (hacker_bytes, _) = crew_res.unwrap();
|
||||||
|
let hacker = Hacker::new(hacker_bytes).unwrap();
|
||||||
|
let _ = black_box(hacker.handle().unwrap());
|
||||||
|
hacker_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
black_box(hacker_count)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// O(1) field accesses on pre-parsed messages.
|
||||||
|
/// Measures only the decode step at a known offset — not the scan.
|
||||||
|
fn bench_field_access(c: &mut Criterion) {
|
||||||
|
let Some(data) = load("small") else { return };
|
||||||
|
|
||||||
|
let campaign = Campaign::new(&data).unwrap();
|
||||||
|
let (op_bytes, _) = campaign.operations().next().unwrap().unwrap();
|
||||||
|
let op = Operation::new(op_bytes).unwrap();
|
||||||
|
let (hacker_bytes, _) = op.crew().next().unwrap().unwrap();
|
||||||
|
let hacker = Hacker::new(hacker_bytes).unwrap();
|
||||||
|
let worm = Worm::new(op.worm().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let mut group = c.benchmark_group("field_access");
|
||||||
|
|
||||||
|
group.bench_function("campaign::name", |b| {
|
||||||
|
b.iter(|| black_box(campaign.name().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("campaign::total_bytes_stolen", |b| {
|
||||||
|
b.iter(|| black_box(campaign.total_bytes_stolen().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("operation::codename", |b| {
|
||||||
|
b.iter(|| black_box(op.codename().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("operation::timestamp", |b| {
|
||||||
|
b.iter(|| black_box(op.timestamp().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("operation::successful", |b| {
|
||||||
|
b.iter(|| black_box(op.successful().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("hacker::handle", |b| {
|
||||||
|
b.iter(|| black_box(hacker.handle().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("hacker::skill_level (f32)", |b| {
|
||||||
|
b.iter(|| black_box(hacker.skill_level().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("hacker::is_elite (bool)", |b| {
|
||||||
|
b.iter(|| black_box(hacker.is_elite().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("worm::polymorphic (bool)", |b| {
|
||||||
|
b.iter(|| black_box(worm.polymorphic().unwrap()))
|
||||||
|
});
|
||||||
|
group.bench_function("worm::payload (bytes)", |b| {
|
||||||
|
b.iter(|| black_box(worm.payload().unwrap()))
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate repeated fields at different depths.
|
||||||
|
fn bench_iterate(c: &mut Criterion) {
|
||||||
|
let cases = [
|
||||||
|
("tiny", load("tiny")),
|
||||||
|
("small", load("small")),
|
||||||
|
("medium", load("medium")),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut group = c.benchmark_group("iterate");
|
||||||
|
for (label, maybe_data) in &cases {
|
||||||
|
let Some(data) = maybe_data else { continue };
|
||||||
|
|
||||||
|
// Top-level repeated field — walk Operation blobs, no inner parse.
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("count_operations", label),
|
||||||
|
data,
|
||||||
|
|b, data| {
|
||||||
|
b.iter(|| {
|
||||||
|
let campaign = Campaign::new(data).unwrap();
|
||||||
|
black_box(campaign.operations().count())
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Nested repeated field — parse each Operation to reach its crew.
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("count_all_crew", label),
|
||||||
|
data,
|
||||||
|
|b, data| {
|
||||||
|
b.iter(|| {
|
||||||
|
let campaign = Campaign::new(data).unwrap();
|
||||||
|
let mut n = 0usize;
|
||||||
|
for op_res in campaign.operations() {
|
||||||
|
let (op_bytes, _) = op_res.unwrap();
|
||||||
|
n += Operation::new(op_bytes).unwrap().crew().count();
|
||||||
|
}
|
||||||
|
black_box(n)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
benches,
|
||||||
|
bench_shallow_parse,
|
||||||
|
bench_deep_parse,
|
||||||
|
bench_field_access,
|
||||||
|
bench_iterate
|
||||||
|
);
|
||||||
|
criterion_main!(benches);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
bench/
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
message Tool {
|
||||||
|
string name = 1;
|
||||||
|
string version = 2;
|
||||||
|
bytes payload = 3;
|
||||||
|
bool is_active = 4;
|
||||||
|
int32 exploit_count = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Connection {
|
||||||
|
string host = 1;
|
||||||
|
int32 port = 2;
|
||||||
|
bool encrypted = 3;
|
||||||
|
int64 bandwidth_bps = 4;
|
||||||
|
bytes session_key = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Hacker {
|
||||||
|
string handle = 1;
|
||||||
|
string real_name = 2;
|
||||||
|
int32 age = 3;
|
||||||
|
float skill_level = 4; // Fixed32
|
||||||
|
bool is_elite = 5;
|
||||||
|
int64 crew_id = 6;
|
||||||
|
repeated string exploits = 7;
|
||||||
|
repeated Tool tools = 8;
|
||||||
|
Connection active_connection = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Worm {
|
||||||
|
string name = 1;
|
||||||
|
int32 variant = 2;
|
||||||
|
int64 size_bytes = 3;
|
||||||
|
bytes payload = 4;
|
||||||
|
bool polymorphic = 5;
|
||||||
|
repeated string targets = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Operation {
|
||||||
|
string codename = 1;
|
||||||
|
string target_corp = 2;
|
||||||
|
int64 timestamp = 3;
|
||||||
|
bool successful = 4;
|
||||||
|
bytes stolen_data = 5;
|
||||||
|
repeated Hacker crew = 6;
|
||||||
|
Worm worm = 7;
|
||||||
|
repeated string log_entries = 8;
|
||||||
|
int32 severity = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Campaign {
|
||||||
|
string name = 1;
|
||||||
|
repeated Operation operations = 2;
|
||||||
|
int64 total_bytes_stolen = 3;
|
||||||
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
//! Generates Hackers-themed benchmark proto binaries using the roto builder API.
|
||||||
|
//!
|
||||||
|
//! Run this once to create the data files that `hackers_bench` loads:
|
||||||
|
//!
|
||||||
|
//! ```sh
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset tiny
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset small
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset medium
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset large
|
||||||
|
//! cargo run --release --bin gen_bench_data -- --preset huge
|
||||||
|
//!
|
||||||
|
//! # Custom: ~50 MB — 500 ops × 100 KB stolen_data each
|
||||||
|
//! cargo run --release --bin gen_bench_data -- \
|
||||||
|
//! --ops 500 --stolen-kb 100 --output data/bench/50mb.pb
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Files land in `data/bench/` by default.
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use roto::hackers::{
|
||||||
|
CampaignBuilder, ConnectionBuilder, HackerBuilder, OperationBuilder, ToolBuilder, WormBuilder,
|
||||||
|
};
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// CLI
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(
|
||||||
|
name = "gen_bench_data",
|
||||||
|
about = "Generate Hackers-themed proto binaries for benchmarks"
|
||||||
|
)]
|
||||||
|
struct Args {
|
||||||
|
/// Output file. Defaults to data/bench/<preset>.pb when --preset is used.
|
||||||
|
#[arg(short, long)]
|
||||||
|
output: Option<String>,
|
||||||
|
|
||||||
|
/// Named size preset: tiny | small | medium | large | huge
|
||||||
|
#[arg(short, long)]
|
||||||
|
preset: Option<String>,
|
||||||
|
|
||||||
|
/// Number of Operation messages.
|
||||||
|
#[arg(long, default_value_t = 100)]
|
||||||
|
ops: usize,
|
||||||
|
|
||||||
|
/// Kilobytes of random stolen_data padding per Operation.
|
||||||
|
#[arg(long, default_value_t = 0)]
|
||||||
|
stolen_kb: usize,
|
||||||
|
|
||||||
|
/// Hacker crew members per Operation.
|
||||||
|
#[arg(long, default_value_t = 3)]
|
||||||
|
crew: usize,
|
||||||
|
|
||||||
|
/// RNG seed.
|
||||||
|
#[arg(long, default_value_t = 42)]
|
||||||
|
seed: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Minimal xorshift64 RNG
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
struct Rng(u64);
|
||||||
|
|
||||||
|
impl Rng {
|
||||||
|
fn new(seed: u64) -> Self {
|
||||||
|
Self(if seed == 0 { 0xdeadbeef_cafebabe } else { seed })
|
||||||
|
}
|
||||||
|
fn next(&mut self) -> u64 {
|
||||||
|
self.0 ^= self.0 << 13;
|
||||||
|
self.0 ^= self.0 >> 7;
|
||||||
|
self.0 ^= self.0 << 17;
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
fn below(&mut self, n: usize) -> usize {
|
||||||
|
(self.next() as usize) % n
|
||||||
|
}
|
||||||
|
fn range(&mut self, lo: u64, hi: u64) -> u64 {
|
||||||
|
lo + self.next() % (hi - lo)
|
||||||
|
}
|
||||||
|
fn bool(&mut self) -> bool {
|
||||||
|
self.next() & 1 == 0
|
||||||
|
}
|
||||||
|
fn pick<'a, T>(&mut self, s: &'a [T]) -> &'a T {
|
||||||
|
&s[self.below(s.len())]
|
||||||
|
}
|
||||||
|
fn bytes(&mut self, n: usize) -> Vec<u8> {
|
||||||
|
(0..n).map(|_| self.next() as u8).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Flavour text
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
const HANDLES: &[&str] = &[
|
||||||
|
"Zero Cool",
|
||||||
|
"Acid Burn",
|
||||||
|
"Phantom Phreak",
|
||||||
|
"Cereal Killer",
|
||||||
|
"Lord Nikon",
|
||||||
|
"The Plague",
|
||||||
|
"Crash Override",
|
||||||
|
];
|
||||||
|
const REAL_NAMES: &[&str] = &[
|
||||||
|
"Dade Murphy",
|
||||||
|
"Kate Libby",
|
||||||
|
"Richard Gill",
|
||||||
|
"Emmanuel Goldstein",
|
||||||
|
"Paul Cook",
|
||||||
|
"Eugene Belford",
|
||||||
|
];
|
||||||
|
const EXPLOITS: &[&str] = &[
|
||||||
|
"buffer overflow",
|
||||||
|
"stack smash",
|
||||||
|
"heap spray",
|
||||||
|
"race condition",
|
||||||
|
"SQL injection",
|
||||||
|
"CSRF",
|
||||||
|
"XSS",
|
||||||
|
"RCE",
|
||||||
|
"privesc",
|
||||||
|
"kernel panic",
|
||||||
|
];
|
||||||
|
const TOOL_NAMES: &[&str] = &[
|
||||||
|
"nmap",
|
||||||
|
"metasploit",
|
||||||
|
"netcat",
|
||||||
|
"tcpdump",
|
||||||
|
"Wireshark",
|
||||||
|
"sqlmap",
|
||||||
|
"Burp Suite",
|
||||||
|
"hashcat",
|
||||||
|
"john",
|
||||||
|
];
|
||||||
|
const CORPS: &[&str] = &[
|
||||||
|
"ELLINGSON MINERAL",
|
||||||
|
"Cyberdelia",
|
||||||
|
"The Gibson",
|
||||||
|
"Prism BBS",
|
||||||
|
"Elite BBS",
|
||||||
|
"CRT Systems",
|
||||||
|
];
|
||||||
|
const LOG_LINES: &[&str] = &[
|
||||||
|
"Hack the planet!",
|
||||||
|
"Mess with the best, die like the rest.",
|
||||||
|
"I'm in.",
|
||||||
|
"They're tracing us.",
|
||||||
|
"It's a Unix system! I know this!",
|
||||||
|
"You are elite.",
|
||||||
|
"Garbage file accessed.",
|
||||||
|
];
|
||||||
|
const WORM_TARGETS: &[&str] = &[
|
||||||
|
"ELLINGSON MINERAL",
|
||||||
|
"Cyberdelia",
|
||||||
|
"The Gibson",
|
||||||
|
"Prism",
|
||||||
|
"CRT BBS",
|
||||||
|
];
|
||||||
|
const OP_NAMES: &[&str] = &[
|
||||||
|
"OPERATION HACK THE PLANET",
|
||||||
|
"GIBSON BREACH",
|
||||||
|
"ELLINGSON STING",
|
||||||
|
"WORM UNLEASHED",
|
||||||
|
"PHANTOM ACCESS",
|
||||||
|
"DA VINCI",
|
||||||
|
];
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Message builders using the generated roto::hackers API
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
fn gen_tool(rng: &mut Rng) -> Vec<u8> {
|
||||||
|
let payload_n = rng.range(8, 64) as usize;
|
||||||
|
let payload = rng.bytes(payload_n);
|
||||||
|
let version = format!("{}.{}", rng.range(1, 9), rng.range(0, 99));
|
||||||
|
let mut buf = vec![0u8; 512];
|
||||||
|
ToolBuilder::builder(&mut buf)
|
||||||
|
.name(*rng.pick(TOOL_NAMES))
|
||||||
|
.unwrap()
|
||||||
|
.version(&version)
|
||||||
|
.unwrap()
|
||||||
|
.payload(&payload)
|
||||||
|
.unwrap()
|
||||||
|
.is_active(rng.bool() as u64)
|
||||||
|
.unwrap()
|
||||||
|
.exploit_count(rng.range(0, 50) as i32)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.unwrap()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_connection(rng: &mut Rng) -> Vec<u8> {
|
||||||
|
let host = format!("192.168.{}.{}", rng.range(1, 254), rng.range(1, 254));
|
||||||
|
let session_key = rng.bytes(32);
|
||||||
|
let mut buf = vec![0u8; 256];
|
||||||
|
ConnectionBuilder::builder(&mut buf)
|
||||||
|
.host(&host)
|
||||||
|
.unwrap()
|
||||||
|
.port(rng.range(1024, 65535) as i32)
|
||||||
|
.unwrap()
|
||||||
|
.encrypted(rng.bool() as u64)
|
||||||
|
.unwrap()
|
||||||
|
.bandwidth_bps(rng.range(1200, 1_000_000_000))
|
||||||
|
.unwrap()
|
||||||
|
.session_key(&session_key)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.unwrap()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_hacker(rng: &mut Rng) -> Vec<u8> {
|
||||||
|
let tools: Vec<Vec<u8>> = (0..rng.range(1, 4)).map(|_| gen_tool(rng)).collect();
|
||||||
|
let connection = gen_connection(rng);
|
||||||
|
// Float (skill_level) is written as raw bytes by the generated builder
|
||||||
|
let skill_bits = (rng.range(10, 100) as f32 / 10.0).to_bits().to_le_bytes();
|
||||||
|
let handle = *rng.pick(HANDLES);
|
||||||
|
let real_name = *rng.pick(REAL_NAMES);
|
||||||
|
let n_exploits = rng.range(2, 5) as usize;
|
||||||
|
let exploits: Vec<&str> = (0..n_exploits).map(|_| *rng.pick(EXPLOITS)).collect();
|
||||||
|
let crew_id = rng.next();
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; 8 * 1024];
|
||||||
|
let mut b = HackerBuilder::builder(&mut buf)
|
||||||
|
.handle(handle)
|
||||||
|
.unwrap()
|
||||||
|
.real_name(real_name)
|
||||||
|
.unwrap()
|
||||||
|
.age(rng.range(16, 35) as i32)
|
||||||
|
.unwrap()
|
||||||
|
.skill_level(&skill_bits)
|
||||||
|
.unwrap()
|
||||||
|
.is_elite(rng.bool() as u64)
|
||||||
|
.unwrap()
|
||||||
|
.crew_id(crew_id)
|
||||||
|
.unwrap();
|
||||||
|
for e in &exploits {
|
||||||
|
b = b.exploits(e).unwrap();
|
||||||
|
}
|
||||||
|
for t in &tools {
|
||||||
|
b = b.tools(t).unwrap();
|
||||||
|
}
|
||||||
|
b.active_connection(&connection)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.unwrap()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_worm(rng: &mut Rng) -> Vec<u8> {
|
||||||
|
let payload = rng.bytes(64);
|
||||||
|
let name = format!("da_vinci.{}", rng.range(1, 99));
|
||||||
|
let n_targets = rng.range(1, 4) as usize;
|
||||||
|
let targets: Vec<&str> = (0..n_targets).map(|_| *rng.pick(WORM_TARGETS)).collect();
|
||||||
|
|
||||||
|
let mut buf = vec![0u8; 1024];
|
||||||
|
let mut b = WormBuilder::builder(&mut buf)
|
||||||
|
.name(&name)
|
||||||
|
.unwrap()
|
||||||
|
.variant(rng.range(1, 5) as i32)
|
||||||
|
.unwrap()
|
||||||
|
.size_bytes(rng.range(1024, 10_000_000))
|
||||||
|
.unwrap()
|
||||||
|
.payload(&payload)
|
||||||
|
.unwrap()
|
||||||
|
.polymorphic(rng.bool() as u64)
|
||||||
|
.unwrap();
|
||||||
|
for t in &targets {
|
||||||
|
b = b.targets(t).unwrap();
|
||||||
|
}
|
||||||
|
b.finish().unwrap().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_operation(rng: &mut Rng, crew_count: usize, stolen_bytes: usize) -> Vec<u8> {
|
||||||
|
let crew: Vec<Vec<u8>> = (0..crew_count).map(|_| gen_hacker(rng)).collect();
|
||||||
|
let worm = gen_worm(rng);
|
||||||
|
let stolen = if stolen_bytes > 0 {
|
||||||
|
rng.bytes(stolen_bytes)
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
let codename = *rng.pick(OP_NAMES);
|
||||||
|
let corp = *rng.pick(CORPS);
|
||||||
|
let ts = 810_000_000u64 + rng.range(0, 10_000_000);
|
||||||
|
let n_logs = rng.range(2, 6) as usize;
|
||||||
|
let logs: Vec<&str> = (0..n_logs).map(|_| *rng.pick(LOG_LINES)).collect();
|
||||||
|
|
||||||
|
// Operation buffer: crew + worm + stolen_data + small overhead
|
||||||
|
let crew_size: usize = crew.iter().map(|h| h.len() + 5).sum();
|
||||||
|
let buf_size = crew_size + worm.len() + stolen_bytes + 1024;
|
||||||
|
let mut buf = vec![0u8; buf_size];
|
||||||
|
|
||||||
|
let mut b = OperationBuilder::builder(&mut buf)
|
||||||
|
.codename(codename)
|
||||||
|
.unwrap()
|
||||||
|
.target_corp(corp)
|
||||||
|
.unwrap()
|
||||||
|
.timestamp(ts)
|
||||||
|
.unwrap()
|
||||||
|
.successful(rng.bool() as u64)
|
||||||
|
.unwrap();
|
||||||
|
if stolen_bytes > 0 {
|
||||||
|
b = b.stolen_data(&stolen).unwrap();
|
||||||
|
}
|
||||||
|
for h in &crew {
|
||||||
|
b = b.crew(h).unwrap();
|
||||||
|
}
|
||||||
|
b = b.worm(&worm).unwrap();
|
||||||
|
for l in &logs {
|
||||||
|
b = b.log_entries(l).unwrap();
|
||||||
|
}
|
||||||
|
b.severity(rng.range(1, 10) as i32)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.unwrap()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_campaign(
|
||||||
|
rng: &mut Rng,
|
||||||
|
op_count: usize,
|
||||||
|
crew_per_op: usize,
|
||||||
|
stolen_bytes: usize,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
let ops: Vec<Vec<u8>> = (0..op_count)
|
||||||
|
.map(|i| {
|
||||||
|
if i > 0 && i % 100 == 0 {
|
||||||
|
eprintln!(
|
||||||
|
" {i}/{op_count} operations ({:.1} MB in ops so far)…",
|
||||||
|
i * (stolen_bytes + 10_000) / 1_000_000
|
||||||
|
);
|
||||||
|
}
|
||||||
|
gen_operation(rng, crew_per_op, stolen_bytes)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Pre-compute campaign buffer size: for each op the wire encoding is
|
||||||
|
// tag(1B) + varint_length(1-5B) + op_bytes
|
||||||
|
let ops_wire_size: usize = ops
|
||||||
|
.iter()
|
||||||
|
.map(|o| 1 + varint_len(o.len() as u64) + o.len())
|
||||||
|
.sum();
|
||||||
|
let mut buf = vec![0u8; ops_wire_size + 64];
|
||||||
|
|
||||||
|
let mut b = CampaignBuilder::builder(&mut buf)
|
||||||
|
.name("HACK THE PLANET CAMPAIGN")
|
||||||
|
.unwrap();
|
||||||
|
for op in &ops {
|
||||||
|
b = b.operations(op).unwrap();
|
||||||
|
}
|
||||||
|
b.total_bytes_stolen((stolen_bytes * op_count) as u64)
|
||||||
|
.unwrap()
|
||||||
|
.finish()
|
||||||
|
.unwrap()
|
||||||
|
.to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of bytes needed to encode `v` as a varint.
|
||||||
|
fn varint_len(mut v: u64) -> usize {
|
||||||
|
let mut n = 1usize;
|
||||||
|
while v >= 128 {
|
||||||
|
v >>= 7;
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Preset table
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
struct Preset {
|
||||||
|
ops: usize,
|
||||||
|
crew: usize,
|
||||||
|
stolen_kb: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(name: &str) -> Option<Preset> {
|
||||||
|
match name {
|
||||||
|
// ops crew stolen_kb approx size
|
||||||
|
"tiny" => Some(Preset {
|
||||||
|
ops: 1,
|
||||||
|
crew: 1,
|
||||||
|
stolen_kb: 0,
|
||||||
|
}), // ~400 B
|
||||||
|
"small" => Some(Preset {
|
||||||
|
ops: 20,
|
||||||
|
crew: 3,
|
||||||
|
stolen_kb: 0,
|
||||||
|
}), // ~25 KB
|
||||||
|
"medium" => Some(Preset {
|
||||||
|
ops: 2_000,
|
||||||
|
crew: 3,
|
||||||
|
stolen_kb: 0,
|
||||||
|
}), // ~2 MB
|
||||||
|
"large" => Some(Preset {
|
||||||
|
ops: 200,
|
||||||
|
crew: 3,
|
||||||
|
stolen_kb: 500,
|
||||||
|
}), // ~100 MB
|
||||||
|
"huge" => Some(Preset {
|
||||||
|
ops: 1_000,
|
||||||
|
crew: 3,
|
||||||
|
stolen_kb: 500,
|
||||||
|
}), // ~500 MB
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// main
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = Args::parse();
|
||||||
|
|
||||||
|
if let Some(ref name) = args.preset.clone() {
|
||||||
|
match resolve(name) {
|
||||||
|
Some(p) => {
|
||||||
|
args.ops = p.ops;
|
||||||
|
args.crew = p.crew;
|
||||||
|
args.stolen_kb = p.stolen_kb;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
eprintln!("Unknown preset '{name}'. Valid: tiny, small, medium, large, huge");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stolen_bytes = args.stolen_kb * 1024;
|
||||||
|
let approx_mb = args.ops * (700 + args.crew * 300 + stolen_bytes) / 1_000_000;
|
||||||
|
eprintln!(
|
||||||
|
"Generating: {} ops × {} crew, {} KB stolen_data each → ~{} MB",
|
||||||
|
args.ops, args.crew, args.stolen_kb, approx_mb
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut rng = Rng::new(args.seed);
|
||||||
|
let data = gen_campaign(&mut rng, args.ops, args.crew, stolen_bytes);
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"Generated {} bytes ({:.2} MB)",
|
||||||
|
data.len(),
|
||||||
|
data.len() as f64 / 1_000_000.0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default output: data/bench/<preset>.pb, or stdout if no output and no preset
|
||||||
|
let out_path = args
|
||||||
|
.output
|
||||||
|
.clone()
|
||||||
|
.or_else(|| args.preset.as_ref().map(|p| format!("data/bench/{}.pb", p)));
|
||||||
|
|
||||||
|
match out_path {
|
||||||
|
Some(ref path) => {
|
||||||
|
if let Some(parent) = Path::new(path).parent() {
|
||||||
|
if !parent.as_os_str().is_empty() {
|
||||||
|
std::fs::create_dir_all(parent).unwrap_or_else(|e| eprintln!("Warning: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fs::write(path, &data).unwrap_or_else(|e| {
|
||||||
|
eprintln!("Error writing {path}: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
eprintln!("Saved to {path}");
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
io::stdout().write_all(&data).unwrap_or_else(|e| {
|
||||||
|
eprintln!("Error: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -53,7 +53,7 @@ fn map_type_to_rust_accessor(field_type: i32, label: i32) -> (String, String) {
|
|||||||
), // TYPE_DOUBLE
|
), // TYPE_DOUBLE
|
||||||
2 => (
|
2 => (
|
||||||
"f32".to_string(),
|
"f32".to_string(),
|
||||||
"f32::from_le_bytes(bytes.try_into().map_err(|_| crate::RotoError::WireFormatViolation)?)".to_string(),
|
"Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| crate::RotoError::WireFormatViolation)?))".to_string(),
|
||||||
), // TYPE_FLOAT
|
), // TYPE_FLOAT
|
||||||
3 | 5 | 15 | 17 => (
|
3 | 5 | 15 | 17 => (
|
||||||
"i32".to_string(),
|
"i32".to_string(),
|
||||||
|
|||||||
+801
@@ -0,0 +1,801 @@
|
|||||||
|
// @generated by protoc-gen-roto — do not edit
|
||||||
|
|
||||||
|
use crate::{ProtoAccessor, ProtoBuilder, Result, RotoError, read_varint, RepeatedFieldIterator};
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct Tool<'a> {
|
||||||
|
accessor: crate::ProtoAccessor<'a>,
|
||||||
|
name_offset: Option<usize>,
|
||||||
|
version_offset: Option<usize>,
|
||||||
|
payload_offset: Option<usize>,
|
||||||
|
is_active_offset: Option<usize>,
|
||||||
|
exploit_count_offset: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Tool<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> crate::Result<Self> {
|
||||||
|
let accessor = crate::ProtoAccessor::new(data)?;
|
||||||
|
let mut name_offset = None;
|
||||||
|
let mut version_offset = None;
|
||||||
|
let mut payload_offset = None;
|
||||||
|
let mut is_active_offset = None;
|
||||||
|
let mut exploit_count_offset = None;
|
||||||
|
for item in accessor.fields() {
|
||||||
|
let (offset, tag, _) = item?;
|
||||||
|
if tag.field_number == 1 { name_offset = Some(offset); }
|
||||||
|
if tag.field_number == 2 { version_offset = Some(offset); }
|
||||||
|
if tag.field_number == 3 { payload_offset = Some(offset); }
|
||||||
|
if tag.field_number == 4 { is_active_offset = Some(offset); }
|
||||||
|
if tag.field_number == 5 { exploit_count_offset = Some(offset); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
accessor,
|
||||||
|
name_offset,
|
||||||
|
version_offset,
|
||||||
|
payload_offset,
|
||||||
|
is_active_offset,
|
||||||
|
exploit_count_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.name_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.version_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(&self) -> crate::Result<&'a [u8]> {
|
||||||
|
let offset = self.payload_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_active(&self) -> crate::Result<bool> {
|
||||||
|
let offset = self.is_active_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exploit_count(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.exploit_count_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ToolBuilder<'b> {
|
||||||
|
builder: crate::ProtoBuilder<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> ToolBuilder<'b> {
|
||||||
|
pub fn builder(buf: &mut [u8]) -> ToolBuilder<'_> {
|
||||||
|
ToolBuilder {
|
||||||
|
builder: crate::ProtoBuilder::new(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(1, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(2, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(3, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_active(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(4, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exploit_count(mut self, value: i32) -> crate::Result<Self> {
|
||||||
|
self.builder.write_int32(5, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Connection<'a> {
|
||||||
|
accessor: crate::ProtoAccessor<'a>,
|
||||||
|
host_offset: Option<usize>,
|
||||||
|
port_offset: Option<usize>,
|
||||||
|
encrypted_offset: Option<usize>,
|
||||||
|
bandwidth_bps_offset: Option<usize>,
|
||||||
|
session_key_offset: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Connection<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> crate::Result<Self> {
|
||||||
|
let accessor = crate::ProtoAccessor::new(data)?;
|
||||||
|
let mut host_offset = None;
|
||||||
|
let mut port_offset = None;
|
||||||
|
let mut encrypted_offset = None;
|
||||||
|
let mut bandwidth_bps_offset = None;
|
||||||
|
let mut session_key_offset = None;
|
||||||
|
for item in accessor.fields() {
|
||||||
|
let (offset, tag, _) = item?;
|
||||||
|
if tag.field_number == 1 { host_offset = Some(offset); }
|
||||||
|
if tag.field_number == 2 { port_offset = Some(offset); }
|
||||||
|
if tag.field_number == 3 { encrypted_offset = Some(offset); }
|
||||||
|
if tag.field_number == 4 { bandwidth_bps_offset = Some(offset); }
|
||||||
|
if tag.field_number == 5 { session_key_offset = Some(offset); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
accessor,
|
||||||
|
host_offset,
|
||||||
|
port_offset,
|
||||||
|
encrypted_offset,
|
||||||
|
bandwidth_bps_offset,
|
||||||
|
session_key_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn host(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.host_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn port(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.port_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypted(&self) -> crate::Result<bool> {
|
||||||
|
let offset = self.encrypted_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bandwidth_bps(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.bandwidth_bps_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_key(&self) -> crate::Result<&'a [u8]> {
|
||||||
|
let offset = self.session_key_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectionBuilder<'b> {
|
||||||
|
builder: crate::ProtoBuilder<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> ConnectionBuilder<'b> {
|
||||||
|
pub fn builder(buf: &mut [u8]) -> ConnectionBuilder<'_> {
|
||||||
|
ConnectionBuilder {
|
||||||
|
builder: crate::ProtoBuilder::new(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn host(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(1, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn port(mut self, value: i32) -> crate::Result<Self> {
|
||||||
|
self.builder.write_int32(2, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypted(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(3, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bandwidth_bps(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(4, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_key(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(5, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Hacker<'a> {
|
||||||
|
accessor: crate::ProtoAccessor<'a>,
|
||||||
|
handle_offset: Option<usize>,
|
||||||
|
real_name_offset: Option<usize>,
|
||||||
|
age_offset: Option<usize>,
|
||||||
|
skill_level_offset: Option<usize>,
|
||||||
|
is_elite_offset: Option<usize>,
|
||||||
|
crew_id_offset: Option<usize>,
|
||||||
|
exploits_start: Option<usize>,
|
||||||
|
exploits_end: Option<usize>,
|
||||||
|
tools_start: Option<usize>,
|
||||||
|
tools_end: Option<usize>,
|
||||||
|
active_connection_offset: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Hacker<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> crate::Result<Self> {
|
||||||
|
let accessor = crate::ProtoAccessor::new(data)?;
|
||||||
|
let mut handle_offset = None;
|
||||||
|
let mut real_name_offset = None;
|
||||||
|
let mut age_offset = None;
|
||||||
|
let mut skill_level_offset = None;
|
||||||
|
let mut is_elite_offset = None;
|
||||||
|
let mut crew_id_offset = None;
|
||||||
|
let mut exploits_start = None;
|
||||||
|
let mut exploits_end = None;
|
||||||
|
let mut tools_start = None;
|
||||||
|
let mut tools_end = None;
|
||||||
|
let mut active_connection_offset = None;
|
||||||
|
for item in accessor.fields() {
|
||||||
|
let (offset, tag, _) = item?;
|
||||||
|
if tag.field_number == 1 { handle_offset = Some(offset); }
|
||||||
|
if tag.field_number == 2 { real_name_offset = Some(offset); }
|
||||||
|
if tag.field_number == 3 { age_offset = Some(offset); }
|
||||||
|
if tag.field_number == 4 { skill_level_offset = Some(offset); }
|
||||||
|
if tag.field_number == 5 { is_elite_offset = Some(offset); }
|
||||||
|
if tag.field_number == 6 { crew_id_offset = Some(offset); }
|
||||||
|
if tag.field_number == 7 {
|
||||||
|
if exploits_start.is_none() { exploits_start = Some(offset); }
|
||||||
|
exploits_end = Some(offset);
|
||||||
|
}
|
||||||
|
if tag.field_number == 8 {
|
||||||
|
if tools_start.is_none() { tools_start = Some(offset); }
|
||||||
|
tools_end = Some(offset);
|
||||||
|
}
|
||||||
|
if tag.field_number == 9 { active_connection_offset = Some(offset); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
accessor,
|
||||||
|
handle_offset,
|
||||||
|
real_name_offset,
|
||||||
|
age_offset,
|
||||||
|
skill_level_offset,
|
||||||
|
is_elite_offset,
|
||||||
|
crew_id_offset,
|
||||||
|
exploits_start, exploits_end,
|
||||||
|
tools_start, tools_end,
|
||||||
|
active_connection_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.handle_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn real_name(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.real_name_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn age(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.age_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skill_level(&self) -> crate::Result<f32> {
|
||||||
|
let offset = self.skill_level_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(f32::from_le_bytes(bytes.try_into().map_err(|_| crate::RotoError::WireFormatViolation)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_elite(&self) -> crate::Result<bool> {
|
||||||
|
let offset = self.is_elite_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crew_id(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.crew_id_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exploits(&self) -> crate::RepeatedFieldIterator<'a> {
|
||||||
|
match (self.exploits_start, self.exploits_end) {
|
||||||
|
(Some(start), Some(end)) => self.accessor.iter_repeated_range(7, start, end),
|
||||||
|
_ => self.accessor.iter_repeated(7),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tools(&self) -> crate::RepeatedFieldIterator<'a> {
|
||||||
|
match (self.tools_start, self.tools_end) {
|
||||||
|
(Some(start), Some(end)) => self.accessor.iter_repeated_range(8, start, end),
|
||||||
|
_ => self.accessor.iter_repeated(8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_connection(&self) -> crate::Result<&'a [u8]> {
|
||||||
|
let offset = self.active_connection_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HackerBuilder<'b> {
|
||||||
|
builder: crate::ProtoBuilder<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> HackerBuilder<'b> {
|
||||||
|
pub fn builder(buf: &mut [u8]) -> HackerBuilder<'_> {
|
||||||
|
HackerBuilder {
|
||||||
|
builder: crate::ProtoBuilder::new(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(1, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn real_name(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(2, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn age(mut self, value: i32) -> crate::Result<Self> {
|
||||||
|
self.builder.write_int32(3, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skill_level(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(4, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_elite(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(5, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crew_id(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(6, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exploits(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(7, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tools(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(8, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_connection(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(9, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Worm<'a> {
|
||||||
|
accessor: crate::ProtoAccessor<'a>,
|
||||||
|
name_offset: Option<usize>,
|
||||||
|
variant_offset: Option<usize>,
|
||||||
|
size_bytes_offset: Option<usize>,
|
||||||
|
payload_offset: Option<usize>,
|
||||||
|
polymorphic_offset: Option<usize>,
|
||||||
|
targets_start: Option<usize>,
|
||||||
|
targets_end: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Worm<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> crate::Result<Self> {
|
||||||
|
let accessor = crate::ProtoAccessor::new(data)?;
|
||||||
|
let mut name_offset = None;
|
||||||
|
let mut variant_offset = None;
|
||||||
|
let mut size_bytes_offset = None;
|
||||||
|
let mut payload_offset = None;
|
||||||
|
let mut polymorphic_offset = None;
|
||||||
|
let mut targets_start = None;
|
||||||
|
let mut targets_end = None;
|
||||||
|
for item in accessor.fields() {
|
||||||
|
let (offset, tag, _) = item?;
|
||||||
|
if tag.field_number == 1 { name_offset = Some(offset); }
|
||||||
|
if tag.field_number == 2 { variant_offset = Some(offset); }
|
||||||
|
if tag.field_number == 3 { size_bytes_offset = Some(offset); }
|
||||||
|
if tag.field_number == 4 { payload_offset = Some(offset); }
|
||||||
|
if tag.field_number == 5 { polymorphic_offset = Some(offset); }
|
||||||
|
if tag.field_number == 6 {
|
||||||
|
if targets_start.is_none() { targets_start = Some(offset); }
|
||||||
|
targets_end = Some(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
accessor,
|
||||||
|
name_offset,
|
||||||
|
variant_offset,
|
||||||
|
size_bytes_offset,
|
||||||
|
payload_offset,
|
||||||
|
polymorphic_offset,
|
||||||
|
targets_start, targets_end,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.name_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variant(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.variant_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_bytes(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.size_bytes_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(&self) -> crate::Result<&'a [u8]> {
|
||||||
|
let offset = self.payload_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn polymorphic(&self) -> crate::Result<bool> {
|
||||||
|
let offset = self.polymorphic_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn targets(&self) -> crate::RepeatedFieldIterator<'a> {
|
||||||
|
match (self.targets_start, self.targets_end) {
|
||||||
|
(Some(start), Some(end)) => self.accessor.iter_repeated_range(6, start, end),
|
||||||
|
_ => self.accessor.iter_repeated(6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WormBuilder<'b> {
|
||||||
|
builder: crate::ProtoBuilder<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> WormBuilder<'b> {
|
||||||
|
pub fn builder(buf: &mut [u8]) -> WormBuilder<'_> {
|
||||||
|
WormBuilder {
|
||||||
|
builder: crate::ProtoBuilder::new(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(1, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variant(mut self, value: i32) -> crate::Result<Self> {
|
||||||
|
self.builder.write_int32(2, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_bytes(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(3, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn payload(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(4, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn polymorphic(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(5, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn targets(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(6, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Operation<'a> {
|
||||||
|
accessor: crate::ProtoAccessor<'a>,
|
||||||
|
codename_offset: Option<usize>,
|
||||||
|
target_corp_offset: Option<usize>,
|
||||||
|
timestamp_offset: Option<usize>,
|
||||||
|
successful_offset: Option<usize>,
|
||||||
|
stolen_data_offset: Option<usize>,
|
||||||
|
crew_start: Option<usize>,
|
||||||
|
crew_end: Option<usize>,
|
||||||
|
worm_offset: Option<usize>,
|
||||||
|
log_entries_start: Option<usize>,
|
||||||
|
log_entries_end: Option<usize>,
|
||||||
|
severity_offset: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Operation<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> crate::Result<Self> {
|
||||||
|
let accessor = crate::ProtoAccessor::new(data)?;
|
||||||
|
let mut codename_offset = None;
|
||||||
|
let mut target_corp_offset = None;
|
||||||
|
let mut timestamp_offset = None;
|
||||||
|
let mut successful_offset = None;
|
||||||
|
let mut stolen_data_offset = None;
|
||||||
|
let mut crew_start = None;
|
||||||
|
let mut crew_end = None;
|
||||||
|
let mut worm_offset = None;
|
||||||
|
let mut log_entries_start = None;
|
||||||
|
let mut log_entries_end = None;
|
||||||
|
let mut severity_offset = None;
|
||||||
|
for item in accessor.fields() {
|
||||||
|
let (offset, tag, _) = item?;
|
||||||
|
if tag.field_number == 1 { codename_offset = Some(offset); }
|
||||||
|
if tag.field_number == 2 { target_corp_offset = Some(offset); }
|
||||||
|
if tag.field_number == 3 { timestamp_offset = Some(offset); }
|
||||||
|
if tag.field_number == 4 { successful_offset = Some(offset); }
|
||||||
|
if tag.field_number == 5 { stolen_data_offset = Some(offset); }
|
||||||
|
if tag.field_number == 6 {
|
||||||
|
if crew_start.is_none() { crew_start = Some(offset); }
|
||||||
|
crew_end = Some(offset);
|
||||||
|
}
|
||||||
|
if tag.field_number == 7 { worm_offset = Some(offset); }
|
||||||
|
if tag.field_number == 8 {
|
||||||
|
if log_entries_start.is_none() { log_entries_start = Some(offset); }
|
||||||
|
log_entries_end = Some(offset);
|
||||||
|
}
|
||||||
|
if tag.field_number == 9 { severity_offset = Some(offset); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
accessor,
|
||||||
|
codename_offset,
|
||||||
|
target_corp_offset,
|
||||||
|
timestamp_offset,
|
||||||
|
successful_offset,
|
||||||
|
stolen_data_offset,
|
||||||
|
crew_start, crew_end,
|
||||||
|
worm_offset,
|
||||||
|
log_entries_start, log_entries_end,
|
||||||
|
severity_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn codename(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.codename_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_corp(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.target_corp_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timestamp(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.timestamp_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn successful(&self) -> crate::Result<bool> {
|
||||||
|
let offset = self.successful_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v != 0).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stolen_data(&self) -> crate::Result<&'a [u8]> {
|
||||||
|
let offset = self.stolen_data_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crew(&self) -> crate::RepeatedFieldIterator<'a> {
|
||||||
|
match (self.crew_start, self.crew_end) {
|
||||||
|
(Some(start), Some(end)) => self.accessor.iter_repeated_range(6, start, end),
|
||||||
|
_ => self.accessor.iter_repeated(6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn worm(&self) -> crate::Result<&'a [u8]> {
|
||||||
|
let offset = self.worm_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
Ok(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_entries(&self) -> crate::RepeatedFieldIterator<'a> {
|
||||||
|
match (self.log_entries_start, self.log_entries_end) {
|
||||||
|
(Some(start), Some(end)) => self.accessor.iter_repeated_range(8, start, end),
|
||||||
|
_ => self.accessor.iter_repeated(8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn severity(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.severity_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OperationBuilder<'b> {
|
||||||
|
builder: crate::ProtoBuilder<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> OperationBuilder<'b> {
|
||||||
|
pub fn builder(buf: &mut [u8]) -> OperationBuilder<'_> {
|
||||||
|
OperationBuilder {
|
||||||
|
builder: crate::ProtoBuilder::new(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn codename(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(1, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_corp(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(2, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timestamp(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(3, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn successful(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(4, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stolen_data(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(5, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crew(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(6, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn worm(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(7, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_entries(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(8, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn severity(mut self, value: i32) -> crate::Result<Self> {
|
||||||
|
self.builder.write_int32(9, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Campaign<'a> {
|
||||||
|
accessor: crate::ProtoAccessor<'a>,
|
||||||
|
name_offset: Option<usize>,
|
||||||
|
operations_start: Option<usize>,
|
||||||
|
operations_end: Option<usize>,
|
||||||
|
total_bytes_stolen_offset: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Campaign<'a> {
|
||||||
|
pub fn new(data: &'a [u8]) -> crate::Result<Self> {
|
||||||
|
let accessor = crate::ProtoAccessor::new(data)?;
|
||||||
|
let mut name_offset = None;
|
||||||
|
let mut operations_start = None;
|
||||||
|
let mut operations_end = None;
|
||||||
|
let mut total_bytes_stolen_offset = None;
|
||||||
|
for item in accessor.fields() {
|
||||||
|
let (offset, tag, _) = item?;
|
||||||
|
if tag.field_number == 1 { name_offset = Some(offset); }
|
||||||
|
if tag.field_number == 2 {
|
||||||
|
if operations_start.is_none() { operations_start = Some(offset); }
|
||||||
|
operations_end = Some(offset);
|
||||||
|
}
|
||||||
|
if tag.field_number == 3 { total_bytes_stolen_offset = Some(offset); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
accessor,
|
||||||
|
name_offset,
|
||||||
|
operations_start, operations_end,
|
||||||
|
total_bytes_stolen_offset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> crate::Result<&'a str> {
|
||||||
|
let offset = self.name_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
str::from_utf8(bytes).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn operations(&self) -> crate::RepeatedFieldIterator<'a> {
|
||||||
|
match (self.operations_start, self.operations_end) {
|
||||||
|
(Some(start), Some(end)) => self.accessor.iter_repeated_range(2, start, end),
|
||||||
|
_ => self.accessor.iter_repeated(2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_bytes_stolen(&self) -> crate::Result<i32> {
|
||||||
|
let offset = self.total_bytes_stolen_offset.ok_or(crate::RotoError::FieldNotFound)?;
|
||||||
|
let (bytes, _) = self.accessor.get_value_at(offset)?;
|
||||||
|
crate::read_varint(bytes).map(|(v, _)| v as i32).map_err(|_| crate::RotoError::WireFormatViolation)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CampaignBuilder<'b> {
|
||||||
|
builder: crate::ProtoBuilder<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> CampaignBuilder<'b> {
|
||||||
|
pub fn builder(buf: &mut [u8]) -> CampaignBuilder<'_> {
|
||||||
|
CampaignBuilder {
|
||||||
|
builder: crate::ProtoBuilder::new(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(mut self, value: &str) -> crate::Result<Self> {
|
||||||
|
self.builder.write_string(1, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn operations(mut self, value: &[u8]) -> crate::Result<Self> {
|
||||||
|
self.builder.write_bytes(2, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_bytes_stolen(mut self, value: u64) -> crate::Result<Self> {
|
||||||
|
self.builder.write_varint(3, value)?;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self) -> crate::Result<&'b mut [u8]> {
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+29
-11
@@ -1,5 +1,6 @@
|
|||||||
pub mod generator;
|
pub mod generator;
|
||||||
pub mod google;
|
pub mod google;
|
||||||
|
pub mod hackers;
|
||||||
// Uncomment this to check if the code compiles
|
// Uncomment this to check if the code compiles
|
||||||
// #[path = "../proto/google/protobuf/descriptor.rs"]
|
// #[path = "../proto/google/protobuf/descriptor.rs"]
|
||||||
// pub mod descriptor;
|
// pub mod descriptor;
|
||||||
@@ -220,11 +221,19 @@ impl<'a> ProtoAccessor<'a> {
|
|||||||
}
|
}
|
||||||
_ => (cursor_after_tag, value_len),
|
_ => (cursor_after_tag, value_len),
|
||||||
};
|
};
|
||||||
Ok((&self.data[value_offset..value_offset + actual_value_len], tag.wire_type))
|
Ok((
|
||||||
|
&self.data[value_offset..value_offset + actual_value_len],
|
||||||
|
tag.wire_type,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator that scans a specific range of the buffer for all occurrences of the specified field.
|
/// Returns an iterator that scans a specific range of the buffer for all occurrences of the specified field.
|
||||||
pub fn iter_repeated_range(&self, field_number: u32, start: usize, end: usize) -> RepeatedFieldIterator<'a> {
|
pub fn iter_repeated_range(
|
||||||
|
&self,
|
||||||
|
field_number: u32,
|
||||||
|
start: usize,
|
||||||
|
end: usize,
|
||||||
|
) -> RepeatedFieldIterator<'a> {
|
||||||
RepeatedFieldIterator::new_range(self.data, field_number, start, end)
|
RepeatedFieldIterator::new_range(self.data, field_number, start, end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,7 +289,11 @@ impl<'a> Iterator for FieldIterator<'a> {
|
|||||||
|
|
||||||
self.cursor = cursor_after_tag + value_len;
|
self.cursor = cursor_after_tag + value_len;
|
||||||
|
|
||||||
Some(Ok((self.cursor - tag_len - value_len, tag, &self.data[value_offset..value_offset + actual_value_len])))
|
Some(Ok((
|
||||||
|
self.cursor - tag_len - value_len,
|
||||||
|
tag,
|
||||||
|
&self.data[value_offset..value_offset + actual_value_len],
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,10 +306,7 @@ pub struct RepeatedFieldIterator<'a> {
|
|||||||
impl<'a> RepeatedFieldIterator<'a> {
|
impl<'a> RepeatedFieldIterator<'a> {
|
||||||
pub fn new(data: &'a [u8], field_number: u32) -> Self {
|
pub fn new(data: &'a [u8], field_number: u32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
iterator: FieldIterator {
|
iterator: FieldIterator { data, cursor: 0 },
|
||||||
data,
|
|
||||||
cursor: 0,
|
|
||||||
},
|
|
||||||
field_number,
|
field_number,
|
||||||
end_offset: None,
|
end_offset: None,
|
||||||
}
|
}
|
||||||
@@ -491,7 +501,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(i32_vals, vec![1, 2, 3, 4, 5]);
|
assert_eq!(i32_vals, vec![1, 2, 3, 4, 5]);
|
||||||
|
|
||||||
let repeated_strings: Vec<_> = acc.iter_repeated(18)
|
let repeated_strings: Vec<_> = acc
|
||||||
|
.iter_repeated(18)
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
let (val, _) = r.expect("Failed to decode repeated string");
|
let (val, _) = r.expect("Failed to decode repeated string");
|
||||||
std::str::from_utf8(val).expect("Invalid utf8")
|
std::str::from_utf8(val).expect("Invalid utf8")
|
||||||
@@ -499,7 +510,8 @@ mod tests {
|
|||||||
.collect();
|
.collect();
|
||||||
assert_eq!(repeated_strings, vec!["one", "two", "three"]);
|
assert_eq!(repeated_strings, vec!["one", "two", "three"]);
|
||||||
|
|
||||||
let repeated_nested: Vec<_> = acc.iter_repeated(19)
|
let repeated_nested: Vec<_> = acc
|
||||||
|
.iter_repeated(19)
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
let (val, _) = r.expect("Failed to decode repeated nested");
|
let (val, _) = r.expect("Failed to decode repeated nested");
|
||||||
let nested_acc = ProtoAccessor::new(val).unwrap();
|
let nested_acc = ProtoAccessor::new(val).unwrap();
|
||||||
@@ -519,7 +531,8 @@ mod tests {
|
|||||||
assert_eq!(id, 200);
|
assert_eq!(id, 200);
|
||||||
|
|
||||||
// Validate that fields appear in the expected relative order
|
// Validate that fields appear in the expected relative order
|
||||||
let field_numbers: Vec<u32> = acc.fields()
|
let field_numbers: Vec<u32> = acc
|
||||||
|
.fields()
|
||||||
.map(|r| r.expect("Failed to decode field").1.field_number)
|
.map(|r| r.expect("Failed to decode field").1.field_number)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -528,7 +541,12 @@ mod tests {
|
|||||||
let mut found_count = 0;
|
let mut found_count = 0;
|
||||||
for &f in &field_numbers {
|
for &f in &field_numbers {
|
||||||
if essential_fields.contains(&f) {
|
if essential_fields.contains(&f) {
|
||||||
assert!(f >= last_field, "Fields appeared out of order: {} came after {}", f, last_field);
|
assert!(
|
||||||
|
f >= last_field,
|
||||||
|
"Fields appeared out of order: {} came after {}",
|
||||||
|
f,
|
||||||
|
last_field
|
||||||
|
);
|
||||||
last_field = f;
|
last_field = f;
|
||||||
found_count += 1;
|
found_count += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
test
|
||||||
|
hackers_bench
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
CC = cc
|
||||||
|
CFLAGS = -O2 -std=c11 -D_POSIX_C_SOURCE=199309L -I. -I/usr/include -Wall -Wextra
|
||||||
|
LDFLAGS = -lupb -lutf8_range
|
||||||
|
|
||||||
|
SRCS = hackers_bench.c hackers.upb.c hackers.upb_minitable.c
|
||||||
|
TARGET = hackers_bench
|
||||||
|
|
||||||
|
.PHONY: all clean regen
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(SRCS)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||||
|
|
||||||
|
# Re-generate upb files from proto/hackers.proto
|
||||||
|
regen:
|
||||||
|
protoc -I ../proto ../proto/hackers.proto \
|
||||||
|
--upb_out=. \
|
||||||
|
--upb_minitable_out=.
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
|||||||
|
/* This file was generated by upb_generator from the input file:
|
||||||
|
*
|
||||||
|
* hackers.proto
|
||||||
|
*
|
||||||
|
* Do not edit -- your changes will be discarded when the file is
|
||||||
|
* regenerated.
|
||||||
|
* NO CHECKED-IN PROTOBUF GENCODE */
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include "upb/generated_code_support.h"
|
||||||
|
#include "hackers.upb_minitable.h"
|
||||||
|
|
||||||
|
// Must be last.
|
||||||
|
#include "upb/port/def.inc"
|
||||||
|
|
||||||
|
extern const struct upb_MiniTable UPB_PRIVATE(_kUpb_MiniTable_StaticallyTreeShaken);
|
||||||
|
static const upb_MiniTableField Tool__fields[5] = {
|
||||||
|
{1, 16, 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{2, UPB_SIZE(24, 32), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{3, UPB_SIZE(32, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{4, 8, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{5, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable Tool_msg_init = {
|
||||||
|
NULL,
|
||||||
|
&Tool__fields[0],
|
||||||
|
UPB_SIZE(40, 64), 5, kUpb_ExtMode_NonExtendable, 5, UPB_FASTTABLE_MASK(255), 0,
|
||||||
|
#ifdef UPB_TRACING_ENABLED
|
||||||
|
"Tool",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable* Tool_msg_init_ptr = &Tool_msg_init;
|
||||||
|
static const upb_MiniTableField Connection__fields[5] = {
|
||||||
|
{1, 16, 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{2, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{3, 8, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{4, UPB_SIZE(32, 48), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{5, UPB_SIZE(24, 32), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable Connection_msg_init = {
|
||||||
|
NULL,
|
||||||
|
&Connection__fields[0],
|
||||||
|
UPB_SIZE(40, 56), 5, kUpb_ExtMode_NonExtendable, 5, UPB_FASTTABLE_MASK(255), 0,
|
||||||
|
#ifdef UPB_TRACING_ENABLED
|
||||||
|
"Connection",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable* Connection_msg_init_ptr = &Connection_msg_init;
|
||||||
|
static const upb_MiniTableSubInternal Hacker__submsgs[2] = {
|
||||||
|
{.UPB_PRIVATE(submsg) = &Tool_msg_init_ptr},
|
||||||
|
{.UPB_PRIVATE(submsg) = &Connection_msg_init_ptr},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const upb_MiniTableField Hacker__fields[9] = {
|
||||||
|
{1, UPB_SIZE(32, 24), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{2, 40, 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{3, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{4, 16, 0, kUpb_NoSub, 2, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{5, 9, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{6, UPB_SIZE(48, 56), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{7, UPB_SIZE(20, 64), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
{8, UPB_SIZE(24, 72), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
{9, UPB_SIZE(28, 80), 64, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable Hacker_msg_init = {
|
||||||
|
&Hacker__submsgs[0],
|
||||||
|
&Hacker__fields[0],
|
||||||
|
UPB_SIZE(56, 88), 9, kUpb_ExtMode_NonExtendable, 9, UPB_FASTTABLE_MASK(56), 0,
|
||||||
|
#ifdef UPB_TRACING_ENABLED
|
||||||
|
"Hacker",
|
||||||
|
#endif
|
||||||
|
UPB_FASTTABLE_INIT({
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
{0x001000003f000025, &upb_DecodeFast_Fixed32_Scalar_Tag1Byte},
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable* Hacker_msg_init_ptr = &Hacker_msg_init;
|
||||||
|
static const upb_MiniTableField Worm__fields[6] = {
|
||||||
|
{1, UPB_SIZE(20, 16), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{2, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{3, UPB_SIZE(40, 48), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{4, UPB_SIZE(28, 32), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{5, 8, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{6, UPB_SIZE(16, 56), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable Worm_msg_init = {
|
||||||
|
NULL,
|
||||||
|
&Worm__fields[0],
|
||||||
|
UPB_SIZE(48, 64), 6, kUpb_ExtMode_NonExtendable, 6, UPB_FASTTABLE_MASK(255), 0,
|
||||||
|
#ifdef UPB_TRACING_ENABLED
|
||||||
|
"Worm",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable* Worm_msg_init_ptr = &Worm_msg_init;
|
||||||
|
static const upb_MiniTableSubInternal Operation__submsgs[2] = {
|
||||||
|
{.UPB_PRIVATE(submsg) = &Hacker_msg_init_ptr},
|
||||||
|
{.UPB_PRIVATE(submsg) = &Worm_msg_init_ptr},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const upb_MiniTableField Operation__fields[9] = {
|
||||||
|
{1, UPB_SIZE(28, 16), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{2, UPB_SIZE(36, 32), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{3, UPB_SIZE(56, 64), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{4, 9, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||||
|
{5, UPB_SIZE(44, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{6, UPB_SIZE(12, 72), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
{7, UPB_SIZE(16, 80), 64, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
{8, UPB_SIZE(20, 88), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
{9, UPB_SIZE(24, 12), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable Operation_msg_init = {
|
||||||
|
&Operation__submsgs[0],
|
||||||
|
&Operation__fields[0],
|
||||||
|
UPB_SIZE(64, 96), 9, kUpb_ExtMode_NonExtendable, 9, UPB_FASTTABLE_MASK(255), 0,
|
||||||
|
#ifdef UPB_TRACING_ENABLED
|
||||||
|
"Operation",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable* Operation_msg_init_ptr = &Operation_msg_init;
|
||||||
|
static const upb_MiniTableSubInternal Campaign__submsgs[1] = {
|
||||||
|
{.UPB_PRIVATE(submsg) = &Operation_msg_init_ptr},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const upb_MiniTableField Campaign__fields[3] = {
|
||||||
|
{1, UPB_SIZE(12, 8), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||||
|
{2, UPB_SIZE(8, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||||
|
{3, UPB_SIZE(24, 32), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable Campaign_msg_init = {
|
||||||
|
&Campaign__submsgs[0],
|
||||||
|
&Campaign__fields[0],
|
||||||
|
UPB_SIZE(32, 40), 3, kUpb_ExtMode_NonExtendable, 3, UPB_FASTTABLE_MASK(255), 0,
|
||||||
|
#ifdef UPB_TRACING_ENABLED
|
||||||
|
"Campaign",
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTable* Campaign_msg_init_ptr = &Campaign_msg_init;
|
||||||
|
static const upb_MiniTable *messages_layout[6] = {
|
||||||
|
&Tool_msg_init,
|
||||||
|
&Connection_msg_init,
|
||||||
|
&Hacker_msg_init,
|
||||||
|
&Worm_msg_init,
|
||||||
|
&Operation_msg_init,
|
||||||
|
&Campaign_msg_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
const upb_MiniTableFile hackers_proto_upb_file_layout = {
|
||||||
|
messages_layout,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
6,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "upb/port/undef.inc"
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/* This file was generated by upb_generator from the input file:
|
||||||
|
*
|
||||||
|
* hackers.proto
|
||||||
|
*
|
||||||
|
* Do not edit -- your changes will be discarded when the file is
|
||||||
|
* regenerated.
|
||||||
|
* NO CHECKED-IN PROTOBUF GENCODE */
|
||||||
|
|
||||||
|
#ifndef HACKERS_PROTO_UPB_H__UPB_MINITABLE_H_
|
||||||
|
#define HACKERS_PROTO_UPB_H__UPB_MINITABLE_H_
|
||||||
|
|
||||||
|
#include "upb/generated_code_support.h"
|
||||||
|
|
||||||
|
// Must be last.
|
||||||
|
#include "upb/port/def.inc"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern const upb_MiniTable Tool_msg_init;
|
||||||
|
extern const upb_MiniTable* Tool_msg_init_ptr;
|
||||||
|
extern const upb_MiniTable Connection_msg_init;
|
||||||
|
extern const upb_MiniTable* Connection_msg_init_ptr;
|
||||||
|
extern const upb_MiniTable Hacker_msg_init;
|
||||||
|
extern const upb_MiniTable* Hacker_msg_init_ptr;
|
||||||
|
extern const upb_MiniTable Worm_msg_init;
|
||||||
|
extern const upb_MiniTable* Worm_msg_init_ptr;
|
||||||
|
extern const upb_MiniTable Operation_msg_init;
|
||||||
|
extern const upb_MiniTable* Operation_msg_init_ptr;
|
||||||
|
extern const upb_MiniTable Campaign_msg_init;
|
||||||
|
extern const upb_MiniTable* Campaign_msg_init_ptr;
|
||||||
|
|
||||||
|
extern const upb_MiniTableFile hackers_proto_upb_file_layout;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} /* extern "C" */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "upb/port/undef.inc"
|
||||||
|
|
||||||
|
#endif /* HACKERS_PROTO_UPB_H__UPB_MINITABLE_H_ */
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* hackers_bench.c — C/upb benchmark mirroring benches/hackers_bench.rs
|
||||||
|
*
|
||||||
|
* Proto: proto/hackers.proto
|
||||||
|
* Generated files: hackers.upb.h / .c, hackers.upb_minitable.h / .c
|
||||||
|
*
|
||||||
|
* Build: make
|
||||||
|
* Run: ./hackers_bench
|
||||||
|
*
|
||||||
|
* Data files are read from ../data/bench/<name>.pb — the same files
|
||||||
|
* produced by `cargo run --release --bin gen_bench_data -- --preset <name>`.
|
||||||
|
*
|
||||||
|
* The four benchmark groups match the Rust/criterion groups exactly:
|
||||||
|
*
|
||||||
|
* shallow_parse — Campaign_parse() + Arena_Free() per iteration.
|
||||||
|
* upb fully decodes the message; roto merely scans
|
||||||
|
* for field offsets. This is the most important
|
||||||
|
* comparison: total cost to "be ready to read".
|
||||||
|
*
|
||||||
|
* deep_parse — parse + walk Campaign → Operations → every Hacker,
|
||||||
|
* touching each Hacker's handle field.
|
||||||
|
*
|
||||||
|
* field_access — message pre-parsed once outside the loop; each
|
||||||
|
* micro-benchmark times a single field read.
|
||||||
|
* upb: direct struct lookup. roto: decode at offset.
|
||||||
|
*
|
||||||
|
* iterate — count_operations: parse + count top-level repeated.
|
||||||
|
* count_all_crew: parse + count nested repeated.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "hackers.upb.h"
|
||||||
|
#include "hackers.upb_minitable.h"
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* Timing
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
static uint64_t now_ns(void) {
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* Black-box sink — prevents the compiler from optimising away benchmark work.
|
||||||
|
* We write the result of every meaningful computation here.
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
static volatile uintptr_t g_sink;
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* File I/O
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t len;
|
||||||
|
char path[256];
|
||||||
|
} BenchData;
|
||||||
|
|
||||||
|
static bool load_bench_data(BenchData *out, const char *name) {
|
||||||
|
snprintf(out->path, sizeof(out->path), "../data/bench/%s.pb", name);
|
||||||
|
FILE *f = fopen(out->path, "rb");
|
||||||
|
if (!f) {
|
||||||
|
printf("[skip] %s not found — "
|
||||||
|
"run `cargo run --release --bin gen_bench_data -- --preset %s` first\n",
|
||||||
|
out->path, name);
|
||||||
|
out->data = NULL;
|
||||||
|
out->len = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
out->len = (size_t)ftell(f);
|
||||||
|
rewind(f);
|
||||||
|
out->data = malloc(out->len);
|
||||||
|
if (!out->data) { fclose(f); return false; }
|
||||||
|
fread(out->data, 1, out->len, f);
|
||||||
|
fclose(f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_bench_data(BenchData *d) {
|
||||||
|
free(d->data);
|
||||||
|
d->data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* Benchmark runner
|
||||||
|
*
|
||||||
|
* Finds a batch size such that one batch takes ≥1 ms, then runs batches
|
||||||
|
* until at least BENCH_MIN_SECS of wall time has elapsed. Reports the
|
||||||
|
* mean ns/iter and, if bytes > 0, the MB/s throughput.
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
#define BENCH_MIN_SECS 0.5
|
||||||
|
|
||||||
|
typedef void (*bench_fn)(void *state);
|
||||||
|
|
||||||
|
static void run_bench(bench_fn fn, void *state, size_t bytes, const char *label) {
|
||||||
|
/* warmup */
|
||||||
|
for (int i = 0; i < 5; i++) fn(state);
|
||||||
|
|
||||||
|
/* calibrate: find batch size so one batch ≥ 1 ms */
|
||||||
|
uint64_t batch = 1;
|
||||||
|
while (batch < 10000000ULL) {
|
||||||
|
uint64_t t0 = now_ns();
|
||||||
|
for (uint64_t i = 0; i < batch; i++) fn(state);
|
||||||
|
if (now_ns() - t0 >= 1000000ULL) break; /* 1 ms */
|
||||||
|
batch *= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* measure */
|
||||||
|
uint64_t target_ns = (uint64_t)(BENCH_MIN_SECS * 1e9);
|
||||||
|
uint64_t total_ns = 0;
|
||||||
|
uint64_t total_its = 0;
|
||||||
|
while (total_ns < target_ns) {
|
||||||
|
uint64_t t0 = now_ns();
|
||||||
|
for (uint64_t i = 0; i < batch; i++) fn(state);
|
||||||
|
total_ns += now_ns() - t0;
|
||||||
|
total_its += batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ns_per_iter = (double)total_ns / (double)total_its;
|
||||||
|
if (bytes > 0) {
|
||||||
|
double mb_per_sec = (double)bytes / ns_per_iter * 1000.0;
|
||||||
|
printf(" %-46s %9.2f ns/iter %8.2f MB/s\n",
|
||||||
|
label, ns_per_iter, mb_per_sec);
|
||||||
|
} else {
|
||||||
|
printf(" %-46s %9.2f ns/iter\n", label, ns_per_iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* shallow_parse — Campaign_parse() + upb_Arena_Free() per iteration
|
||||||
|
*
|
||||||
|
* Measures the full cost of becoming "ready to access any field", matching
|
||||||
|
* the Rust `Campaign::new()` benchmark. upb fully decodes; roto only scans.
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
static void fn_shallow_parse(void *state) {
|
||||||
|
BenchData *d = state;
|
||||||
|
upb_Arena *arena = upb_Arena_New();
|
||||||
|
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||||
|
g_sink = (uintptr_t)c;
|
||||||
|
upb_Arena_Free(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bench_shallow_parse(void) {
|
||||||
|
const char *sizes[] = {"tiny", "small", "medium", "large", NULL};
|
||||||
|
printf("\n=== shallow_parse ===\n");
|
||||||
|
for (int i = 0; sizes[i]; i++) {
|
||||||
|
BenchData d;
|
||||||
|
if (!load_bench_data(&d, sizes[i])) continue;
|
||||||
|
char label[80];
|
||||||
|
snprintf(label, sizeof(label), "Campaign_parse/%s [%zu B]", sizes[i], d.len);
|
||||||
|
run_bench(fn_shallow_parse, &d, d.len, label);
|
||||||
|
free_bench_data(&d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* deep_parse — parse + walk Campaign → Operations → Hackers
|
||||||
|
*
|
||||||
|
* After Campaign_parse(), upb has already decoded everything. The "deep"
|
||||||
|
* walk is pointer-chasing through the decoded tree. In roto each level
|
||||||
|
* calls ::new(), paying another linear scan over that sub-message's bytes.
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
static void fn_deep_parse(void *state) {
|
||||||
|
BenchData *d = state;
|
||||||
|
upb_Arena *arena = upb_Arena_New();
|
||||||
|
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||||
|
|
||||||
|
size_t n_ops;
|
||||||
|
const Operation * const *ops = Campaign_operations(c, &n_ops);
|
||||||
|
size_t hacker_count = 0;
|
||||||
|
for (size_t i = 0; i < n_ops; i++) {
|
||||||
|
size_t n_crew;
|
||||||
|
const Hacker * const *crew = Operation_crew(ops[i], &n_crew);
|
||||||
|
for (size_t j = 0; j < n_crew; j++) {
|
||||||
|
upb_StringView handle = Hacker_handle(crew[j]);
|
||||||
|
g_sink = (uintptr_t)handle.data;
|
||||||
|
hacker_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_sink = hacker_count;
|
||||||
|
upb_Arena_Free(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bench_deep_parse(void) {
|
||||||
|
const char *sizes[] = {"tiny", "small", "medium", NULL};
|
||||||
|
printf("\n=== deep_parse ===\n");
|
||||||
|
for (int i = 0; sizes[i]; i++) {
|
||||||
|
BenchData d;
|
||||||
|
if (!load_bench_data(&d, sizes[i])) continue;
|
||||||
|
char label[80];
|
||||||
|
snprintf(label, sizeof(label), "Campaign+Ops+Hackers/%s [%zu B]", sizes[i], d.len);
|
||||||
|
run_bench(fn_deep_parse, &d, d.len, label);
|
||||||
|
free_bench_data(&d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* field_access — individual field reads on a pre-parsed message
|
||||||
|
*
|
||||||
|
* Parse once outside the loop; each micro-benchmark measures the accessor
|
||||||
|
* call itself. upb: a struct-field read with a MiniTable lookup.
|
||||||
|
* roto: decode the value at a pre-recorded byte offset.
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
upb_Arena *arena;
|
||||||
|
Campaign *campaign;
|
||||||
|
Operation *op;
|
||||||
|
Hacker *hacker;
|
||||||
|
Worm *worm;
|
||||||
|
} FieldState;
|
||||||
|
|
||||||
|
static void fn_field_campaign_name(void *s) {
|
||||||
|
upb_StringView v = Campaign_name(((FieldState *)s)->campaign);
|
||||||
|
g_sink = (uintptr_t)v.data;
|
||||||
|
}
|
||||||
|
static void fn_field_total_bytes_stolen(void *s) {
|
||||||
|
g_sink = (uintptr_t)(uint64_t)Campaign_total_bytes_stolen(((FieldState *)s)->campaign);
|
||||||
|
}
|
||||||
|
static void fn_field_op_codename(void *s) {
|
||||||
|
upb_StringView v = Operation_codename(((FieldState *)s)->op);
|
||||||
|
g_sink = (uintptr_t)v.data;
|
||||||
|
}
|
||||||
|
static void fn_field_op_timestamp(void *s) {
|
||||||
|
g_sink = (uintptr_t)(uint64_t)Operation_timestamp(((FieldState *)s)->op);
|
||||||
|
}
|
||||||
|
static void fn_field_op_successful(void *s) {
|
||||||
|
g_sink = (uintptr_t)Operation_successful(((FieldState *)s)->op);
|
||||||
|
}
|
||||||
|
static void fn_field_hacker_handle(void *s) {
|
||||||
|
upb_StringView v = Hacker_handle(((FieldState *)s)->hacker);
|
||||||
|
g_sink = (uintptr_t)v.data;
|
||||||
|
}
|
||||||
|
static void fn_field_hacker_skill_level(void *s) {
|
||||||
|
/* store float bits to avoid FPU → int conversion costs */
|
||||||
|
float f = Hacker_skill_level(((FieldState *)s)->hacker);
|
||||||
|
uint32_t bits; memcpy(&bits, &f, 4);
|
||||||
|
g_sink = bits;
|
||||||
|
}
|
||||||
|
static void fn_field_hacker_is_elite(void *s) {
|
||||||
|
g_sink = (uintptr_t)Hacker_is_elite(((FieldState *)s)->hacker);
|
||||||
|
}
|
||||||
|
static void fn_field_worm_polymorphic(void *s) {
|
||||||
|
g_sink = (uintptr_t)Worm_polymorphic(((FieldState *)s)->worm);
|
||||||
|
}
|
||||||
|
static void fn_field_worm_payload(void *s) {
|
||||||
|
upb_StringView v = Worm_payload(((FieldState *)s)->worm);
|
||||||
|
g_sink = (uintptr_t)v.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bench_field_access(void) {
|
||||||
|
BenchData d;
|
||||||
|
if (!load_bench_data(&d, "small")) return;
|
||||||
|
|
||||||
|
upb_Arena *arena = upb_Arena_New();
|
||||||
|
Campaign *campaign = Campaign_parse((const char *)d.data, d.len, arena);
|
||||||
|
if (!campaign) { fprintf(stderr, "parse failed\n"); return; }
|
||||||
|
|
||||||
|
size_t n_ops;
|
||||||
|
const Operation * const *ops = Campaign_operations(campaign, &n_ops);
|
||||||
|
if (n_ops == 0) { fprintf(stderr, "no operations\n"); return; }
|
||||||
|
Operation *op = (Operation *)ops[0]; /* cast away const for state */
|
||||||
|
|
||||||
|
size_t n_crew;
|
||||||
|
const Hacker * const *crew = Operation_crew(op, &n_crew);
|
||||||
|
if (n_crew == 0) { fprintf(stderr, "no crew\n"); return; }
|
||||||
|
Hacker *hacker = (Hacker *)crew[0];
|
||||||
|
|
||||||
|
const Worm *worm = Operation_worm(op);
|
||||||
|
if (!worm) { fprintf(stderr, "no worm\n"); return; }
|
||||||
|
|
||||||
|
FieldState state = {
|
||||||
|
.arena = arena,
|
||||||
|
.campaign = campaign,
|
||||||
|
.op = op,
|
||||||
|
.hacker = hacker,
|
||||||
|
.worm = (Worm *)worm,
|
||||||
|
};
|
||||||
|
|
||||||
|
printf("\n=== field_access ===\n");
|
||||||
|
run_bench(fn_field_campaign_name, &state, 0, "campaign::name");
|
||||||
|
run_bench(fn_field_total_bytes_stolen, &state, 0, "campaign::total_bytes_stolen");
|
||||||
|
run_bench(fn_field_op_codename, &state, 0, "operation::codename");
|
||||||
|
run_bench(fn_field_op_timestamp, &state, 0, "operation::timestamp");
|
||||||
|
run_bench(fn_field_op_successful, &state, 0, "operation::successful");
|
||||||
|
run_bench(fn_field_hacker_handle, &state, 0, "hacker::handle");
|
||||||
|
run_bench(fn_field_hacker_skill_level, &state, 0, "hacker::skill_level (f32)");
|
||||||
|
run_bench(fn_field_hacker_is_elite, &state, 0, "hacker::is_elite (bool)");
|
||||||
|
run_bench(fn_field_worm_polymorphic, &state, 0, "worm::polymorphic (bool)");
|
||||||
|
run_bench(fn_field_worm_payload, &state, 0, "worm::payload (bytes)");
|
||||||
|
|
||||||
|
upb_Arena_Free(arena);
|
||||||
|
free_bench_data(&d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* iterate — count repeated fields at different depths
|
||||||
|
*
|
||||||
|
* count_operations: after parsing, Campaign_operations() returns pointer+count
|
||||||
|
* in O(1) — upb already decoded the array.
|
||||||
|
* roto's Campaign::new() scan IS the counting work.
|
||||||
|
*
|
||||||
|
* count_all_crew: parse + walk ops + sum crew sizes.
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
static void fn_count_operations(void *state) {
|
||||||
|
BenchData *d = state;
|
||||||
|
upb_Arena *arena = upb_Arena_New();
|
||||||
|
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||||
|
size_t n;
|
||||||
|
Campaign_operations(c, &n);
|
||||||
|
g_sink = n;
|
||||||
|
upb_Arena_Free(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fn_count_all_crew(void *state) {
|
||||||
|
BenchData *d = state;
|
||||||
|
upb_Arena *arena = upb_Arena_New();
|
||||||
|
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||||
|
size_t n_ops;
|
||||||
|
const Operation * const *ops = Campaign_operations(c, &n_ops);
|
||||||
|
size_t total = 0;
|
||||||
|
for (size_t i = 0; i < n_ops; i++) {
|
||||||
|
size_t n_crew;
|
||||||
|
Operation_crew(ops[i], &n_crew);
|
||||||
|
total += n_crew;
|
||||||
|
}
|
||||||
|
g_sink = total;
|
||||||
|
upb_Arena_Free(arena);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bench_iterate(void) {
|
||||||
|
const char *sizes[] = {"tiny", "small", "medium", NULL};
|
||||||
|
printf("\n=== iterate ===\n");
|
||||||
|
for (int i = 0; sizes[i]; i++) {
|
||||||
|
BenchData d;
|
||||||
|
if (!load_bench_data(&d, sizes[i])) continue;
|
||||||
|
|
||||||
|
char label[80];
|
||||||
|
|
||||||
|
snprintf(label, sizeof(label), "count_operations/%s [%zu B]", sizes[i], d.len);
|
||||||
|
run_bench(fn_count_operations, &d, d.len, label);
|
||||||
|
|
||||||
|
snprintf(label, sizeof(label), "count_all_crew/%s [%zu B]", sizes[i], d.len);
|
||||||
|
run_bench(fn_count_all_crew, &d, d.len, label);
|
||||||
|
|
||||||
|
free_bench_data(&d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* main
|
||||||
|
* ========================================================================== */
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
printf("hackers_bench (upb / protobuf %s)\n", "33.1");
|
||||||
|
printf("Data files: ../data/bench/<name>.pb\n");
|
||||||
|
printf("Run `cargo run --release --bin gen_bench_data -- --preset <name>` to generate.\n");
|
||||||
|
|
||||||
|
bench_shallow_parse();
|
||||||
|
bench_deep_parse();
|
||||||
|
bench_field_access();
|
||||||
|
bench_iterate();
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "hackers.upb.h"
|
||||||
|
#include "hackers.upb_minitable.h"
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
upb_Arena *arena = upb_Arena_New();
|
||||||
|
Campaign *c = Campaign_new(arena);
|
||||||
|
(void)c;
|
||||||
|
printf("name: %.*s\n", (int)Campaign_name(c).size, Campaign_name(c).data);
|
||||||
|
upb_Arena_Free(arena);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user