diff --git a/Cargo.lock b/Cargo.lock index 340a0bd..74c7017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aead" version = "0.4.3" @@ -39,25 +54,49 @@ dependencies = [ ] [[package]] -name = "async-recursion" +name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b015a331cc64ebd1774ba119538573603427eaace0a1950c423ab971f903796" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] name = "async-trait" -version = "0.1.64" +version = "0.1.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] @@ -66,12 +105,33 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bitflags" version = "1.3.2" @@ -80,28 +140,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "generic-array", ] -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" @@ -115,6 +165,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" @@ -141,22 +197,27 @@ dependencies = [ ] [[package]] -name = "cpufeatures" -version = "0.2.5" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" dependencies = [ "libc", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "crc32fast" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "generic-array", - "typenum", + "cfg-if", ] [[package]] @@ -169,15 +230,33 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.6" +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "block-buffer", - "crypto-common", + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encoding" version = "0.2.33" @@ -242,6 +321,33 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "fastrand" version = "1.9.0" @@ -251,6 +357,23 @@ dependencies = [ "instant", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "libz-sys", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -259,18 +382,33 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] -name = "futures-channel" -version = "0.3.26" +name = "futures" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -278,31 +416,63 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] [[package]] name = "futures-sink" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.26" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -310,9 +480,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -320,9 +490,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", @@ -340,22 +510,43 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.15" +name = "gimli" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" dependencies = [ - "bytes", - "fnv", + "futures-channel", "futures-core", "futures-sink", - "futures-util", + "gloo-utils", "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", ] [[package]] @@ -365,38 +556,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "headers" -version = "0.3.8" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" -dependencies = [ - "base64", - "bitflags", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" [[package]] name = "hex" @@ -421,58 +590,17 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -480,9 +608,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", @@ -498,33 +626,115 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.5" +name = "io-lifetimes" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] -name = "libc" -version = "0.2.139" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libz-sys" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" -version = "0.4.17" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "logos" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" dependencies = [ - "cfg-if", + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 2.0.25", +] + +[[package]] +name = "logos-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +dependencies = [ + "logos-codegen", ] [[package]] @@ -534,56 +744,83 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "mime" -version = "0.3.16" +name = "miette" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "a236ff270093b0b67451bc50a509bd1bad302cb1d3c7d37d5efe931238581fa9" +dependencies = [ + "miette-derive", + "once_cell", + "thiserror", + "unicode-width", +] [[package]] -name = "mime_guess" -version = "2.0.4" +name = "miette-derive" +version = "5.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "4901771e1d44ddb37964565c654a3223ba41a594d02b8da471cc4464912b5cfa" dependencies = [ - "mime", - "unicase", + "proc-macro2", + "quote", + "syn 2.0.25", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] -name = "multipart" -version = "0.18.0" +name = "multimap" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "nrpc" +version = "0.10.0" dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand", - "safemem", - "tempfile", - "twoway", + "async-trait", + "bytes", + "futures", + "prost", +] + +[[package]] +name = "nrpc-build" +version = "0.10.0" +dependencies = [ + "nrpc", + "prettyplease 0.2.10", + "proc-macro2", + "prost-build", + "prost-types", + "protox", + "quote", + "syn 2.0.25", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", @@ -596,10 +833,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2b2cbbfd8defa51ff24450a61d73b3ff3e158484ddd274a883e886e6fbaa78" [[package]] -name = "once_cell" -version = "1.17.1" +name = "object" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -608,36 +854,69 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "parking_lot" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" [[package]] name = "pin-utils" @@ -645,6 +924,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "polyval" version = "0.5.3" @@ -664,25 +949,133 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] -name = "proc-macro2" -version = "1.0.51" +name = "prettyplease" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "prettyplease" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92139198957b410250d43fad93e630d956499a625c527eda65175c8680f83387" +dependencies = [ + "proc-macro2", + "syn 2.0.25", +] + +[[package]] +name = "proc-macro2" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" dependencies = [ "unicode-ident", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "prost" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease 0.1.25", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-reflect" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000e1e05ebf7b26e1eba298e66fe4eee6eb19c567d0ffb35e0dd34231cdac4c8" +dependencies = [ + "logos", + "miette", + "once_cell", + "prost", + "prost-types", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + +[[package]] +name = "protox" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24022a7eb88547eaba87a1db7954c9c4cb4a143565c4e8f2b7f3e76eed63db2d" +dependencies = [ + "bytes", + "miette", + "prost", + "prost-reflect", + "prost-types", + "protox-parse", + "thiserror", +] + +[[package]] +name = "protox-parse" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712a2a651fa4466e67df6c967df5d7fc6cbffac89afc7b834f97ec49846c9c11" +dependencies = [ + "logos", + "miette", + "prost-types", + "thiserror", +] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ "proc-macro2", ] @@ -718,43 +1111,143 @@ dependencies = [ ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "ratchet_core" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "faed301a9f297e8cd3617a2bc79ed17eefa88d5873ed08517c96628b48d1f386" +dependencies = [ + "base64", + "bitflags", + "bytes", + "derive_more", + "either", + "fnv", + "http", + "httparse", + "log", + "rand", + "ratchet_ext", + "sha-1", + "thiserror", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "ratchet_deflate" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77238362df52f64482e0bd1c413d2d3d0e20052056ba4d88918ef2e962c86f11" +dependencies = [ + "bytes", + "flate2", + "http", + "log", + "ratchet_ext", + "thiserror", +] + +[[package]] +name = "ratchet_ext" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f5bf3bd015a94b77730229e895e03af945627984ee5c4f95d40fd9227ea36b" +dependencies = [ + "bytes", + "http", + "httparse", +] + +[[package]] +name = "ratchet_rs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d326d7cd4227a7f58b36c1efb16b348f7e2e43e1d1ef032e9b094ff6cec583" +dependencies = [ + "ratchet_core", + "ratchet_deflate", + "ratchet_ext", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ - "winapi", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.7.3", ] [[package]] -name = "rustls-pemfile" -version = "0.2.1" +name = "regex-automata" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf" dependencies = [ - "base64", + "aho-corasick", + "memchr", + "regex-syntax 0.7.3", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", ] [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" [[package]] name = "scoped-tls" @@ -763,70 +1256,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "serde" -version = "1.0.152" +name = "scopeguard" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" [[package]] name = "serde_json" -version = "1.0.93" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" dependencies = [ "itoa", "ryu", "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha-1" -version = "0.10.1" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ + "block-buffer", "cfg-if", "cpufeatures", "digest", + "opaque-debug", ] [[package]] -name = "sha1" -version = "0.10.5" +name = "signal-hook-registry" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] -name = "socket2" -version = "0.4.7" +name = "smallvec" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -840,9 +1339,20 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" dependencies = [ "proc-macro2", "quote", @@ -851,36 +1361,36 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.3.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys", ] [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", ] [[package]] @@ -900,117 +1410,47 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.25.0" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", - "windows-sys 0.42.0", + "tokio-macros", + "windows-sys", ] [[package]] -name = "tokio-stream" -version = "0.1.11" +name = "tokio-macros" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite", + "proc-macro2", + "quote", + "syn 2.0.25", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes", "futures-core", "futures-sink", + "log", "pin-project-lite", "tokio", - "tracing", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "tungstenite" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" -dependencies = [ - "base64", - "byteorder", - "bytes", - "http", - "httparse", - "log", - "rand", - "sha-1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", ] [[package]] @@ -1019,26 +1459,17 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" [[package]] name = "unicode-normalization" @@ -1049,6 +1480,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "universal-hash" version = "0.4.1" @@ -1061,38 +1498,51 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] -[[package]] -name = "usdpl" -version = "0.10.0" - [[package]] name = "usdpl-back" -version = "0.10.1" +version = "0.11.0" dependencies = [ + "async-lock", "async-recursion", "async-trait", "bytes", "gettext-ng", "hex", "log", + "nrpc", "obfstr", + "prost", + "ratchet_rs", "tokio", + "usdpl-build", "usdpl-core", - "warp", +] + +[[package]] +name = "usdpl-build" +version = "0.11.0" +dependencies = [ + "nrpc-build", + "prettyplease 0.2.10", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn 2.0.25", ] [[package]] name = "usdpl-core" -version = "0.10.0" +version = "0.11.0" dependencies = [ "aes-gcm-siv", "base64", @@ -1101,12 +1551,18 @@ dependencies = [ [[package]] name = "usdpl-front" -version = "0.10.1" +version = "0.11.0" dependencies = [ "console_error_panic_hook", + "futures", + "futures-channel", + "gloo-net", "hex", "js-sys", + "log", + "nrpc", "obfstr", + "prost", "usdpl-core", "wasm-bindgen", "wasm-bindgen-futures", @@ -1115,10 +1571,10 @@ dependencies = [ ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" @@ -1126,47 +1582,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "warp" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http", - "hyper", - "log", - "mime", - "mime_guess", - "multipart", - "percent-encoding", - "pin-project", - "rustls-pemfile", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-stream", - "tokio-tungstenite", - "tokio-util", - "tower-service", - "tracing", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1175,9 +1590,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1185,24 +1600,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.25", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -1212,9 +1627,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1222,28 +1637,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.25", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-bindgen-test" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" +checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" dependencies = [ "console_error_panic_hook", "js-sys", @@ -1255,9 +1670,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.34" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" +checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" dependencies = [ "proc-macro2", "quote", @@ -1265,14 +1680,25 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1297,33 +1723,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.1" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1336,45 +1747,45 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.42.1" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 7d15700..52e25b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,16 @@ -[package] -name = "usdpl" -version = "0.10.0" -authors = ["NGnius (Graham) "] -edition = "2021" -license = "GPL-3.0-only" -repository = "https://github.com/NGnius/usdpl-rs" -readme = "README.md" +[workspace] +members = [ + "usdpl-core", + "usdpl-front", + "usdpl-back", + "usdpl-build", +] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +exclude = [ + "templates/decky/backend" +] + +resolver = "2" [profile.release] # Tell `rustc` to optimize for small code size. @@ -16,14 +19,3 @@ debug = false strip = true lto = true codegen-units = 4 - -[workspace] -members = [ - "usdpl-core", - "usdpl-front", - "usdpl-back", -] - -exclude = [ - "templates/decky/backend" -] diff --git a/usdpl-back/Cargo.toml b/usdpl-back/Cargo.toml index 69807b4..67e03cd 100644 --- a/usdpl-back/Cargo.toml +++ b/usdpl-back/Cargo.toml @@ -1,29 +1,35 @@ [package] name = "usdpl-back" -version = "0.10.1" +version = "0.11.0" edition = "2021" +authors = ["NGnius "] license = "GPL-3.0-only" -repository = "https://github.com/NGnius/usdpl-rs" -readme = "README.md" +repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs" +readme = "../README.md" description = "Universal Steam Deck Plugin Library back-end" [features] -default = ["blocking", "translate"] +default = ["blocking"] decky = ["usdpl-core/decky"] -crankshaft = ["usdpl-core/crankshaft"] -blocking = ["tokio", "tokio/rt", "tokio/rt-multi-thread"] # synchronous API for async functionality, using tokio -encrypt = ["usdpl-core/encrypt", "obfstr", "hex"] -translate = ["usdpl-core/translate", "gettext-ng"] +blocking = [] # synchronous API for async functionality, using tokio +#encrypt = ["usdpl-core", "obfstr", "hex"] [dependencies] -usdpl-core = { version = "0.10", path = "../usdpl-core"} +usdpl-core = { version = "0.11", path = "../usdpl-core"} log = "0.4" +# gRPC/protobuf +nrpc = { version = "0.10", path = "../../nRPC/nrpc", default-features = false, features = [ "server-send" ] } +async-lock = "2.7" +prost = "0.11" + +# websocket framework +ratchet_rs = { version = "0.4", features = [ "deflate" ] } + # HTTP web framework -warp = { version = "0.3" } bytes = { version = "1.1" } -tokio = { version = "1", optional = true } +tokio = { version = "1", features = [ "full" ]} # this is why people don't like async async-trait = "0.1.57" @@ -34,4 +40,7 @@ obfstr = { version = "0.3", optional = true } hex = { version = "0.4", optional = true } # translations -gettext-ng = { version = "0.4.1", optional = true } +gettext-ng = { version = "0.4.1" } + +[build-dependencies] +usdpl-build = { version = "0.11", path = "../usdpl-build" } diff --git a/usdpl-back/build.rs b/usdpl-back/build.rs new file mode 100644 index 0000000..2e3439b --- /dev/null +++ b/usdpl-back/build.rs @@ -0,0 +1,6 @@ +fn main() { + usdpl_build::back::build_with_custom_builtins( + [].into_iter(), + [].into_iter(), + ) +} diff --git a/usdpl-back/src/api_any/dirs.rs b/usdpl-back/src/api_any/dirs.rs index 6ac1b5a..e35ba03 100644 --- a/usdpl-back/src/api_any/dirs.rs +++ b/usdpl-back/src/api_any/dirs.rs @@ -5,14 +5,10 @@ use std::process::Command; /// The home directory of the user currently running the Steam Deck UI (specifically: running gamescope). pub fn home() -> Option { - let who_out = Command::new("who") - .output().ok()?; + let who_out = Command::new("who").output().ok()?; let who_str = String::from_utf8_lossy(who_out.stdout.as_slice()); for login in who_str.split("\n") { - let username = login - .split(" ") - .next()? - .trim(); + let username = login.split(" ").next()?.trim(); let path = Path::new("/home").join(username); if path.is_dir() { return Some(path); diff --git a/usdpl-back/src/api_common/dirs.rs b/usdpl-back/src/api_common/dirs.rs index b07f347..c27e12a 100644 --- a/usdpl-back/src/api_common/dirs.rs +++ b/usdpl-back/src/api_common/dirs.rs @@ -6,15 +6,11 @@ use std::path::PathBuf; pub fn home() -> Option { #[cfg(not(any(feature = "decky", feature = "crankshaft")))] let result = crate::api_any::dirs::home(); - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - let result = None; // TODO #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] - let result = crate::api_decky::home().ok() - .map(|x| PathBuf::from(x) - .join("..") - .canonicalize() - .ok() - ).flatten(); + let result = crate::api_decky::home() + .ok() + .map(|x| PathBuf::from(x).join("..").canonicalize().ok()) + .flatten(); result } @@ -23,8 +19,6 @@ pub fn home() -> Option { pub fn plugin() -> Option { #[cfg(not(any(feature = "decky", feature = "crankshaft")))] let result = None; // TODO - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - let result = None; // TODO #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] let result = crate::api_decky::plugin_dir().ok().map(|x| x.into()); @@ -35,8 +29,6 @@ pub fn plugin() -> Option { pub fn log() -> Option { #[cfg(not(any(feature = "decky", feature = "crankshaft")))] let result = crate::api_any::dirs::log(); - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - let result = None; // TODO #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] let result = crate::api_decky::log_dir().ok().map(|x| x.into()); diff --git a/usdpl-back/src/api_common/files.rs b/usdpl-back/src/api_common/files.rs deleted file mode 100644 index 48b555f..0000000 --- a/usdpl-back/src/api_common/files.rs +++ /dev/null @@ -1,46 +0,0 @@ -//! Common low-level file operations -use std::fmt::Display; -use std::path::Path; -use std::fs::File; -use std::io::{Read, Write, self}; -use std::str::FromStr; - -/// Write something to a file. -/// Useful for kernel configuration files. -#[inline] -pub fn write_single, D: Display>(path: P, display: D) -> Result<(), io::Error> { - let mut file = File::create(path)?; - write!(file, "{}", display) -} - -/// read_single error -#[derive(Debug)] -pub enum ReadError { - /// IO Error - Io(io::Error), - /// String parsing error - Parse(E), -} - -impl std::fmt::Display for ReadError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Io(io) => write!(f, "io: {}", io), - Self::Parse(e) => write!(f, "parse: {}", e), - } - } -} - -impl std::error::Error for ReadError { - -} - -/// Read something from a file. -/// Useful for kernel configuration files. -#[inline] -pub fn read_single, D: FromStr, E>(path: P) -> Result> { - let mut file = File::open(path).map_err(ReadError::Io)?; - let mut string = String::new(); - file.read_to_string(&mut string).map_err(ReadError::Io)?; - string.trim().parse().map_err(ReadError::Parse) -} diff --git a/usdpl-back/src/api_common/mod.rs b/usdpl-back/src/api_common/mod.rs index 63763e5..16ec367 100644 --- a/usdpl-back/src/api_common/mod.rs +++ b/usdpl-back/src/api_common/mod.rs @@ -1,2 +1 @@ pub mod dirs; -pub mod files; diff --git a/usdpl-back/src/api_crankshaft/mod.rs b/usdpl-back/src/api_crankshaft/mod.rs deleted file mode 100644 index c040648..0000000 --- a/usdpl-back/src/api_crankshaft/mod.rs +++ /dev/null @@ -1 +0,0 @@ -compile_error!("Crankshaft unsupported (project no longer maintained)"); diff --git a/usdpl-back/src/callable.rs b/usdpl-back/src/callable.rs deleted file mode 100644 index 2a954c2..0000000 --- a/usdpl-back/src/callable.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::sync::{Arc, Mutex}; - -use usdpl_core::serdes::Primitive; - -/// A mutable function which can be called from the front-end (remotely) -pub trait MutCallable: Send + Sync { - /// Invoke the function - fn call(&mut self, params: Vec) -> Vec; -} - -impl) -> Vec) + Send + Sync> MutCallable for F { - fn call(&mut self, params: Vec) -> Vec { - (self)(params) - } -} - -/// A function which can be called from the front-end (remotely) -pub trait Callable: Send + Sync { - /// Invoke the function - fn call(&self, params: Vec) -> Vec; -} - -impl) -> Vec) + Send + Sync> Callable for F { - fn call(&self, params: Vec) -> Vec { - (self)(params) - } -} - -/// An async function which can be called from the front-end (remotely) -#[async_trait::async_trait] -pub trait AsyncCallable: Send + Sync { - /// Invoke the function - async fn call(&self, params: Vec) -> Vec; -} - -#[async_trait::async_trait] -impl) -> A) + Send + Sync, A: core::future::Future> + Send> AsyncCallable for F { - async fn call(&self, params: Vec) -> Vec { - (self)(params).await - } -} - -pub enum WrappedCallable { - Blocking(Arc>>), - Ref(Arc>), - Async(Arc>), -} - -impl WrappedCallable { - pub fn new_ref(callable: T) -> Self { - Self::Ref(Arc::new(Box::new(callable))) - } - - pub fn new_locking(callable: T) -> Self { - Self::Blocking(Arc::new(Mutex::new(Box::new(callable)))) - } - - pub fn new_async(callable: T) -> Self { - Self::Async(Arc::new(Box::new(callable))) - } -} - -impl Clone for WrappedCallable { - fn clone(&self) -> Self { - match self { - Self::Blocking(x) => Self::Blocking(x.clone()), - Self::Ref(x) => Self::Ref(x.clone()), - Self::Async(x) => Self::Async(x.clone()), - } - } -} - -#[async_trait::async_trait] -impl AsyncCallable for WrappedCallable { - async fn call(&self, params: Vec) -> Vec { - match self { - Self::Blocking(mut_callable) => { - mut_callable - .lock() - .expect("Failed to acquire mut_callable lock") - .call(params) - }, - Self::Ref(callable) => callable.call(params), - Self::Async(async_callable) => async_callable.call(params).await, - } - } -} diff --git a/usdpl-back/src/instance.rs b/usdpl-back/src/instance.rs deleted file mode 100644 index 7dd842e..0000000 --- a/usdpl-back/src/instance.rs +++ /dev/null @@ -1,262 +0,0 @@ -use std::collections::HashMap; -use std::sync::atomic::{AtomicU64, Ordering}; - -use warp::Filter; - -use usdpl_core::serdes::{Dumpable, Loadable}; -use usdpl_core::{socket, RemoteCallResponse}; - -use super::{Callable, MutCallable, AsyncCallable, WrappedCallable}; - -static LAST_ID: AtomicU64 = AtomicU64::new(0); -const MAX_ID_DIFFERENCE: u64 = 32; - -//type WrappedCallable = Arc>>; // thread-safe, cloneable Callable - -#[cfg(feature = "encrypt")] -const NONCE: [u8; socket::NONCE_SIZE] = [0u8; socket::NONCE_SIZE]; - -/// Back-end instance for interacting with the front-end -pub struct Instance { - calls: HashMap, - port: u16, - #[cfg(feature = "encrypt")] - encryption_key: Vec, -} - -impl Instance { - /// Initialise an instance of the back-end - #[inline] - pub fn new(port_usdpl: u16) -> Self { - Instance { - calls: HashMap::new(), - port: port_usdpl, - #[cfg(feature = "encrypt")] - encryption_key: hex::decode(obfstr::obfstr!(env!("USDPL_ENCRYPTION_KEY"))).unwrap(), - } - } - - /// Register a thread-safe function which can be invoked by the front-end - pub fn register, F: Callable + 'static>( - mut self, - name: S, - f: F, - ) -> Self { - self.calls - .insert(name.into(), WrappedCallable::new_ref(f)); - self - } - - /// Register a thread-unsafe function which can be invoked by the front-end - pub fn register_blocking, F: MutCallable + 'static>( - mut self, - name: S, - f: F, - ) -> Self { - self.calls - .insert(name.into(), WrappedCallable::new_locking(f)); - self - } - - /// Register a thread-unsafe function which can be invoked by the front-end - pub fn register_async, F: AsyncCallable + 'static>( - mut self, - name: S, - f: F, - ) -> Self { - self.calls - .insert(name.into(), WrappedCallable::new_async(f)); - self - } - - /// Run the web server instance forever, blocking this thread - #[cfg(feature = "blocking")] - pub fn run_blocking(&self) -> Result<(), ()> { - let result = self.serve_internal(); - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(result) - } - - /// Run the web server forever, asynchronously - pub async fn run(&self) -> Result<(), ()> { - self.serve_internal().await - } - - #[async_recursion::async_recursion] - async fn handle_call( - packet: socket::Packet, - handlers: &HashMap, - ) -> socket::Packet { - match packet { - socket::Packet::Call(call) => { - log::debug!("Got USDPL call {} (`{}`, params: {})", call.id, call.function, call.parameters.len()); - let last_id = LAST_ID.load(Ordering::SeqCst); - if last_id == 0 { - log::info!("Last id is 0, assuming resumed connection (overriding last id)"); - LAST_ID.store(call.id, Ordering::SeqCst); - } else if call.id < MAX_ID_DIFFERENCE { - log::info!("Call ID is low, assuming new connection (resetting last id)"); - LAST_ID.store(call.id, Ordering::SeqCst); - } else if call.id > last_id && call.id - last_id < MAX_ID_DIFFERENCE { - LAST_ID.store(call.id, Ordering::SeqCst); - } else if call.id < last_id && last_id - call.id < MAX_ID_DIFFERENCE { - // Allowed, but don't store new (lower) LAST_ID - } else { - #[cfg(not(debug_assertions))] - { - log::error!("Got USDPL call with strange ID! got:{} last id:{} (rejecting packet)", call.id, last_id); - return socket::Packet::Invalid - } - #[cfg(debug_assertions)] - log::warn!("Got USDPL call with strange ID! got:{} last id:{} (in release mode this packet will be rejected)", call.id, last_id); - } - //let handlers = CALLS.lock().expect("Failed to acquire CALLS lock"); - if let Some(target) = handlers.get(&call.function) { - let result = target.call(call.parameters).await; - socket::Packet::CallResponse(RemoteCallResponse { - id: call.id, - response: result, - }) - } else { - socket::Packet::Invalid - } - }, - socket::Packet::Many(packets) => { - let mut result = Vec::with_capacity(packets.len()); - for packet in packets { - result.push(Self::handle_call(packet, handlers).await); - } - socket::Packet::Many(result) - }, - #[cfg(feature = "translate")] - socket::Packet::Language(lang) => socket::Packet::Translations(get_all_translations(lang)), - _ => socket::Packet::Invalid, - } - } - - #[cfg(not(feature = "encrypt"))] - async fn process_body((data, handlers): (bytes::Bytes, HashMap)) -> impl warp::Reply { - let (packet, _) = match socket::Packet::load_base64(&data) { - Ok(x) => x, - Err(e) => { - return warp::reply::with_status( - warp::http::Response::builder() - .body(format!("Failed to load packet: {}", e)), - warp::http::StatusCode::from_u16(400).unwrap(), - ) - } - }; - //let mut buffer = [0u8; socket::PACKET_BUFFER_SIZE]; - let mut buffer = String::with_capacity(socket::PACKET_BUFFER_SIZE); - let response = Self::handle_call(packet, &handlers).await; - let _len = match response.dump_base64(&mut buffer) { - Ok(x) => x, - Err(e) => { - return warp::reply::with_status( - warp::http::Response::builder() - .body(format!("Failed to dump response packet: {}", e)), - warp::http::StatusCode::from_u16(500).unwrap(), - ) - } - }; - warp::reply::with_status( - warp::http::Response::builder().body(buffer), - warp::http::StatusCode::from_u16(200).unwrap(), - ) - } - - #[cfg(feature = "encrypt")] - async fn process_body((data, handlers, key): (bytes::Bytes, HashMap, Vec)) -> impl warp::Reply { - let (packet, _) = match socket::Packet::load_encrypted(&data, &key, &NONCE) { - Ok(x) => x, - Err(_) => { - return warp::reply::with_status( - warp::http::Response::builder() - .body("Failed to load packet".to_string()), - warp::http::StatusCode::from_u16(400).unwrap(), - ) - } - }; - let mut buffer = Vec::with_capacity(socket::PACKET_BUFFER_SIZE); - //buffer.extend(&[0u8; socket::PACKET_BUFFER_SIZE]); - let response = Self::handle_call(packet, &handlers).await; - let len = match response.dump_encrypted(&mut buffer, &key, &NONCE) { - Ok(x) => x, - Err(_) => { - return warp::reply::with_status( - warp::http::Response::builder() - .body("Failed to dump response packet".to_string()), - warp::http::StatusCode::from_u16(500).unwrap(), - ) - } - }; - buffer.truncate(len); - let string: String = String::from_utf8(buffer).unwrap().into(); - warp::reply::with_status( - warp::http::Response::builder().body(string), - warp::http::StatusCode::from_u16(200).unwrap(), - ) - } - - /// Receive and execute callbacks forever - async fn serve_internal(&self) -> Result<(), ()> { - let handlers = self.calls.clone(); - #[cfg(not(feature = "encrypt"))] - let input_mapper = move |data: bytes::Bytes| { (data, handlers.clone()) }; - #[cfg(feature = "encrypt")] - let key = self.encryption_key.clone(); - #[cfg(feature = "encrypt")] - let input_mapper = move |data: bytes::Bytes| { (data, handlers.clone(), key.clone()) }; - //self.calls = HashMap::new(); - let calls = warp::post() - .and(warp::path!("usdpl" / "call")) - .and(warp::body::content_length_limit( - (socket::PACKET_BUFFER_SIZE * 2) as _, - )) - .and(warp::body::bytes()) - .map(input_mapper) - .then(Self::process_body) - .map(|reply| warp::reply::with_header(reply, "Access-Control-Allow-Origin", "*")); - #[cfg(debug_assertions)] - warp::serve(calls).run(([0, 0, 0, 0], self.port)).await; - #[cfg(not(debug_assertions))] - warp::serve(calls).run(([127, 0, 0, 1], self.port)).await; - Ok(()) - } -} - -#[cfg(feature = "translate")] -fn get_all_translations(language: String) -> Vec<(String, Vec)> { - log::debug!("Loading translations for language `{}`...", language); - let result = load_locale(&language); - match result { - Ok(catalog) => { - let map = catalog.nalltext(); - let mut result = Vec::with_capacity(map.len()); - for (key, val) in map.iter() { - result.push((key.to_owned().into(), val.iter().map(|x| x.into()).collect())); - } - result - }, - Err(e) => { - log::error!("Failed to load translations for language `{}`: {}", language, e); - vec![] - } - } -} - -#[cfg(feature = "translate")] -fn load_locale(lang: &str) -> Result { - let path = crate::api::dirs::plugin().unwrap_or_else(|| "".into()).join("translations").join(format!("{}.mo", lang)); - let file = std::fs::File::open(path).map_err(|e| gettext_ng::Error::Io(e))?; - gettext_ng::Catalog::parse(file) -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::*; -} diff --git a/usdpl-back/src/lib.rs b/usdpl-back/src/lib.rs index 2771cf7..369a985 100644 --- a/usdpl-back/src/lib.rs +++ b/usdpl-back/src/lib.rs @@ -5,42 +5,59 @@ //! #![warn(missing_docs)] -#[cfg(not(any(feature = "decky", feature = "crankshaft")))] +#[cfg(not(any(feature = "decky")))] mod api_any; mod api_common; -#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] -mod api_crankshaft; -#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] +#[cfg(all(feature = "decky", not(any(feature = "any"))))] mod api_decky; -mod callable; -//mod errors; -mod instance; +mod rpc; +mod services_impl; -pub use callable::{Callable, MutCallable, AsyncCallable}; -pub(crate) use callable::WrappedCallable; -pub use instance::Instance; +//mod errors; +mod websockets; + +pub use websockets::WebsocketServer as Server; //pub use errors::{ServerError, ServerResult}; + +#[allow(missing_docs)] +#[allow(dead_code)] +pub(crate) mod services { + include!(concat!(env!("OUT_DIR"), "/mod.rs")); +} + /// USDPL backend API. /// This contains functionality used exclusively by the back-end. pub mod api { pub use super::api_common::*; /// Standard interfaces not specific to a single plugin loader - #[cfg(not(any(feature = "decky", feature = "crankshaft")))] - pub mod any { pub use super::super::api_any::*; } - - /// Crankshaft-specific interfaces (FIXME) - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - pub mod crankshaft { pub use super::super::api_crankshaft::*; } + #[cfg(not(any(feature = "decky")))] + pub mod any { + pub use super::super::api_any::*; + } /// Decky-specific interfaces - #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] - pub mod decky { pub use super::super::api_decky::*; } + #[cfg(all(feature = "decky", not(any(feature = "any"))))] + pub mod decky { + pub use super::super::api_decky::*; + } } /// usdpl-core re-export pub mod core { pub use usdpl_core::*; } + +/// nrpc re-export +pub mod nrpc { + pub use nrpc::*; +} + +/*/// nRPC-generated exports +#[allow(missing_docs)] +#[allow(dead_code)] +pub mod services { + include!(concat!(env!("OUT_DIR"), "/mod.rs")); +}*/ diff --git a/usdpl-back/src/rpc/mod.rs b/usdpl-back/src/rpc/mod.rs new file mode 100644 index 0000000..506bfd6 --- /dev/null +++ b/usdpl-back/src/rpc/mod.rs @@ -0,0 +1,5 @@ +mod registry; +pub use registry::{ServiceRegistry, StaticServiceRegistry}; + +mod websocket_stream; +pub use websocket_stream::ws_stream; diff --git a/usdpl-back/src/rpc/registry.rs b/usdpl-back/src/rpc/registry.rs new file mode 100644 index 0000000..3ca2ee8 --- /dev/null +++ b/usdpl-back/src/rpc/registry.rs @@ -0,0 +1,43 @@ +use async_lock::Mutex; +use std::collections::HashMap; +use std::sync::Arc; + +use nrpc::{ServerService, ServiceError, ServiceServerStream}; + +pub type StaticServiceRegistry = ServiceRegistry<'static>; + +#[derive(Default, Clone)] +pub struct ServiceRegistry<'a> { + entries: HashMap + Send + 'a>>>>, +} + +impl<'a> ServiceRegistry<'a> { + pub async fn call_descriptor<'b: 'a>( + &mut self, + descriptor: &str, + method: &str, + input: ServiceServerStream<'a, bytes::Bytes>, + ) -> Result, ServiceError> { + if let Some(service) = self.entries.get(descriptor) { + let mut service_lock = service.lock_arc().await; + let output = service_lock.call(method, input).await?; + Ok(output.into()) + } else { + Err(ServiceError::ServiceNotFound) + } + } + + pub fn register + Send + 'a>(&mut self, service: S) -> &mut Self { + let key = service.descriptor().to_owned(); + self.entries + .insert(key, Arc::new(Mutex::new(Box::new(service)))); + self + } + + pub fn with_builtins() -> Self { + let mut reg = Self::default(); + reg.register(crate::services::usdpl::DevToolsServer::new(crate::services_impl::DevTools{})) + .register(crate::services::usdpl::TranslationsServer::new(crate::services_impl::Translations{})); + reg + } +} diff --git a/usdpl-back/src/rpc/websocket_stream.rs b/usdpl-back/src/rpc/websocket_stream.rs new file mode 100644 index 0000000..ea44c00 --- /dev/null +++ b/usdpl-back/src/rpc/websocket_stream.rs @@ -0,0 +1,34 @@ +use core::marker::Unpin; +use std::sync::Arc; + +use tokio::{net::TcpStream, sync::Mutex}; +use ratchet_rs::{WebSocket, Message, Error as RatchetError, Extension}; + +use nrpc::ServiceError; +use nrpc::_helpers::futures::Stream; +use nrpc::_helpers::bytes::{BytesMut, Bytes}; + +struct WsStreamState{ + ws: Arc>>, + buf: BytesMut, +} + +pub fn ws_stream<'a, T: Extension + Unpin + 'a>(ws: Arc>>) -> impl Stream> + 'a { + nrpc::_helpers::futures::stream::unfold(WsStreamState { ws, buf: BytesMut::new() }, |mut state| async move { + let mut locked_ws = state.ws.lock().await; + if locked_ws.is_closed() || !locked_ws.is_active() { + None + } else { + let result = locked_ws.read(&mut state.buf).await; + drop(locked_ws); + match result { + Ok(Message::Binary) => Some((Ok(state.buf.clone().freeze()), state)), + Ok(_) => Some((Err(ServiceError::Method(Box::new(RatchetError::with_cause( + ratchet_rs::ErrorKind::Protocol, + "Websocket text messages are not accepted", + )))), state)), + Err(e) => Some((Err(ServiceError::Method(Box::new(e))), state)) + } + } + }) +} diff --git a/usdpl-back/src/services_impl/dev_tools.rs b/usdpl-back/src/services_impl/dev_tools.rs new file mode 100644 index 0000000..1be811f --- /dev/null +++ b/usdpl-back/src/services_impl/dev_tools.rs @@ -0,0 +1,22 @@ +use crate::services::usdpl as generated; + +/// Built-in dev tools service implementation +pub(crate) struct DevTools {} + +#[async_trait::async_trait] +impl<'a> generated::IDevTools<'a> for DevTools { + async fn log( + &mut self, + input: generated::LogMessage, + ) -> Result> { + match input.level { + lvl if lvl == generated::LogLevel::Trace as _ => log::trace!("{}", input.msg), + lvl if lvl == generated::LogLevel::Debug as _ => log::debug!("{}", input.msg), + lvl if lvl == generated::LogLevel::Info as _ => log::info!("{}", input.msg), + lvl if lvl == generated::LogLevel::Warn as _ => log::warn!("{}", input.msg), + lvl if lvl == generated::LogLevel::Error as _ => log::error!("{}", input.msg), + lvl => return Err(Box::::from(format!("Unexpected input log level {}", lvl))) + } + Ok(generated::Empty{ ok: true }) + } +} diff --git a/usdpl-back/src/services_impl/mod.rs b/usdpl-back/src/services_impl/mod.rs new file mode 100644 index 0000000..7e3aa3a --- /dev/null +++ b/usdpl-back/src/services_impl/mod.rs @@ -0,0 +1,5 @@ +mod dev_tools; +pub(crate) use dev_tools::DevTools; + +mod translations; +pub(crate) use translations::Translations; diff --git a/usdpl-back/src/services_impl/translations.rs b/usdpl-back/src/services_impl/translations.rs new file mode 100644 index 0000000..d51734e --- /dev/null +++ b/usdpl-back/src/services_impl/translations.rs @@ -0,0 +1,31 @@ +use crate::services::usdpl as generated; + +/// Built-in translation service implementation +pub(crate) struct Translations {} + +#[async_trait::async_trait] +impl<'a> generated::ITranslations<'a> for Translations { + async fn get_language( + &mut self, + input: generated::LanguageRequest, + ) -> Result> { + let catalog = load_locale(&input.lang).map_err(|e| Box::new(e) as _)?; + let catalog_map = catalog.nalltext(); + let mut map = std::collections::HashMap::with_capacity(catalog_map.len()); + for (key, val) in catalog_map.into_iter() { + if val.len() > 1 { + log::warn!("Translations key {} for language {} has plural entries which aren't currently supported", key, input.lang); + } + if let Some(val_0) = val.get(0) { + map.insert(key.to_owned(), val_0.to_owned()); + } + } + Ok(generated::TranslationsReply { translations: map }) + } +} + +fn load_locale(lang: &str) -> Result { + let path = crate::api::dirs::plugin().unwrap_or_else(|| "".into()).join("translations").join(format!("{}.mo", lang)); + let file = std::fs::File::open(path).map_err(|e| gettext_ng::Error::Io(e))?; + gettext_ng::Catalog::parse(file) +} diff --git a/usdpl-back/src/websockets.rs b/usdpl-back/src/websockets.rs new file mode 100644 index 0000000..142226b --- /dev/null +++ b/usdpl-back/src/websockets.rs @@ -0,0 +1,175 @@ +use ratchet_rs::deflate::DeflateExtProvider; +use ratchet_rs::{Error as RatchetError, ProtocolRegistry, WebSocketConfig}; +use tokio::net::{TcpListener, TcpStream}; + +use nrpc::_helpers::futures::StreamExt; + +use crate::rpc::StaticServiceRegistry; + +struct MethodDescriptor<'a> { + service: &'a str, + method: &'a str, +} + +/// Handler for communication to and from the front-end +pub struct WebsocketServer { + services: StaticServiceRegistry, + port: u16, +} + +impl WebsocketServer { + /// Initialise an instance of the back-end websocket server + pub fn new(port_usdpl: u16) -> Self { + Self { + services: StaticServiceRegistry::with_builtins(), + port: port_usdpl, + } + } + + /// Get the service registry that the server handles + pub fn registry(&mut self) -> &'_ mut StaticServiceRegistry { + &mut self.services + } + + /// Register a nRPC service for this server to handle + pub fn register + Send + 'static>(mut self, service: S) -> Self { + self.services.register(service); + self + } + + /// Run the web server forever, asynchronously + pub async fn run(&self) -> std::io::Result<()> { + #[cfg(debug_assertions)] + let addr = (std::net::Ipv4Addr::UNSPECIFIED, self.port); + #[cfg(not(debug_assertions))] + let addr = (std::net::Ipv4Addr::LOCALHOST, self.port); + + let tcp = TcpListener::bind(addr).await?; + + while let Ok((stream, _addr_do_not_use)) = tcp.accept().await { + tokio::spawn(error_logger("USDPL websocket server error", Self::connection_handler(self.services.clone(), stream))); + } + + Ok(()) + } + + #[cfg(feature = "blocking")] + /// Run the server forever, blocking the current thread + pub fn run_blocking(self) -> std::io::Result<()> { + let runner = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + runner.block_on(self.run()) + } + + async fn connection_handler( + mut services: StaticServiceRegistry, + stream: TcpStream, + ) -> Result<(), RatchetError> { + log::debug!("connection_handler invoked!"); + let upgraded = ratchet_rs::accept_with( + stream, + WebSocketConfig::default(), + DeflateExtProvider::default(), + ProtocolRegistry::new(["usdpl-nrpc"])?, + ) + .await? + .upgrade() + .await?; + + let request_path = upgraded.request.uri().path(); + + log::debug!("accepted new connection on uri {}", request_path); + + let websocket = std::sync::Arc::new(tokio::sync::Mutex::new(upgraded.websocket)); + + let descriptor = Self::parse_uri_path(request_path) + .map_err(|e| RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e))?; + + let input_stream = Box::new(nrpc::_helpers::futures::stream::StreamExt::boxed(crate::rpc::ws_stream(websocket.clone()))); + let output_stream = services + .call_descriptor( + descriptor.service, + descriptor.method, + input_stream, + ) + .await + .map_err(|e| { + RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string()) + })?; + + output_stream.for_each(|result| async { + match result { + Ok(msg) => { + let mut ws_lock = websocket.lock().await; + if let Err(e) = ws_lock.write_binary(msg).await { + log::error!("websocket error while writing response on uri {}: {}", request_path, e); + } + }, + Err(e) => { + log::error!("service error while writing response on uri {}: {}", request_path, e); + } + } + }).await; + + websocket.lock().await.close(ratchet_rs::CloseReason { + code: ratchet_rs::CloseCode::Normal, + description: None, + }).await?; + + /*let mut buf = BytesMut::new(); + loop { + match websocket.read(&mut buf).await? { + Message::Text => { + return Err(RatchetError::with_cause( + ratchet_rs::ErrorKind::Protocol, + "Websocket text messages are not accepted", + )) + } + Message::Binary => { + log::debug!("got binary ws message on uri {}", request_path); + let response = services + .call_descriptor( + descriptor.service, + descriptor.method, + buf.clone().freeze(), + ) + .await + .map_err(|e| { + RatchetError::with_cause(ratchet_rs::ErrorKind::Protocol, e.to_string()) + })?; + log::debug!("service completed response on uri {}", request_path); + websocket.write_binary(response).await?; + } + Message::Ping(x) => websocket.write_pong(x).await?, + Message::Pong(_) => {} + Message::Close(_) => break, + } + }*/ + log::debug!("ws connection {} closed", request_path); + Ok(()) + } + + fn parse_uri_path<'a>(path: &'a str) -> Result, &'static str> { + let mut iter = path.trim_matches('/').split('/'); + if let Some(service) = iter.next() { + if let Some(method) = iter.next() { + if iter.next().is_none() { + return Ok(MethodDescriptor { service, method }); + } else { + Err("URL path has too many separators") + } + } else { + Err("URL path has no method") + } + } else { + Err("URL path has no service") + } + } +} + +async fn error_logger(msg: &'static str, f: impl core::future::Future>) { + if let Err(e) = f.await { + log::error!("{}: {}", msg, e); + } +} diff --git a/usdpl-build/Cargo.toml b/usdpl-build/Cargo.toml new file mode 100644 index 0000000..9e0a40a --- /dev/null +++ b/usdpl-build/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "usdpl-build" +version = "0.11.0" +edition = "2021" +authors = ["NGnius "] +license = "GPL-3.0-only" +repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs" +readme = "../README.md" +description = "Universal Steam Deck Plugin Library core" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nrpc-build = { version = "0.10", path = "../../nRPC/nrpc-build" } +prost-build = "0.11" +prost-types = "0.11" + +# code gen +prettyplease = "0.2" +quote = "1.0" +syn = "2.0" +proc-macro2 = "1.0" + diff --git a/usdpl-build/protos/debug.proto b/usdpl-build/protos/debug.proto new file mode 100644 index 0000000..86aaf0b --- /dev/null +++ b/usdpl-build/protos/debug.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package usdpl; + +// The translation service +service DevTools { + // Retrieves all translations for the provided 4-letter code + rpc Log (LogMessage) returns (Empty); +} + +enum LogLevel { + Trace = 0; + Debug = 1; + Info = 2; + Warn = 3; + Error = 4; +} + +// The request message containing the log message +message LogMessage { + LogLevel level = 1; + string msg = 2; +} + +message Empty { + bool ok = 1; +} diff --git a/usdpl-build/protos/translations.proto b/usdpl-build/protos/translations.proto new file mode 100644 index 0000000..3fc4e59 --- /dev/null +++ b/usdpl-build/protos/translations.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package usdpl; + +// The translation service +service Translations { + // Retrieves all translations for the provided 4-letter code + rpc GetLanguage (LanguageRequest) returns (TranslationsReply) {} +} + +// The request message containing the language code +message LanguageRequest { + string lang = 1; +} + +// The response message containing all translations for the language +message TranslationsReply { + map translations = 1; +} diff --git a/usdpl-build/src/back/mod.rs b/usdpl-build/src/back/mod.rs new file mode 100644 index 0000000..b5683e2 --- /dev/null +++ b/usdpl-build/src/back/mod.rs @@ -0,0 +1,20 @@ +pub fn build( + custom_protos: impl Iterator, + custom_dirs: impl Iterator, +) { + nrpc_build::compile_servers( + custom_protos, + crate::proto_out_paths(custom_dirs), + ) +} + +pub fn build_with_custom_builtins( + custom_protos: impl Iterator, + custom_dirs: impl Iterator, +) { + crate::dump_protos_out().unwrap(); + nrpc_build::compile_servers( + crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos), + crate::proto_out_paths(custom_dirs), + ) +} diff --git a/usdpl-build/src/front/mod.rs b/usdpl-build/src/front/mod.rs new file mode 100644 index 0000000..d706453 --- /dev/null +++ b/usdpl-build/src/front/mod.rs @@ -0,0 +1,45 @@ +mod preprocessor; +pub use preprocessor::WasmProtoPreprocessor; + +mod service_generator; +pub use service_generator::WasmServiceGenerator; + +mod shared_state; +pub(crate) use shared_state::SharedState; + +pub fn build( + custom_protos: impl Iterator, + custom_dirs: impl Iterator, +) { + let shared_state = SharedState::new(); + crate::dump_protos_out().unwrap(); + nrpc_build::Transpiler::new( + crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos), + crate::proto_out_paths(custom_dirs), + ) + .unwrap() + .generate_client() + .with_preprocessor(nrpc_build::AbstractImpl::outer( + WasmProtoPreprocessor::with_state(&shared_state), + )) + .with_service_generator(nrpc_build::AbstractImpl::outer( + WasmServiceGenerator::with_state(&shared_state), + )) + .transpile() + .unwrap() +} + +pub fn build_min( + custom_protos: impl Iterator, + custom_dirs: impl Iterator, +) { + crate::dump_protos_out().unwrap(); + nrpc_build::Transpiler::new( + crate::all_proto_filenames(crate::proto_builtins_out_path(), custom_protos), + crate::proto_out_paths(custom_dirs), + ) + .unwrap() + .generate_client() + .transpile() + .unwrap() +} diff --git a/usdpl-build/src/front/preprocessor.rs b/usdpl-build/src/front/preprocessor.rs new file mode 100644 index 0000000..bcaf763 --- /dev/null +++ b/usdpl-build/src/front/preprocessor.rs @@ -0,0 +1,24 @@ +use nrpc_build::IPreprocessor; +//use prost_build::{Service, ServiceGenerator}; +use prost_types::FileDescriptorSet; + +use super::SharedState; + +pub struct WasmProtoPreprocessor { + shared: SharedState, +} + +impl WasmProtoPreprocessor { + pub fn with_state(state: &SharedState) -> Self { + Self { + shared: state.clone(), + } + } +} + +impl IPreprocessor for WasmProtoPreprocessor { + fn process(&mut self, fds: &mut FileDescriptorSet) -> proc_macro2::TokenStream { + self.shared.lock().expect("Cannot lock shared state").fds = Some(fds.clone()); + quote::quote! {} + } +} diff --git a/usdpl-build/src/front/service_generator.rs b/usdpl-build/src/front/service_generator.rs new file mode 100644 index 0000000..a4201f1 --- /dev/null +++ b/usdpl-build/src/front/service_generator.rs @@ -0,0 +1,1117 @@ +use std::collections::HashSet; + +use nrpc_build::IServiceGenerator; +use prost_build::Service; +use prost_types::{DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorSet}; + +use super::SharedState; + +pub struct WasmServiceGenerator { + shared: SharedState, +} + +impl WasmServiceGenerator { + pub fn with_state(state: &SharedState) -> Self { + Self { + shared: state.clone(), + } + } +} + +fn generate_service_methods( + service: &Service, + fds: &FileDescriptorSet, +) -> proc_macro2::TokenStream { + let mut gen_methods = Vec::with_capacity(service.methods.len()); + for method in &service.methods { + let method_name_str = method.name.clone(); + let method_name = quote::format_ident!("{}", method.name); + let method_input = quote::format_ident!("{}{}", &service.name, method.input_type); + let method_output = quote::format_ident!("{}{}Wasm", &service.name, method.output_type); + let method_output_as_in = quote::format_ident!("{}{}", &service.name, method.output_type); + + let input_type = find_message_type(&method.input_type, &service.package, fds) + .expect("Protobuf message is used but not found"); + + let mut input_params = Vec::with_capacity(input_type.field.len()); + let mut params_to_fields = Vec::with_capacity(input_type.field.len()); + + match (method.client_streaming, method.server_streaming) { + (false, false) => { + for field in &input_type.field { + //let param_name = quote::format_ident!("val{}", i.to_string()); + let type_enum = ProtobufType::from_field(field, &service.name, false); + //let rs_type_name = type_enum.to_tokens(); + let js_type_name = type_enum.to_wasm_tokens(); + let rs_type_name = type_enum.to_tokens(); + let field_name = quote::format_ident!( + "{}", + field + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + input_params.push(quote::quote! { + #field_name: #js_type_name, + }); + params_to_fields.push(quote::quote! { + #field_name: #rs_type_name::from_wasm(#field_name.into()),//: #field_name, + }); + } + let params_to_fields_transformer = if input_type.field.len() == 1 { + let field_name = quote::format_ident!( + "{}", + input_type.field[0] + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + quote::quote! { + let val = #method_input::from_wasm(#field_name.into()); + } + } else if input_type.field.is_empty() { + quote::quote! { + let val = #method_input {}; + } + } else { + quote::quote! { + let val = #method_input { + #(#params_to_fields)* + }; + } + }; + + gen_methods.push(quote::quote! { + #[wasm_bindgen] + pub async fn #method_name(&self, #(#input_params)*) -> Option<#method_output> { + + #params_to_fields_transformer + + match self.service.#method_name(val.into()).await { + Ok(x) => { + let x2: #method_output_as_in = x.into(); + Some(x2.into_wasm()) + }, + Err(e) => { + // log error + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e); + None + } + } + } + }); + }, + (true, false) => { + // many -> 1 + gen_methods.push(quote::quote! { + #[wasm_bindgen] + pub async fn #method_name(&self, generator: js_sys::Function) -> Option<#method_output> { + + // function into Rust futures Stream + let stream = Box::new(::usdpl_front::wasm::JsFunctionStream::<#method_input>::from_function(generator)); + + match self.service.#method_name(stream).await { + Ok(x) => { + let x2: #method_output_as_in = x.into(); + Some(x2.into_wasm()) + }, + Err(e) => { + // log error + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e); + None + } + } + } + }); + }, + (false, true) => { + // 1 -> many + for field in &input_type.field { + //let param_name = quote::format_ident!("val{}", i.to_string()); + let type_enum = ProtobufType::from_field(field, &service.name, false); + //let rs_type_name = type_enum.to_tokens(); + let js_type_name = type_enum.to_wasm_tokens(); + let rs_type_name = type_enum.to_tokens(); + let field_name = quote::format_ident!( + "{}", + field + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + input_params.push(quote::quote! { + #field_name: #js_type_name, + }); + params_to_fields.push(quote::quote! { + #field_name: #rs_type_name::from_wasm(#field_name.into()),//: #field_name, + }); + } + let params_to_fields_transformer = if input_type.field.len() == 1 { + let field_name = quote::format_ident!( + "{}", + input_type.field[0] + .name + .as_ref() + .expect("Protobuf message field needs a name") + ); + quote::quote! { + let val = #method_input::from_wasm(#field_name.into()); + } + } else if input_type.field.is_empty() { + quote::quote! { + let val = #method_input {}; + } + } else { + quote::quote! { + let val = #method_input { + #(#params_to_fields)* + }; + } + }; + + gen_methods.push(quote::quote! { + #[wasm_bindgen] + pub async fn #method_name(&self, #(#input_params)* callback: js_sys::Function) { + #params_to_fields_transformer + + match self.service.#method_name(val.into()).await { + Ok(mut x) => { + while let Some(next_result) = x.next().await { + match next_result { + Err(e) => { + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e); + }, + Ok(item) => { + #[inline(always)] + fn js_to_string(js: &JsValue) -> String { + if let Some(s) = js.as_string() { + s + } else { + format!("{:?}", js) + } + } + let item: #method_output = item.into(); + match callback.call1(&JsValue::undefined(), &item.into_wasm_streamable()) { + Ok(js_val) => { + if !(js_val.is_undefined() && js_val.is_null()) { + let str_val = js_to_string(&js_val); + log::info!("service:{}|method:{}|callback result:{}", self.service.descriptor(), #method_name_str, str_val); + } + }, + Err(js_e) => { + let str_e = js_to_string(&js_e); + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, str_e); + } + } + } + } + } + }, + Err(e) => { + // log error + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e); + } + } + } + }); + }, + (true, true) => { + // many -> many + gen_methods.push(quote::quote! { + #[wasm_bindgen] + pub async fn #method_name(&self, generator: js_sys::Function, callback: js_sys::Function) -> Option<#method_output> { + + // function into Rust futures Stream + let stream = Box::new(::usdpl_front::wasm::JsFunctionStream::<#method_input>::from_function(generator)); + + match self.service.#method_name(stream).await { + Ok(mut x) => { + while let Some(next_result) = x.next().await { + match next_result { + Err(e) => { + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e); + }, + Ok(item) => { + #[inline(always)] + fn js_to_string(js: &JsValue) -> String { + if let Some(s) = js.as_string() { + s + } else { + format!("{:?}", js) + } + } + let item: #method_output = item.into(); + match callback.call1(&JsValue::undefined(), &item.into_wasm_streamable()) { + Ok(js_val) => { + if !(js_val.is_undefined() && js_val.is_null()) { + let str_val = js_to_string(&js_val); + log::info!("service:{}|method:{}|callback result:{}", self.service.descriptor(), #method_name_str, str_val); + } + }, + Err(js_e) => { + let str_e = js_to_string(&js_e); + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, str_e); + } + } + } + } + } + }, + Err(e) => { + // log error + log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e); + None + } + } + } + }); + }, + } + + + + + } + quote::quote! { + #(#gen_methods)* + } +} + +fn find_message_type<'a>( + want_type: &str, + want_package: &str, + fds: &'a FileDescriptorSet, +) -> Option<&'a DescriptorProto> { + for file in &fds.file { + if let Some(pkg) = &file.package { + if pkg == want_package { + for message_type in &file.message_type { + if let Some(name) = &message_type.name { + if name == want_type { + return Some(message_type); + } + } + } + } + } + } + None +} + +fn find_enum_type<'a>( + want_type: &str, + want_package: &str, + fds: &'a FileDescriptorSet, +) -> Option<&'a EnumDescriptorProto> { + for file in &fds.file { + for enum_type in &file.enum_type { + if let Some(name) = &enum_type.name { + if let Some(pkg) = &file.package { + if name == want_type && pkg == want_package { + return Some(enum_type); + } + } + } + } + } + None +} + +fn find_field<'a>( + want_field: &str, + descriptor: &'a DescriptorProto, +) -> Option<&'a FieldDescriptorProto> { + for field in &descriptor.field { + if let Some(name) = &field.name { + if name == want_field { + return Some(field); + } + } + } + None +} + +fn is_known_map(field: &FieldDescriptorProto, known_maps: &HashSet) -> bool { + if let Some(type_name) = &field.type_name { + let name = type_name.split('.').last().unwrap(); + known_maps.contains(name) + } else { + false + } +} + +fn generate_wasm_struct_interop( + descriptor: &DescriptorProto, + handled_enums: &mut HashSet, + handled_types: &mut HashSet, + known_maps: &mut HashSet, + seen_super_enums: &mut HashSet, + is_response_msg: bool, + service: &str, +) -> proc_macro2::TokenStream { + let msg_name = quote::format_ident!( + "{}{}", + service, + descriptor + .name + .as_ref() + .expect("Protobuf message needs a name") + ); + let msg_name_wasm = quote::format_ident!( + "{}{}Wasm", + service, + descriptor + .name + .as_ref() + .expect("Protobuf message needs a name") + ); + let super_msg_name = quote::format_ident!( + "{}", + descriptor + .name + .as_ref() + .expect("Protobuf message needs a name") + ); + let js_map_name = quote::format_ident!("{}", "js_map"); + let mut gen_fields = Vec::with_capacity(descriptor.field.len()); + let mut gen_into_fields = Vec::with_capacity(descriptor.field.len()); + let mut gen_from_fields = Vec::with_capacity(descriptor.field.len()); + + let mut gen_nested_types = Vec::with_capacity(descriptor.nested_type.len()); + + let mut gen_enums = Vec::with_capacity(descriptor.enum_type.len()); + + if let Some(options) = &descriptor.options { + //dbg!(options); + if let Some(map_entry) = options.map_entry { + // TODO deal with options when necessary + if map_entry { + //dbg!(descriptor); + let name = descriptor + .name + .clone() + .expect("Protobuf message needs a name"); + known_maps.insert(name.clone()); + let key_field = + find_field("key", descriptor).expect("Protobuf map entry has no key field"); + let key_type = ProtobufType::from_field(&key_field, service, false); + let value_field = + find_field("value", descriptor).expect("Protobuf map entry has no value field"); + let value_type = ProtobufType::from_field(&value_field, service, false); + + let map_type = ProtobufType::Map { + key: Box::new(key_type), + value: Box::new(value_type), + }; + + //dbg!("Generated map type", name); + + let map_tokens = map_type.to_tokens(); + let wasm_tokens = map_type.to_wasm_tokens(); + + return quote::quote! { + pub type #msg_name = #map_tokens; + pub type #msg_name_wasm = #wasm_tokens; + }; + } + } + // TODO Deal with other message options when necessary + } + + //dbg!(&descriptor.options); + + for n_type in &descriptor.nested_type { + let type_name = n_type + .name + .clone() + .expect("Protobuf nested message needs a name"); + if !handled_types.contains(&type_name) { + handled_types.insert(type_name); + gen_nested_types.push(generate_wasm_struct_interop( + n_type, + handled_enums, + handled_types, + known_maps, + seen_super_enums, + is_response_msg, + service, + )); + } + } + + for e_type in &descriptor.enum_type { + let type_name = e_type.name.clone().expect("Protobuf enum needs a name"); + if !handled_enums.contains(&type_name) { + handled_enums.insert(type_name); + gen_enums.push(generate_wasm_enum_interop( + e_type, + service, + seen_super_enums, + )); + } + } + + if descriptor.field.len() == 0 { + quote::quote! { + pub type #msg_name = (); + pub type #msg_name_wasm = #msg_name; + + #(#gen_nested_types)* + + #(#gen_enums)* + } + } else if descriptor.field.len() == 1 { + let field = &descriptor.field[0]; + //dbg!(descriptor, field); + let field_name_str = field + .name + .as_ref() + .expect("Protobuf message field needs a name"); + let field_name = quote::format_ident!( + "{}", + field_name_str + ); + let type_enum = ProtobufType::from_field(field, service, is_known_map(field, known_maps)); + let type_name = type_enum.to_tokens(); + let wasm_type_name = type_enum.to_wasm_tokens(); + + /*let wasm_streamable_impl = if type_enum.is_already_wasm_streamable() { + quote::quote!{} + } else { + let into_wasm_streamable = quote::quote!{self.into_wasm_streamable()}; + let from_wasm_streamable = quote::quote!{#type_name::from_wasm_streamable(js)}; + quote::quote!{ + impl ::usdpl_front::wasm::FromWasmStreamableType for #msg_name { + fn from_wasm_streamable(js: JsValue) -> Result { + #from_wasm_streamable + } + } + + impl ::usdpl_front::wasm::IntoWasmStreamableType for #msg_name { + fn into_wasm_streamable(self) -> JsValue { + #into_wasm_streamable + } + } + } + };*/ + + + quote::quote! { + pub type #msg_name = #type_name; + pub type #msg_name_wasm = #wasm_type_name; + + impl std::convert::Into for #msg_name { + #[inline] + fn into(self) -> super::#super_msg_name { + super::#super_msg_name { + #field_name: self + } + } + } + + impl std::convert::From for #msg_name { + #[inline] + #[allow(unused_variables)] + fn from(other: super::#super_msg_name) -> Self { + other.#field_name + } + } + + // #wasm_streamable_impl + + #(#gen_nested_types)* + + #(#gen_enums)* + } + } else { + let mut gen_into_wasm_streamable_fields = Vec::with_capacity(descriptor.field.len()); + let mut gen_from_wasm_streamable_fields = Vec::with_capacity(descriptor.field.len()); + + for field in &descriptor.field { + let field_name_str = field + .name + .as_ref() + .expect("Protobuf message field needs a name"); + let field_name = quote::format_ident!( + "{}", + field_name_str + ); + let type_enum = + ProtobufType::from_field(field, service, is_known_map(field, known_maps)); + let type_name = type_enum.to_tokens(); + + let into_wasm_streamable = type_enum.to_into_wasm_streamable(field_name_str, &js_map_name); + let from_wasm_streamable = type_enum.to_from_wasm_streamable(field_name_str, &js_map_name); + //let wasm_type_name = type_enum.to_wasm_tokens(); + gen_fields.push(quote::quote! { + pub #field_name: #type_name, + }); + gen_into_fields.push(quote::quote! { + #field_name: self.#field_name.into(), + }); + + gen_from_fields.push(quote::quote! { + #field_name: <_>::from(other.#field_name), + }); + + gen_into_wasm_streamable_fields.push(into_wasm_streamable); + gen_from_wasm_streamable_fields.push(from_wasm_streamable); + } + + let wasm_attribute_maybe = + if (descriptor.field.len() == 1 || !is_response_msg) && !descriptor.field.is_empty() { + quote::quote! {} + } else { + quote::quote! { + #[wasm_bindgen] + } + }; + + quote::quote! { + #wasm_attribute_maybe + pub struct #msg_name { + #(#gen_fields)* + } + + impl KnownWasmCompatible for #msg_name {} + + impl IntoWasmable<#msg_name> for #msg_name { + fn into_wasm(self) -> Self { + self + } + } + + impl FromWasmable<#msg_name> for #msg_name { + fn from_wasm(x: Self) -> Self { + x + } + } + + type #msg_name_wasm = #msg_name; + + impl std::convert::Into for #msg_name { + #[inline] + fn into(self) -> super::#super_msg_name { + super::#super_msg_name { + #(#gen_into_fields)* + } + } + } + + impl std::convert::From for #msg_name { + #[inline] + #[allow(unused_variables)] + fn from(other: super::#super_msg_name) -> Self { + #msg_name { + #(#gen_from_fields)* + } + } + } + + impl ::usdpl_front::wasm::FromWasmStreamableType for #msg_name { + fn from_wasm_streamable(js: JsValue) -> Result { + let #js_map_name = js_sys::Map::from(js); + Ok(Self { + #(#gen_from_wasm_streamable_fields)* + }) + } + } + + impl ::usdpl_front::wasm::IntoWasmStreamableType for #msg_name { + fn into_wasm_streamable(self) -> JsValue { + let #js_map_name = js_sys::Map::new(); + #(#gen_into_wasm_streamable_fields)* + #js_map_name.into() + } + } + + #(#gen_nested_types)* + + #(#gen_enums)* + } + } +} + +#[derive(Debug)] +enum ProtobufType { + Double, + Float, + Int32, + Int64, + Uint32, + Uint64, + Sint32, + Sint64, + Fixed32, + Fixed64, + Sfixed32, + Sfixed64, + Bool, + String, + Bytes, + Repeated(Box), + Map { + key: Box, + value: Box, + }, + Custom(String), +} + +impl ProtobufType { + fn from_str(type_name: &str, service: &str) -> Self { + match type_name { + "double" => Self::Double, + "float" => Self::Float, + "int32" => Self::Int32, + "int64" => Self::Int64, + "uint32" => Self::Uint32, + "uint64" => Self::Uint64, + "sint32" => Self::Sint32, + "sint64" => Self::Sint64, + "fixed32" => Self::Fixed32, + "fixed64" => Self::Fixed64, + "sfixed32" => Self::Sfixed32, + "sfixed64" => Self::Sfixed64, + "bool" => Self::Bool, + "string" => Self::String, + "bytes" => Self::Bytes, + t => Self::Custom(format!("{}{}", service, t.split('.').last().unwrap())), + } + } + + fn from_id(id: i32) -> Self { + match id { + 1 => Self::Double, + //"float" => quote::quote!{f32}, + //"int32" => quote::quote!{i32}, + //"int64" => quote::quote!{i64}, + //"uint32" => quote::quote!{u32}, + 4 => Self::Uint64, + 5 => Self::Int32, + //"sint32" => quote::quote!{i32}, + //"sint64" => quote::quote!{i64}, + //"fixed32" => quote::quote!{u32}, + //"fixed64" => quote::quote!{u64}, + //"sfixed32" => quote::quote!{i32}, + //"sfixed64" => quote::quote!{i64}, + 8 => Self::Bool, + 9 => Self::String, + 13 => Self::Uint32, + //"bytes" => quote::quote!{Vec}, + t => Self::Custom(format!("UnknownType{}", t)), + } + } + + fn from_field(field: &FieldDescriptorProto, service: &str, is_map: bool) -> Self { + let inner = if let Some(type_name) = &field.type_name { + Self::from_str(type_name, service) + } else { + let number = field.r#type.unwrap(); + Self::from_id(number) + }; + if let Some(label) = field.label { + match label { + 3 if !is_map => Self::Repeated(Box::new(inner)), // is also the label for maps for some reason... + _ => inner, + } + } else { + inner + } + } + + fn to_tokens(&self) -> proc_macro2::TokenStream { + match self { + Self::Double => quote::quote! {f64}, + Self::Float => quote::quote! {f32}, + Self::Int32 => quote::quote! {i32}, + Self::Int64 => quote::quote! {i64}, + Self::Uint32 => quote::quote! {u32}, + Self::Uint64 => quote::quote! {u64}, + Self::Sint32 => quote::quote! {i32}, + Self::Sint64 => quote::quote! {i64}, + Self::Fixed32 => quote::quote! {u32}, + Self::Fixed64 => quote::quote! {u64}, + Self::Sfixed32 => quote::quote! {i32}, + Self::Sfixed64 => quote::quote! {i64}, + Self::Bool => quote::quote! {bool}, + Self::String => quote::quote! {String}, + Self::Bytes => quote::quote! {Vec}, + Self::Repeated(t) => { + let inner = t.to_tokens(); + quote::quote! {Vec::<#inner>} + } + Self::Map { key, value } => { + let key = key.to_tokens(); + let value = value.to_tokens(); + quote::quote! {std::collections::HashMap<#key, #value>} + } + Self::Custom(t) => { + let ident = quote::format_ident!("{}", t); + quote::quote! {#ident} + } + } + } + + fn to_wasm_tokens(&self) -> proc_macro2::TokenStream { + match self { + Self::Double => quote::quote! {f64}, + Self::Float => quote::quote! {f32}, + Self::Int32 => quote::quote! {i32}, + Self::Int64 => quote::quote! {i64}, + Self::Uint32 => quote::quote! {u32}, + Self::Uint64 => quote::quote! {u64}, + Self::Sint32 => quote::quote! {i32}, + Self::Sint64 => quote::quote! {i64}, + Self::Fixed32 => quote::quote! {u32}, + Self::Fixed64 => quote::quote! {u64}, + Self::Sfixed32 => quote::quote! {i32}, + Self::Sfixed64 => quote::quote! {i64}, + Self::Bool => quote::quote! {bool}, + Self::String => quote::quote! {String}, + Self::Bytes => quote::quote! {Vec}, + Self::Repeated(_) => quote::quote! {js_sys::Array}, + Self::Map { .. } => quote::quote! {js_sys::Map}, + Self::Custom(t) => { + let ident = quote::format_ident!("{}Wasm", t); + quote::quote! {#ident} + } + } + } + + fn to_into_wasm_streamable(&self, field_name: &str, js_map_name: &syn::Ident) -> proc_macro2::TokenStream { + //let type_tokens = self.to_tokens(); + let field_ident = quote::format_ident!("{}", field_name); + quote::quote!{#js_map_name.set(&JsValue::from(#field_name), &self.#field_ident.into_wasm_streamable());} + } + + fn to_from_wasm_streamable(&self, field_name: &str, js_map_name: &syn::Ident) -> proc_macro2::TokenStream { + let type_tokens = self.to_tokens(); + let field_ident = quote::format_ident!("{}", field_name); + quote::quote!{#field_ident: #type_tokens::from_wasm_streamable(#js_map_name.get(&JsValue::from(#field_name)))?,} + } + + /*fn is_already_wasm_streamable(&self) -> bool { + !matches!(self, Self::Custom(_)) + }*/ +} + +fn generate_wasm_enum_interop( + descriptor: &EnumDescriptorProto, + service: &str, + seen_super_enums: &mut HashSet, +) -> proc_macro2::TokenStream { + let enum_name = quote::format_ident!( + "{}{}", + service, + descriptor + .name + .as_ref() + .expect("Protobuf enum needs a name") + ); + let enum_name_wasm = quote::format_ident!( + "{}{}Wasm", + service, + descriptor + .name + .as_ref() + .expect("Protobuf enum needs a name") + ); + let super_enum_name = quote::format_ident!( + "{}", + descriptor + .name + .as_ref() + .expect("Protobuf enum needs a name") + ); + let mut gen_values = Vec::with_capacity(descriptor.value.len()); + let mut gen_into_values = Vec::with_capacity(descriptor.value.len()); + let mut gen_from_values = Vec::with_capacity(descriptor.value.len()); + if let Some(_options) = &descriptor.options { + // TODO deal with options when necessary + todo!("Deal with enum options when necessary"); + } + for value in &descriptor.value { + let val_name = quote::format_ident!( + "{}", + value + .name + .as_ref() + .expect("Protobuf enum value needs a name") + ); + if let Some(_val_options) = &value.options { + // TODO deal with options when necessary + todo!("Deal with enum value options when necessary"); + } else { + if let Some(number) = &value.number { + gen_values.push(quote::quote! { + #val_name = #number, + }); + } else { + gen_values.push(quote::quote! { + #val_name, + }); + } + gen_into_values.push(quote::quote! { + Self::#val_name => super::#super_enum_name::#val_name, + }); + + gen_from_values.push(quote::quote! { + super::#super_enum_name::#val_name => Self::#val_name, + }); + } + } + + let impl_wasm_compat = if seen_super_enums.contains(descriptor.name.as_ref().unwrap()) { + quote::quote! {} + } else { + seen_super_enums.insert(descriptor.name.clone().unwrap()); + quote::quote! { + //impl KnownWasmCompatible for super::#super_enum_name {} + } + }; + + quote::quote! { + #[wasm_bindgen] + #[repr(i32)] + #[derive(Clone, Copy)] + pub enum #enum_name { + #(#gen_values)* + } + + type #enum_name_wasm = #enum_name; + + impl std::convert::Into for #enum_name { + fn into(self) -> super::#super_enum_name { + match self { + #(#gen_into_values)* + } + } + } + + impl std::convert::From for #enum_name { + fn from(other: super::#super_enum_name) -> Self { + match other { + #(#gen_from_values)* + } + } + } + + #impl_wasm_compat + + impl FromWasmable for #enum_name { + fn from_wasm(js: i32) -> Self { + #enum_name::from(super::#super_enum_name::from_i32(js).unwrap()) + } + } + + impl IntoWasmable for #enum_name { + fn into_wasm(self) -> i32 { + self as i32 + } + } + + impl From for #enum_name { + fn from(other: i32) -> Self { + #enum_name::from(super::#super_enum_name::from_i32(other).unwrap()) + } + } + + impl Into for #enum_name { + fn into(self) -> i32 { + self as i32 + } + } + + impl ::usdpl_front::wasm::FromWasmStreamableType for #enum_name { + fn from_wasm_streamable(js: JsValue) -> Result { + if let Some(float) = js.as_f64() { + Ok(Self::from_wasm(float as i32)) + } else { + Err(::usdpl_front::wasm::WasmStreamableConversionError::UnexpectedType { + expected: ::usdpl_front::wasm::JsType::Number, + got: ::usdpl_front::wasm::JsType::guess(&js), + }) + } + } + } + + impl ::usdpl_front::wasm::IntoWasmStreamableType for #enum_name { + fn into_wasm_streamable(self) -> JsValue { + JsValue::from(self.into_wasm()) + } + } + } +} + +fn generate_service_io_types( + service: &Service, + fds: &FileDescriptorSet, +) -> proc_macro2::TokenStream { + let mut gen_types = Vec::with_capacity(service.methods.len() * 2); + let mut gen_enums = Vec::new(); + let mut handled_enums = HashSet::new(); + let mut handled_types = HashSet::new(); + let mut known_maps = HashSet::new(); + let mut seen_super_enums = HashSet::new(); + for method in &service.methods { + if let Some(input_message) = find_message_type(&method.input_type, &service.package, fds) { + let msg_name = input_message + .name + .clone() + .expect("Protobuf message name required"); + if !handled_types.contains(&msg_name) { + handled_types.insert(msg_name); + gen_types.push(generate_wasm_struct_interop( + input_message, + &mut handled_enums, + &mut handled_types, + &mut known_maps, + &mut seen_super_enums, + false, + &service.name, + )); + } + } else if let Some(input_enum) = find_enum_type(&method.input_type, &service.package, fds) { + let enum_name = input_enum + .name + .clone() + .expect("Protobuf enum name required"); + if !handled_enums.contains(&enum_name) { + handled_enums.insert(enum_name); + gen_enums.push(generate_wasm_enum_interop( + input_enum, + &service.name, + &mut seen_super_enums, + )); + } + } else { + panic!( + "Cannot find input proto type/message {}/{} for method {}", + service.package, method.input_type, method.name + ); + } + + if let Some(output_message) = find_message_type(&method.output_type, &service.package, fds) + { + let msg_name = output_message + .name + .clone() + .expect("Protobuf message name required"); + if !handled_types.contains(&msg_name) { + handled_types.insert(msg_name); + gen_types.push(generate_wasm_struct_interop( + output_message, + &mut handled_enums, + &mut handled_types, + &mut known_maps, + &mut seen_super_enums, + true, + &service.name, + )); + } + } else if let Some(output_enum) = find_enum_type(&method.output_type, &service.package, fds) + { + let enum_name = output_enum + .name + .clone() + .expect("Protobuf enum name required"); + if !handled_enums.contains(&enum_name) { + handled_enums.insert(enum_name); + gen_enums.push(generate_wasm_enum_interop( + output_enum, + &service.name, + &mut seen_super_enums, + )); + } + } else { + panic!( + "Cannot find output proto type/message {}/{} for method {}", + service.package, method.output_type, method.name + ); + } + } + + // always generate all enums, since they aren't encountered (ever, afaik) when generating message structs + for file in &fds.file { + if let Some(pkg) = &file.package { + if pkg == &service.package { + for enum_type in &file.enum_type { + let enum_name = enum_type.name.clone().expect("Protobuf enum name required"); + if !handled_enums.contains(&enum_name) { + handled_enums.insert(enum_name); + gen_enums.push(generate_wasm_enum_interop( + enum_type, + &service.name, + &mut seen_super_enums, + )); + } + } + } + } + } + quote::quote! { + #(#gen_types)* + + #(#gen_enums)* + } +} + +impl IServiceGenerator for WasmServiceGenerator { + fn generate(&mut self, service: Service) -> proc_macro2::TokenStream { + let lock = self.shared.lock().expect("Cannot lock shared state"); + let fds = lock + .fds + .as_ref() + .expect("FileDescriptorSet required for WASM service generator"); + let service_struct_name = quote::format_ident!("{}Client", service.name); + let service_js_name = quote::format_ident!("{}", service.name); + let service_str_name = service.name.clone(); + let service_methods = generate_service_methods(&service, fds); + let service_types = generate_service_io_types(&service, fds); + let mod_name = quote::format_ident!("js_{}", service.name.to_lowercase()); + quote::quote! { + mod #mod_name { + #![allow(dead_code, unused_imports)] + use usdpl_front::_helpers::wasm_bindgen::prelude::*; + use usdpl_front::_helpers::wasm_bindgen; + use usdpl_front::_helpers::wasm_bindgen_futures; + use usdpl_front::_helpers::js_sys; + use usdpl_front::_helpers::log; + use usdpl_front::_helpers::futures; + use usdpl_front::_helpers::futures::StreamExt; + use usdpl_front::_helpers::nrpc::ClientService; + use usdpl_front::wasm::{IntoWasmStreamableType, FromWasmStreamableType}; + + use usdpl_front::wasm::*; + + use usdpl_front::WebSocketHandler; + + #service_types + + /// WASM/JS-compatible wrapper of the Rust nRPC service + #[wasm_bindgen] + pub struct #service_js_name { + //#[wasm_bindgen(skip)] + service: super::#service_struct_name<'static, WebSocketHandler>, + } + + #[wasm_bindgen] + impl #service_js_name { + #[wasm_bindgen(constructor)] + pub fn new(port: u16) -> Self { + usdpl_front::init_usdpl(); + let implementation = super::#service_struct_name::new( + WebSocketHandler::new(port) + ); + log::info!("Initialized ws service {} on port {}", #service_str_name, port); + Self { + service: implementation, + } + } + + #service_methods + } + } + } + } +} diff --git a/usdpl-build/src/front/shared_state.rs b/usdpl-build/src/front/shared_state.rs new file mode 100644 index 0000000..fb098f3 --- /dev/null +++ b/usdpl-build/src/front/shared_state.rs @@ -0,0 +1,24 @@ +use std::sync::{Arc, Mutex}; + +use prost_types::FileDescriptorSet; + +#[derive(Clone)] +pub struct SharedState(Arc>); + +impl SharedState { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(SharedProtoData { fds: None }))) + } +} + +impl std::ops::Deref for SharedState { + type Target = Arc>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub struct SharedProtoData { + pub fds: Option, +} diff --git a/usdpl-build/src/lib.rs b/usdpl-build/src/lib.rs new file mode 100644 index 0000000..3fd10ac --- /dev/null +++ b/usdpl-build/src/lib.rs @@ -0,0 +1,7 @@ +pub mod back; +pub mod front; + +mod proto_files; +pub use proto_files::{ + all_proto_filenames, dump_protos, dump_protos_out, proto_builtins_out_path, proto_out_paths, +}; diff --git a/usdpl-build/src/proto_files.rs b/usdpl-build/src/proto_files.rs new file mode 100644 index 0000000..d15570e --- /dev/null +++ b/usdpl-build/src/proto_files.rs @@ -0,0 +1,86 @@ +use std::path::{Path, PathBuf}; + +struct IncludedFileStr<'a> { + filename: &'a str, + contents: &'a str, +} + +const ADDITIONAL_PROTOBUFS_ENV_VAR: &'static str = "USDPL_PROTOS_PATH"; + +const DEBUG_PROTO: IncludedFileStr<'static> = IncludedFileStr { + filename: "debug.proto", + contents: include_str!("../protos/debug.proto"), +}; + +const TRANSLATIONS_PROTO: IncludedFileStr<'static> = IncludedFileStr { + filename: "translations.proto", + contents: include_str!("../protos/translations.proto"), +}; + +const ALL_PROTOS: [IncludedFileStr<'static>; 2] = [DEBUG_PROTO, TRANSLATIONS_PROTO]; + +pub fn proto_builtins_out_path() -> PathBuf { + PathBuf::from(std::env::var("OUT_DIR").expect("Not in a build.rs context (missing $OUT_DIR)")) + .join("protos") +} + +pub fn proto_out_paths(additionals: impl Iterator) -> impl Iterator { + std::iter::once(proto_builtins_out_path()) + .map(|x| x.to_str().unwrap().to_owned()) + .chain(custom_protos_dirs(additionals).into_iter()) +} + +fn custom_protos_dirs(additionals: impl Iterator) -> Vec { + let dirs = std::env::var(ADDITIONAL_PROTOBUFS_ENV_VAR).unwrap_or_else(|_| "".to_owned()); + dirs.split(':') + .filter(|x| std::fs::read_dir(x).is_ok()) + .map(|x| x.to_owned()) + .chain(additionals) + .collect() +} + +fn custom_protos_filenames() -> Vec { + let dirs = std::env::var(ADDITIONAL_PROTOBUFS_ENV_VAR).unwrap_or_else(|_| "".to_owned()); + dirs.split(':') + .map(std::fs::read_dir) + .filter(|x| x.is_ok()) + .flat_map(|x| x.unwrap()) + .filter(|x| x.is_ok()) + .map(|x| x.unwrap().path()) + .filter(|x| { + if let Some(ext) = x.extension() { + ext.to_ascii_lowercase() == "proto" && x.is_file() + } else { + false + } + }) + .filter_map(|x| x.to_str().map(|x| x.to_owned())) + .collect() +} + +pub fn all_proto_filenames( + p: impl AsRef + 'static, + additionals: impl Iterator, +) -> impl Iterator { + //let p = p.as_ref(); + ALL_PROTOS + .iter() + .map(move |x| p.as_ref().join(x.filename).to_str().unwrap().to_owned()) + .chain(custom_protos_filenames()) + .chain(additionals) +} + +pub fn dump_protos(p: impl AsRef) -> std::io::Result<()> { + let p = p.as_ref(); + for f in ALL_PROTOS { + let fullpath = p.join(f.filename); + std::fs::write(fullpath, f.contents)?; + } + Ok(()) +} + +pub fn dump_protos_out() -> std::io::Result<()> { + let path = proto_builtins_out_path(); + std::fs::create_dir_all(&path)?; + dump_protos(&path) +} diff --git a/usdpl-core/Cargo.toml b/usdpl-core/Cargo.toml index 5efa156..e9c1e5e 100644 --- a/usdpl-core/Cargo.toml +++ b/usdpl-core/Cargo.toml @@ -1,22 +1,22 @@ [package] name = "usdpl-core" -version = "0.10.0" +version = "0.11.0" +authors = ["NGnius "] edition = "2021" license = "GPL-3.0-only" -repository = "https://github.com/NGnius/usdpl-rs" -readme = "README.md" -description = "Universal Steam Deck Plugin Library core" +repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs" +readme = "../README.md" +description = "Universal Steam Deck Plugin Library core designed for all architectures" [features] default = [] decky = [] -crankshaft = [] encrypt = ["aes-gcm-siv"] -translate = [] [dependencies] base64 = "0.13" aes-gcm-siv = { version = "0.10", optional = true, default-features = false, features = ["alloc", "aes"] } +# nrpc = "0.2" [dev-dependencies] hex-literal = "0.3.4" diff --git a/usdpl-core/src/api_common/target.rs b/usdpl-core/src/api_common/target.rs index ed7f3ca..d34e502 100644 --- a/usdpl-core/src/api_common/target.rs +++ b/usdpl-core/src/api_common/target.rs @@ -4,8 +4,6 @@ pub enum Platform { Any, /// Decky aka PluginLoader platform Decky, - /// Crankshaft platform - Crankshaft, } impl Platform { @@ -16,10 +14,6 @@ impl Platform { { Self::Decky } - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - { - Self::Crankshaft - } #[cfg(not(any(feature = "decky", feature = "crankshaft")))] { Self::Any @@ -32,7 +26,6 @@ impl std::fmt::Display for Platform { match self { Self::Any => write!(f, "any"), Self::Decky => write!(f, "decky"), - Self::Crankshaft => write!(f, "crankshaft"), } } } diff --git a/usdpl-core/src/api_crankshaft/mod.rs b/usdpl-core/src/api_crankshaft/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/usdpl-core/src/api_crankshaft/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/usdpl-core/src/lib.rs b/usdpl-core/src/lib.rs index 7090ffa..18ddfb9 100644 --- a/usdpl-core/src/lib.rs +++ b/usdpl-core/src/lib.rs @@ -2,29 +2,18 @@ //! This contains serialization functionality and networking datatypes. #![warn(missing_docs)] -mod remote_call; - -#[cfg(not(any(feature = "decky", feature = "crankshaft")))] +#[cfg(not(any(feature = "decky")))] mod api_any; mod api_common; -#[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] -mod api_crankshaft; -#[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] +#[cfg(all(feature = "decky", not(any(feature = "any"))))] mod api_decky; -pub mod serdes; -pub mod socket; - -pub use remote_call::{RemoteCall, RemoteCallResponse}; - /// USDPL core API. /// This contains functionality used in both the back-end and front-end. pub mod api { - #[cfg(not(any(feature = "decky", feature = "crankshaft")))] + #[cfg(not(any(feature = "decky")))] pub use super::api_any::*; pub use super::api_common::*; - #[cfg(all(feature = "crankshaft", not(any(feature = "decky"))))] - pub use super::api_crankshaft::*; - #[cfg(all(feature = "decky", not(any(feature = "crankshaft"))))] + #[cfg(all(feature = "decky", not(any(feature = "any"))))] pub use super::api_decky::*; } diff --git a/usdpl-core/src/remote_call.rs b/usdpl-core/src/remote_call.rs deleted file mode 100644 index e189304..0000000 --- a/usdpl-core/src/remote_call.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::io::{Read, Write}; - -use crate::serdes::{DumpError, Dumpable, LoadError, Loadable, Primitive}; - -/// Remote call packet representing a function to call on the back-end, sent from the front-end -pub struct RemoteCall { - /// The call id assigned by the front-end - pub id: u64, - /// The function's name - pub function: String, - /// The function's input parameters - pub parameters: Vec, -} - -impl Loadable for RemoteCall { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let (id_num, len0) = u64::load(buffer)?; - let (function_name, len1) = String::load(buffer)?; - let (params, len2) = Vec::::load(buffer)?; - Ok(( - Self { - id: id_num, - function: function_name, - parameters: params, - }, - len0 + len1 + len2, - )) - } -} - -impl Dumpable for RemoteCall { - fn dump(&self, buffer: &mut dyn Write) -> Result { - let len0 = self.id.dump(buffer)?; - let len1 = self.function.dump(buffer)?; - let len2 = self.parameters.dump(buffer)?; - Ok(len0 + len1 + len2) - } -} - -/// Remote call response packet representing the response from a remote call after the back-end has executed it. -pub struct RemoteCallResponse { - /// The call id from the RemoteCall - pub id: u64, - /// The function's result - pub response: Vec, -} - -impl Loadable for RemoteCallResponse { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let (id_num, len0) = u64::load(buffer)?; - let (response_var, len1) = Vec::::load(buffer)?; - Ok(( - Self { - id: id_num, - response: response_var, - }, - len0 + len1, - )) - } -} - -impl Dumpable for RemoteCallResponse { - fn dump(&self, buffer: &mut dyn Write) -> Result { - let len0 = self.id.dump(buffer)?; - let len1 = self.response.dump(buffer)?; - Ok(len0 + len1) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn remote_call_idempotence_test() { - let call = RemoteCall{ - id: 42, - function: "something very long just in case this causes unexpected issues".into(), - parameters: vec!["param1".into(), 42f64.into()], - }; - - let mut buffer = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - let len = call.dump_base64(&mut buffer).unwrap(); - - println!("base64 dumped: `{}` (len: {})", buffer, len); - - let (loaded_call, loaded_len) = RemoteCall::load_base64(buffer.as_bytes()).unwrap(); - assert_eq!(len, loaded_len, "Expected load and dump lengths to match"); - - assert_eq!(loaded_call.id, call.id, "RemoteCall.id does not match"); - assert_eq!(loaded_call.function, call.function, "RemoteCall.function does not match"); - if let Primitive::String(loaded) = &loaded_call.parameters[0] { - if let Primitive::String(original) = &call.parameters[0] { - assert_eq!(loaded, original, "RemoteCall.parameters[0] does not match"); - } else { - panic!("Original call parameter 0 is not String") - } - } else { - panic!("Loaded call parameter 0 is not String") - } - if let Primitive::F64(loaded) = &loaded_call.parameters[1] { - if let Primitive::F64(original) = &call.parameters[1] { - assert_eq!(loaded, original, "RemoteCall.parameters[1] does not match"); - } else { - panic!("Original call parameter 1 is not f64") - } - } else { - panic!("Loaded call parameter 1 is not f64") - } - } -} diff --git a/usdpl-core/src/serdes/dump_impl.rs b/usdpl-core/src/serdes/dump_impl.rs deleted file mode 100644 index a74f08c..0000000 --- a/usdpl-core/src/serdes/dump_impl.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::io::Write; - -use super::{DumpError, Dumpable}; - -impl Dumpable for String { - fn dump(&self, buffer: &mut dyn Write) -> Result { - let str_bytes = self.as_bytes(); - let len_bytes = (str_bytes.len() as u32).to_le_bytes(); - let size1 = buffer.write(&len_bytes).map_err(DumpError::Io)?; - let size2 = buffer.write(&str_bytes).map_err(DumpError::Io)?; - Ok(size1 + size2) - } -} - -impl Dumpable for Vec { - fn dump(&self, buffer: &mut dyn Write) -> Result { - let len_bytes = (self.len() as u32).to_le_bytes(); - let mut total = buffer.write(&len_bytes).map_err(DumpError::Io)?; - for obj in self.iter() { - let len = obj.dump(buffer)?; - total += len; - } - Ok(total) - } -} - -impl Dumpable for (T0, T1) { - fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? - + self.1.dump(buffer)? - ) - } -} - -impl Dumpable for (T0, T1, T2) { - fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? - + self.1.dump(buffer)? - + self.2.dump(buffer)? - ) - } -} - -impl Dumpable for (T0, T1, T2, T3) { - fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? - + self.1.dump(buffer)? - + self.2.dump(buffer)? - + self.3.dump(buffer)? - ) - } -} - -impl Dumpable for (T0, T1, T2, T3, T4) { - fn dump(&self, buffer: &mut dyn Write) -> Result { - Ok( - self.0.dump(buffer)? - + self.1.dump(buffer)? - + self.2.dump(buffer)? - + self.3.dump(buffer)? - + self.4.dump(buffer)? - ) - } -} - -impl Dumpable for bool { - fn dump(&self, buffer: &mut dyn Write) -> Result { - buffer.write(&[*self as u8]).map_err(DumpError::Io) - } -} - -impl Dumpable for u8 { - fn dump(&self, buffer: &mut dyn Write) -> Result { - buffer.write(&[*self]).map_err(DumpError::Io) - } -} - -/*impl Dumpable for i8 { - fn dump(&self, buffer: &mut dyn Write) -> Result { - buffer.write(&self.to_le_bytes()).map_err(DumpError::Io) - } -}*/ - -macro_rules! int_impl { - ($type:ty) => { - impl Dumpable for $type { - fn dump(&self, buffer: &mut dyn Write) -> Result { - buffer.write(&self.to_le_bytes()).map_err(DumpError::Io) - } - } - }; -} - -int_impl! {u16} -int_impl! {u32} -int_impl! {u64} -int_impl! {u128} - -int_impl! {i8} -int_impl! {i16} -int_impl! {i32} -int_impl! {i64} -int_impl! {i128} - -int_impl! {f32} -int_impl! {f64} - -#[cfg(test)] -mod tests { - use super::*; - - macro_rules! test_impl { - ($fn_name:ident, $data:expr, $expected_len:literal, $expected_dump:expr) => { - #[test] - fn $fn_name() { - let data = $data; - let mut buffer = Vec::with_capacity(128); - let write_len = data.dump(&mut buffer).expect("Dump not ok"); - assert_eq!(write_len, $expected_len, "Wrong amount written"); - assert_eq!(&buffer[..write_len], $expected_dump); - println!("Dumped {:?}", buffer.as_slice()); - } - }; - } - - test_impl! {string_dump_test, "test".to_string(), 8, &[4, 0, 0, 0, 116, 101, 115, 116]} - - test_impl! { - vec_dump_test, - vec![ - "".to_string(), - "test1".to_string(), - "test2".to_string() - ], - 26, - &[3, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 5, 0, 0, 0, 116, 101, 115, 116, 50] - } - - test_impl! {tuple2_dump_test, (0u8, 1u8), 2, &[0, 1]} - test_impl! {tuple3_dump_test, (0u8, 1u8, 2u8), 3, &[0, 1, 2]} - test_impl! {tuple4_dump_test, (0u8, 1u8, 2u8, 3u8), 4, &[0, 1, 2, 3]} - test_impl! {tuple5_dump_test, (0u8, 1u8, 2u8, 3u8, 4u8), 5, &[0, 1, 2, 3, 4]} - - test_impl! {bool_true_dump_test, true, 1, &[1]} - test_impl! {bool_false_dump_test, false, 1, &[0]} - - // testing macro-generated code isn't particularly useful, but do it anyway - - test_impl! {u8_dump_test, 42u8, 1, &[42]} - test_impl! {u16_dump_test, 42u16, 2, &[42, 0]} - test_impl! {u32_dump_test, 42u32, 4, &[42, 0, 0, 0]} - test_impl! {u64_dump_test, 42u64, 8, &[42, 0, 0, 0, 0, 0, 0, 0]} - test_impl! {u128_dump_test, 42u128, 16, &[42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} - - test_impl! {i8_dump_test, 42i8, 1, &[42]} - test_impl! {i16_dump_test, 42i16, 2, &[42, 0]} - test_impl! {i32_dump_test, 42i32, 4, &[42, 0, 0, 0]} - test_impl! {i64_dump_test, 42i64, 8, &[42, 0, 0, 0, 0, 0, 0, 0]} - test_impl! {i128_dump_test, 42i128, 16, &[42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]} - - test_impl! {f32_dump_test, 42f32, 4, &[0, 0, 40, 66]} - test_impl! {f64_dump_test, 42f64, 8, &[0, 0, 0, 0, 0, 0, 69, 64]} -} diff --git a/usdpl-core/src/serdes/load_impl.rs b/usdpl-core/src/serdes/load_impl.rs deleted file mode 100644 index 2e37097..0000000 --- a/usdpl-core/src/serdes/load_impl.rs +++ /dev/null @@ -1,195 +0,0 @@ -use std::io::Read; - -use super::{LoadError, Loadable}; - -impl Loadable for String { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut u32_bytes: [u8; 4] = [u8::MAX; 4]; - buffer.read_exact(&mut u32_bytes).map_err(LoadError::Io)?; - let str_size = u32::from_le_bytes(u32_bytes) as usize; - //let mut str_buf = String::with_capacity(str_size); - let mut str_buf = Vec::with_capacity(str_size); - let mut byte_buf = [u8::MAX; 1]; - for _ in 0..str_size { - buffer.read_exact(&mut byte_buf).map_err(LoadError::Io)?; - str_buf.push(byte_buf[0]); - } - //let size2 = buffer.read_to_string(&mut str_buf).map_err(LoadError::Io)?; - Ok(( - String::from_utf8(str_buf).map_err(|_| LoadError::InvalidData)?, - str_size + 4, - )) - } -} - -impl Loadable for Vec { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut u32_bytes: [u8; 4] = [u8::MAX; 4]; - buffer.read_exact(&mut u32_bytes).map_err(LoadError::Io)?; - let count = u32::from_le_bytes(u32_bytes) as usize; - let mut cursor = 4; - let mut items = Vec::with_capacity(count); - for _ in 0..count { - let (obj, len) = T::load(buffer)?; - cursor += len; - items.push(obj); - } - Ok((items, cursor)) - } -} - -impl Loadable for (T0, T1) { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let (t0, len0) = T0::load(buffer)?; - let (t1, len1) = T1::load(buffer)?; - Ok(( - (t0, t1), - len0 + len1 - )) - } -} - -impl Loadable for (T0, T1, T2) { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let (t0, len0) = T0::load(buffer)?; - let (t1, len1) = T1::load(buffer)?; - let (t2, len2) = T2::load(buffer)?; - Ok(( - (t0, t1, t2), - len0 + len1 + len2 - )) - } -} - -impl Loadable for (T0, T1, T2, T3) { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let (t0, len0) = T0::load(buffer)?; - let (t1, len1) = T1::load(buffer)?; - let (t2, len2) = T2::load(buffer)?; - let (t3, len3) = T3::load(buffer)?; - Ok(( - (t0, t1, t2, t3), - len0 + len1 + len2 + len3 - )) - } -} - -impl Loadable for (T0, T1, T2, T3, T4) { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let (t0, len0) = T0::load(buffer)?; - let (t1, len1) = T1::load(buffer)?; - let (t2, len2) = T2::load(buffer)?; - let (t3, len3) = T3::load(buffer)?; - let (t4, len4) = T4::load(buffer)?; - Ok(( - (t0, t1, t2, t3, t4), - len0 + len1 + len2 + len3 + len4 - )) - } -} - -impl Loadable for bool { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut byte = [u8::MAX; 1]; - buffer.read_exact(&mut byte).map_err(LoadError::Io)?; - Ok((byte[0] != 0, 1)) - } -} - -impl Loadable for u8 { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut byte = [u8::MAX; 1]; - buffer.read_exact(&mut byte).map_err(LoadError::Io)?; - Ok((byte[0], 1)) - } -} - -impl Loadable for i8 { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut byte = [u8::MAX; 1]; - buffer.read_exact(&mut byte).map_err(LoadError::Io)?; - Ok((i8::from_le_bytes(byte), 1)) - } -} - -macro_rules! int_impl { - ($type:ty, $size:literal) => { - impl Loadable for $type { - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut bytes: [u8; $size] = [u8::MAX; $size]; - buffer.read_exact(&mut bytes).map_err(LoadError::Io)?; - let i = <$type>::from_le_bytes(bytes); - Ok((i, $size)) - } - } - }; -} - -int_impl! {u16, 2} -int_impl! {u32, 4} -int_impl! {u64, 8} -int_impl! {u128, 16} - -int_impl! {i16, 2} -int_impl! {i32, 4} -int_impl! {i64, 8} -int_impl! {i128, 16} - -int_impl! {f32, 4} -int_impl! {f64, 8} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Cursor; - - macro_rules! test_impl { - ($fn_name:ident, $data:expr, $type:ty, $expected_len:literal, $expected_load:expr) => { - #[test] - fn $fn_name() { - let buffer_data = $data; - let mut buffer = Vec::with_capacity(buffer_data.len()); - buffer.extend_from_slice(&buffer_data); - let (obj, read_len) = <$type>::load(&mut Cursor::new(buffer)).expect("Load not ok"); - assert_eq!(read_len, $expected_len, "Wrong amount read"); - assert_eq!(obj, $expected_load, "Loaded value not as expected"); - println!("Loaded {:?}", obj); - } - }; - } - - test_impl! {string_load_test, [4u8, 0, 0, 0, 116, 101, 115, 116, 0, 128], String, 8, "test"} - test_impl! { - vec_load_test, - [3u8, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 5, 0, 0, 0, 116, 101, 115, 116, 50], - Vec, - 26, - vec![ - "".to_string(), - "test1".to_string(), - "test2".to_string() - ] - } - - test_impl! {tuple2_load_test, [0, 1], (u8, u8), 2, (0, 1)} - - test_impl! {bool_true_load_test, [1], bool, 1, true} - test_impl! {bool_false_load_test, [0], bool, 1, false} - - // testing macro-generated code isn't particularly useful, but do it anyway - - test_impl! {u8_load_test, [42], u8, 1, 42u8} - test_impl! {u16_load_test, [42, 0], u16, 2, 42u16} - test_impl! {u32_load_test, [42, 0, 0, 0], u32, 4, 42u32} - test_impl! {u64_load_test, [42, 0, 0, 0, 0, 0, 0, 0], u64, 8, 42u64} - test_impl! {u128_load_test, [42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], u128, 16, 42u128} - - test_impl! {i8_load_test, [42], i8, 1, 42i8} - test_impl! {i16_load_test, [42, 0], i16, 2, 42i16} - test_impl! {i32_load_test, [42, 0, 0, 0], i32, 4, 42i32} - test_impl! {i64_load_test, [42, 0, 0, 0, 0, 0, 0, 0], i64, 8, 42i64} - test_impl! {i128_load_test, [42, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], i128, 16, 42i128} - - test_impl! {f32_load_test, [0, 0, 40, 66], f32, 4, 42f32} - test_impl! {f64_load_test, [0, 0, 0, 0, 0, 0, 69, 64], f64, 8, 42f64} -} diff --git a/usdpl-core/src/serdes/mod.rs b/usdpl-core/src/serdes/mod.rs deleted file mode 100644 index f911e6d..0000000 --- a/usdpl-core/src/serdes/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Serialization and deserialization functionality. -//! Little endian is preferred. - -mod dump_impl; -mod load_impl; -mod primitive; -mod traits; - -pub use primitive::Primitive; -pub use traits::{DumpError, Dumpable, LoadError, Loadable}; diff --git a/usdpl-core/src/serdes/primitive.rs b/usdpl-core/src/serdes/primitive.rs deleted file mode 100644 index d652ea1..0000000 --- a/usdpl-core/src/serdes/primitive.rs +++ /dev/null @@ -1,162 +0,0 @@ -use std::io::{Read, Write}; -use super::{DumpError, Dumpable, LoadError, Loadable}; - -/// Primitive types supported for communication between the USDPL back- and front-end. -/// These are used for sending over the TCP connection. -pub enum Primitive { - /// Null or unsupported object - Empty, - /// String-like - String(String), - /// f32 - F32(f32), - /// f64 - F64(f64), - /// u32 - U32(u32), - /// u64 - U64(u64), - /// i32 - I32(i32), - /// i64 - I64(i64), - /// boolean - Bool(bool), - /// Non-primitive in Json format - Json(String), -} - -impl Primitive { - /// Discriminant -- first byte of a dumped primitive - const fn discriminant(&self) -> u8 { - match self { - Self::Empty => 1, - Self::String(_) => 2, - Self::F32(_) => 3, - Self::F64(_) => 4, - Self::U32(_) => 5, - Self::U64(_) => 6, - Self::I32(_) => 7, - Self::I64(_) => 8, - Self::Bool(_) => 9, - Self::Json(_) => 10, - } - } -} - -impl Loadable for Primitive { - fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut discriminant_buf = [u8::MAX; 1]; - buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?; - let mut result: (Self, usize) = match discriminant_buf[0] { - //0 => (None, 0), - 1 => (Self::Empty, 0), - 2 => String::load(buf).map(|(obj, len)| (Self::String(obj), len))?, - 3 => f32::load(buf).map(|(obj, len)| (Self::F32(obj), len))?, - 4 => f64::load(buf).map(|(obj, len)| (Self::F64(obj), len))?, - 5 => u32::load(buf).map(|(obj, len)| (Self::U32(obj), len))?, - 6 => u64::load(buf).map(|(obj, len)| (Self::U64(obj), len))?, - 7 => i32::load(buf).map(|(obj, len)| (Self::I32(obj), len))?, - 8 => i64::load(buf).map(|(obj, len)| (Self::I64(obj), len))?, - 9 => bool::load(buf).map(|(obj, len)| (Self::Bool(obj), len))?, - 10 => String::load(buf).map(|(obj, len)| (Self::Json(obj), len))?, - _ => return Err(LoadError::InvalidData), - }; - result.1 += 1; - Ok(result) - } -} - -impl Dumpable for Primitive { - fn dump(&self, buf: &mut dyn Write) -> Result { - let size1 = buf.write(&[self.discriminant()]).map_err(DumpError::Io)?; - let result = match self { - Self::Empty => Ok(0), - Self::String(s) => s.dump(buf), - Self::F32(x) => x.dump(buf), - Self::F64(x) => x.dump(buf), - Self::U32(x) => x.dump(buf), - Self::U64(x) => x.dump(buf), - Self::I32(x) => x.dump(buf), - Self::I64(x) => x.dump(buf), - Self::Bool(x) => x.dump(buf), - Self::Json(x) => x.dump(buf), - }?; - Ok(size1 + result) - } -} - -impl std::convert::Into for &str { - fn into(self) -> Primitive { - Primitive::String(self.to_string()) - } -} - -impl std::convert::Into for () { - fn into(self) -> Primitive { - Primitive::Empty - } -} - -macro_rules! into_impl { - ($type:ty, $variant:ident) => { - impl std::convert::Into for $type { - fn into(self) -> Primitive { - Primitive::$variant(self) - } - } - } -} - -into_impl! {String, String} -into_impl! {bool, Bool} - -into_impl! {u32, U32} -into_impl! {u64, U64} - -into_impl! {i32, I32} -into_impl! {i64, I64} - -into_impl! {f32, F32} -into_impl! {f64, F64} - -#[cfg(test)] -mod tests { - use super::*; - use std::io::Cursor; - - #[test] - fn string_idempotence_test() { - let data = "Test"; - let primitive = Primitive::String(data.to_string()); - let mut buffer = Vec::with_capacity(128); - let write_len = primitive.dump(&mut buffer).expect("Dump not ok"); - let (obj, read_len) = Primitive::load(&mut Cursor::new(buffer)).expect("Load not ok"); - assert_eq!( - write_len, read_len, - "Amount written and amount read do not match" - ); - if let Primitive::String(result) = obj { - assert_eq!(data, result, "Data written and read does not match"); - } else { - panic!("Read non-string primitive"); - } - } - - #[test] - fn empty_idempotence_test() { - let primitive = Primitive::Empty; - let mut buffer = Vec::with_capacity(128); - let write_len = primitive.dump(&mut buffer).expect("Dump not ok"); - let (obj, read_len) = Primitive::load(&mut Cursor::new(buffer)).expect("Load not ok"); - assert_eq!( - write_len, read_len, - "Amount written and amount read do not match" - ); - if let Primitive::Empty = obj { - //assert_eq!(data, result, "Data written and read does not match"); - } else { - panic!("Read non-string primitive"); - } - } -} diff --git a/usdpl-core/src/serdes/traits.rs b/usdpl-core/src/serdes/traits.rs deleted file mode 100644 index 07bc2fd..0000000 --- a/usdpl-core/src/serdes/traits.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::io::{Read, Write, Cursor}; -use base64::{decode_config_buf, encode_config_buf, Config}; - -const B64_CONF: Config = Config::new(base64::CharacterSet::Standard, true); - -#[cfg(feature = "encrypt")] -const ASSOCIATED_DATA: &[u8] = b"usdpl-core-data"; - -#[cfg(feature = "encrypt")] -use aes_gcm_siv::aead::{AeadInPlace, NewAead}; - -/// Errors from Loadable::load -#[derive(Debug)] -pub enum LoadError { - /// Buffer smaller than expected - TooSmallBuffer, - /// Unexpected/corrupted data encountered - InvalidData, - /// Encrypted data cannot be decrypted - #[cfg(feature = "encrypt")] - DecryptionError, - /// Read error - Io(std::io::Error), - /// Unimplemented - #[cfg(debug_assertions)] - Todo, -} - -impl std::fmt::Display for LoadError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::TooSmallBuffer => write!(f, "LoadError: TooSmallBuffer"), - Self::InvalidData => write!(f, "LoadError: InvalidData"), - #[cfg(feature = "encrypt")] - Self::DecryptionError => write!(f, "LoadError: DecryptionError"), - Self::Io(err) => write!(f, "LoadError: Io({})", err), - #[cfg(debug_assertions)] - Self::Todo => write!(f, "LoadError: TODO!"), - } - } -} - -/// Load an object from the buffer -pub trait Loadable: Sized { - /// Read the buffer, building the object and returning the amount of bytes read. - /// If anything is wrong with the buffer, Err should be returned. - fn load(buffer: &mut dyn Read) -> Result<(Self, usize), LoadError>; - - /// Load data from a base64-encoded buffer - fn load_base64(buffer: &[u8]) -> Result<(Self, usize), LoadError> { - let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - decode_config_buf(buffer, B64_CONF, &mut buffer2) - .map_err(|_| LoadError::InvalidData)?; - let mut cursor = Cursor::new(buffer2); - Self::load(&mut cursor) - } - - /// Load data from an encrypted base64-encoded buffer - #[cfg(feature = "encrypt")] - fn load_encrypted(buffer: &[u8], key: &[u8], nonce: &[u8]) -> Result<(Self, usize), LoadError> { - //println!("encrypted buffer: {}", String::from_utf8(buffer.to_vec()).unwrap()); - let key = aes_gcm_siv::Key::from_slice(key); - let cipher = aes_gcm_siv::Aes256GcmSiv::new(key); - let nonce = aes_gcm_siv::Nonce::from_slice(nonce); - let mut decoded_buf = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - base64::decode_config_buf(buffer, B64_CONF, &mut decoded_buf) - .map_err(|_| LoadError::InvalidData)?; - //println!("Decoded buf: {:?}", decoded_buf); - cipher.decrypt_in_place(nonce, ASSOCIATED_DATA, &mut decoded_buf).map_err(|_| LoadError::DecryptionError)?; - //println!("Decrypted buf: {:?}", decoded_buf); - let mut cursor = Cursor::new(decoded_buf); - Self::load(&mut cursor) - } -} - -/// Errors from Dumpable::dump -#[derive(Debug)] -pub enum DumpError { - /// Buffer not big enough to dump data into - TooSmallBuffer, - /// Data cannot be dumped - Unsupported, - /// Data cannot be encrypted - #[cfg(feature = "encrypt")] - EncryptionError, - /// Write error - Io(std::io::Error), - /// Unimplemented - #[cfg(debug_assertions)] - Todo, -} - -impl std::fmt::Display for DumpError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Self::TooSmallBuffer => write!(f, "DumpError: TooSmallBuffer"), - Self::Unsupported => write!(f, "DumpError: Unsupported"), - #[cfg(feature = "encrypt")] - Self::EncryptionError => write!(f, "DumpError: EncryptionError"), - Self::Io(err) => write!(f, "DumpError: Io({})", err), - #[cfg(debug_assertions)] - Self::Todo => write!(f, "DumpError: TODO!"), - } - } -} - -/// Dump an object into the buffer -pub trait Dumpable { - /// Write the object to the buffer, returning the amount of bytes written. - /// If anything is wrong, false should be returned. - fn dump(&self, buffer: &mut dyn Write) -> Result; - - /// Dump data as base64-encoded. - /// Useful for transmitting data as text. - fn dump_base64(&self, buffer: &mut String) -> Result { - let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - let len = self.dump(&mut buffer2)?; - encode_config_buf(&buffer2[..len], B64_CONF, buffer); - Ok(len) - } - - /// Dump data as an encrypted base64-encoded buffer - #[cfg(feature = "encrypt")] - fn dump_encrypted(&self, buffer: &mut Vec, key: &[u8], nonce: &[u8]) -> Result { - let mut buffer2 = Vec::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - let size = self.dump(&mut buffer2)?; - buffer2.truncate(size); - //println!("Buf: {:?}", buffer2); - let key = aes_gcm_siv::Key::from_slice(key); - let cipher = aes_gcm_siv::Aes256GcmSiv::new(key); - let nonce = aes_gcm_siv::Nonce::from_slice(nonce); - cipher.encrypt_in_place(nonce, ASSOCIATED_DATA, &mut buffer2).map_err(|_| DumpError::EncryptionError)?; - //println!("Encrypted slice: {:?}", &buffer2); - let mut base64_buf = String::with_capacity(crate::socket::PACKET_BUFFER_SIZE); - encode_config_buf(buffer2.as_slice(), B64_CONF, &mut base64_buf); - //println!("base64 len: {}", base64_buf.as_bytes().len()); - buffer.extend_from_slice(base64_buf.as_bytes()); - //let string = String::from_utf8(buffer.as_slice().to_vec()).unwrap(); - //println!("Encoded slice: {}", string); - Ok(base64_buf.len()) - } -} diff --git a/usdpl-core/src/socket.rs b/usdpl-core/src/socket.rs deleted file mode 100644 index 5a28762..0000000 --- a/usdpl-core/src/socket.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! Web messaging -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; -use std::io::{Read, Write}; - -use crate::serdes::{DumpError, Dumpable, LoadError, Loadable}; -use crate::{RemoteCall, RemoteCallResponse}; - -/// Host IP address for web browsers -pub const HOST_STR: &str = "localhost"; -/// Host IP address -pub const HOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); - -/// Standard max packet size -pub const PACKET_BUFFER_SIZE: usize = 1024; -/// Encryption nonce size -pub const NONCE_SIZE: usize = 12; - -/// Address and port -#[inline] -pub fn socket_addr(port: u16) -> SocketAddr { - SocketAddr::V4(SocketAddrV4::new(HOST, port)) -} - -/// Accepted Packet types and the data they contain -pub enum Packet { - /// A remote call - Call(RemoteCall), - /// A reponse to a remote call - CallResponse(RemoteCallResponse), - /// Unused - KeepAlive, - /// Invalid - Invalid, - /// General message - Message(String), - /// Response to an unsupported packet - Unsupported, - /// Broken packet type, useful for testing - Bad, - /// Many packets merged into one - Many(Vec), - /// Translation data dump - #[cfg(feature = "translate")] - Translations(Vec<(String, Vec)>), - /// Request translations for language - #[cfg(feature = "translate")] - Language(String), -} - -impl Packet { - /// Byte representing the packet type -- the first byte of any packet in USDPL - const fn discriminant(&self) -> u8 { - match self { - Self::Call(_) => 1, - Self::CallResponse(_) => 2, - Self::KeepAlive => 3, - Self::Invalid => 4, - Self::Message(_) => 5, - Self::Unsupported => 6, - Self::Bad => 7, - Self::Many(_) => 8, - #[cfg(feature = "translate")] - Self::Translations(_) => 9, - #[cfg(feature = "translate")] - Self::Language(_) => 10, - } - } -} - -impl Loadable for Packet { - fn load(buf: &mut dyn Read) -> Result<(Self, usize), LoadError> { - let mut discriminant_buf = [u8::MAX; 1]; - buf.read_exact(&mut discriminant_buf).map_err(LoadError::Io)?; - let mut result: (Self, usize) = match discriminant_buf[0] { - //0 => (None, 0), - 1 => { - let (obj, len) = RemoteCall::load(buf)?; - (Self::Call(obj), len) - } - 2 => { - let (obj, len) = RemoteCallResponse::load(buf)?; - (Self::CallResponse(obj), len) - } - 3 => (Self::KeepAlive, 0), - 4 => (Self::Invalid, 0), - 5 => { - let (obj, len) = String::load(buf)?; - (Self::Message(obj), len) - } - 6 => (Self::Unsupported, 0), - 7 => return Err(LoadError::InvalidData), - 8 => { - let (obj, len) = <_>::load(buf)?; - (Self::Many(obj), len) - }, - #[cfg(feature = "translate")] - 9 => { - let (obj, len) = <_>::load(buf)?; - (Self::Translations(obj), len) - }, - #[cfg(feature = "translate")] - 10 => { - let (obj, len) = <_>::load(buf)?; - (Self::Language(obj), len) - }, - _ => return Err(LoadError::InvalidData), - }; - result.1 += 1; - Ok(result) - } -} - -impl Dumpable for Packet { - fn dump(&self, buf: &mut dyn Write) -> Result { - let size1 = buf.write(&[self.discriminant()]).map_err(DumpError::Io)?; - let result = match self { - Self::Call(c) => c.dump(buf), - Self::CallResponse(c) => c.dump(buf), - Self::KeepAlive => Ok(0), - Self::Invalid => Ok(0), - Self::Message(s) => s.dump(buf), - Self::Unsupported => Ok(0), - Self::Bad => return Err(DumpError::Unsupported), - Self::Many(v) => v.dump(buf), - #[cfg(feature = "translate")] - Self::Translations(tr) => tr.dump(buf), - #[cfg(feature = "translate")] - Self::Language(l) => l.dump(buf), - }?; - Ok(size1 + result) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(feature = "encrypt")] - #[test] - fn encryption_integration_test() { - let key = hex_literal::hex!("59C4E408F27250B3147E7724511824F1D28ED7BEF43CF7103ACE747F77A2B265"); - let nonce = [0u8; NONCE_SIZE]; - let packet = Packet::Call(RemoteCall{ - id: 42, - function: "test".into(), - parameters: Vec::new(), - }); - let mut buffer = Vec::with_capacity(PACKET_BUFFER_SIZE); - let len = packet.dump_encrypted(&mut buffer, &key, &nonce).unwrap(); - println!("buffer: {}", String::from_utf8(buffer.as_slice()[..len].to_vec()).unwrap()); - - let (packet_out, _len) = Packet::load_encrypted(&buffer.as_slice()[..len], &key, &nonce).unwrap(); - - if let Packet::Call(call_out) = packet_out { - if let Packet::Call(call_in) = packet { - assert_eq!(call_in.id, call_out.id, "Input and output packets do not match"); - assert_eq!(call_in.function, call_out.function, "Input and output packets do not match"); - assert_eq!(call_in.parameters.len(), call_out.parameters.len(), "Input and output packets do not match"); - } else { - panic!("Packet in not a Call"); - } - } else { - panic!("Packet out not a Call!"); - } - } -} diff --git a/usdpl-front/Cargo.toml b/usdpl-front/Cargo.toml index 81b42da..9dfc78c 100644 --- a/usdpl-front/Cargo.toml +++ b/usdpl-front/Cargo.toml @@ -1,27 +1,28 @@ [package] name = "usdpl-front" -version = "0.10.1" -authors = ["NGnius (Graham) "] +version = "0.11.0" +authors = ["NGnius "] edition = "2021" license = "GPL-3.0-only" -repository = "https://github.com/NGnius/usdpl-rs" -readme = "README.md" +repository = "https://git.ngni.us/NG-SD-Plugins/usdpl-rs" +readme = "../README.md" description = "Universal Steam Deck Plugin Library front-end designed for WASM" [lib] crate-type = ["cdylib", "rlib"] [features] -default = ["translate"] +default = [] decky = ["usdpl-core/decky"] -crankshaft = ["usdpl-core/crankshaft"] debug = ["console_error_panic_hook"] -encrypt = ["usdpl-core/encrypt", "obfstr", "hex"] -translate = ["usdpl-core/translate"] +#encrypt = ["usdpl-core/encrypt", "obfstr", "hex"] [dependencies] wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" +gloo-net = { version = "0.4", features = ["websocket"] } +futures = "0.3" +futures-channel = "0.3" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires @@ -36,13 +37,17 @@ web-sys = { version = "0.3", features = [ 'RequestMode', 'Response', 'Window', + 'console', ]} js-sys = { version = "0.3" } obfstr = { version = "0.3", optional = true } hex = { version = "0.4", optional = true } -usdpl-core = { version = "0.10", path = "../usdpl-core" } +nrpc = { version = "0.10", path = "../../nRPC/nrpc", default-features = false} +usdpl-core = { version = "0.11", path = "../usdpl-core" } +prost = "0.11" +log = "0.4" [dev-dependencies] wasm-bindgen-test = { version = "0.3.13" } diff --git a/usdpl-front/src/client_handler.rs b/usdpl-front/src/client_handler.rs new file mode 100644 index 0000000..eff4991 --- /dev/null +++ b/usdpl-front/src/client_handler.rs @@ -0,0 +1,168 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +use futures::{SinkExt, StreamExt, future::{select, Either}}; +use gloo_net::websocket::{futures::WebSocket, Message}; +use nrpc::{ClientHandler, ServiceError, ServiceClientStream, _helpers::async_trait, _helpers::bytes}; +use wasm_bindgen_futures::spawn_local; + +static LAST_ID: AtomicU64 = AtomicU64::new(0); + +/// Websocket client. +/// In most cases, this shouldn't be used directly, but generated code will use this. +pub struct WebSocketHandler { + port: u16, +} + +#[inline] +fn ws_is_alive(ws_state: &gloo_net::websocket::State) -> bool { + match ws_state { + gloo_net::websocket::State::Connecting | gloo_net::websocket::State::Open => true, + gloo_net::websocket::State::Closing | gloo_net::websocket::State::Closed => false, + } +} + +async fn send_recv_ws<'a>(mut tx: futures_channel::mpsc::Sender>, url: String, mut input: ServiceClientStream<'a, bytes::Bytes>) { + let ws = match WebSocket::open_with_protocol(&url, "usdpl-nrpc").map_err(|e| e.to_string()) { + Ok(x) => x, + Err(e) => { + log::error!("ws open error: {}", e); + tx.send(Err(e.to_string())).await.unwrap_or(()); + return; + } + }; + + + log::debug!("ws opened successfully with url `{}`", url); + + let (mut input_done, mut output_done) = (false, false); + let mut last_ws_state = ws.state(); + log::debug!("ws with url `{}` initial state: {:?}", url, last_ws_state); + let (mut ws_sink, mut ws_stream) = ws.split(); + let (mut left, mut right) = (input.next(), ws_stream.next()); + while ws_is_alive(&last_ws_state) { + if !input_done && !output_done { + log::debug!("Input and output streams are both alive"); + match select(left, right).await { + Either::Left((next, outstanding)) => { + log::debug!("Got message to send over websocket"); + if let Some(next) = next { + match next { + Ok(next) => { + if let Err(e) = ws_sink.send(Message::Bytes(next.into())).await { + tx.send(Err(e.to_string())).await.unwrap_or(()); + } + }, + Err(e) => tx.send(Err(e.to_string())).await.unwrap_or(()) + } + } else { + input_done = true; + } + right = outstanding; + left = input.next(); + }, + Either::Right((response, outstanding)) => { + log::debug!("Received message from websocket"); + if let Some(next) = response { + match next { + Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()), + Ok(_) => tx.send(Err("Message::Text not allowed".into())).await.unwrap_or(()), + Err(e) => tx.send(Err(e.to_string())).await.unwrap_or(()), + } + } else { + output_done = true; + } + left = outstanding; + let ws = ws_stream.reunite(ws_sink).unwrap(); + last_ws_state = ws.state(); + (ws_sink, ws_stream) = ws.split(); + right = ws_stream.next(); + } + } + } else if input_done { + log::debug!("Input stream is complete"); + if let Some(next) = right.await { + log::debug!("Received message from websocket"); + match next { + Ok(Message::Bytes(b)) => tx.send(Ok(b.into())).await.unwrap_or(()), + Ok(_) => tx.send(Err("Message::Text not allowed".into())).await.unwrap_or(()), + Err(e) => tx.send(Err(e.to_string())).await.unwrap_or(()), + } + } else { + output_done = true; + } + //left = outstanding; + let ws = ws_stream.reunite(ws_sink).unwrap(); + last_ws_state = ws.state(); + (ws_sink, ws_stream) = ws.split(); + right = ws_stream.next(); + } else { + // output_done is true + log::debug!("Output stream is complete"); + if let Some(next) = left.await { + log::debug!("Got message to send over websocket"); + match next { + Ok(next) => { + if let Err(e) = ws_sink.send(Message::Bytes(next.into())).await { + tx.send(Err(e.to_string())).await.unwrap_or(()); + } + }, + Err(e) => tx.send(Err(e.to_string())).await.unwrap_or(()) + } + } else { + input_done = true; + } + //right = outstanding; + let ws = ws_stream.reunite(ws_sink).unwrap(); + last_ws_state = ws.state(); + (ws_sink, ws_stream) = ws.split(); + left = input.next(); + right = ws_stream.next(); // this should always resolve to None (but compiler is unhappy without this) + } + } + + log::debug!("ws with url `{}` has closed", url); +} + +#[derive(Debug)] +struct ErrorStr(String); + +impl std::fmt::Display for ErrorStr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Error message: {}", self.0) + } +} + +impl std::error::Error for ErrorStr {} + +const CHANNEL_BOUND: usize = 4; + +impl WebSocketHandler { + /// Instantiate the web socket client for connecting on the specified port + pub fn new(port: u16) -> Self { + Self { port } + } +} + +#[async_trait::async_trait(?Send)] +impl ClientHandler<'static> for WebSocketHandler { + async fn call<'a: 'static>( + &self, + package: &str, + service: &str, + method: &str, + input: ServiceClientStream<'a, bytes::Bytes>, + ) -> Result, ServiceError> { + let id = LAST_ID.fetch_add(1, Ordering::SeqCst); + let url = format!( + "ws://usdpl-ws-{}.localhost:{}/{}.{}/{}", + id, self.port, package, service, method, + ); + log::debug!("doing send/receive on ws url `{}`", url); + let (tx, rx) = futures_channel::mpsc::channel(CHANNEL_BOUND); + spawn_local(send_recv_ws(tx, url, input)); + + Ok(Box::new(rx.map(|buf_result: Result| buf_result + .map(|buf| bytes::Bytes::from(buf)) + .map_err(|e| ServiceError::Method(Box::new(ErrorStr(e))))))) + } +} diff --git a/usdpl-front/src/connection.rs b/usdpl-front/src/connection.rs deleted file mode 100644 index a41979a..0000000 --- a/usdpl-front/src/connection.rs +++ /dev/null @@ -1,104 +0,0 @@ -//use std::net::TcpStream; -//use std::io::{Read, Write}; - -use wasm_bindgen::prelude::*; -use wasm_bindgen::JsCast; -use wasm_bindgen_futures::JsFuture; - -//use web_sys::{WebSocket, MessageEvent, ErrorEvent}; -use js_sys::JsString; -use web_sys::{Request, RequestInit, RequestMode, Response}; -//use wasm_rs_shared_channel::{Expects, spsc::{Receiver, Sender}}; - -use usdpl_core::serdes::{Dumpable, Loadable, Primitive}; -use usdpl_core::socket; - -#[cfg(feature = "encrypt")] -const NONCE: [u8; socket::NONCE_SIZE]= [0u8; socket::NONCE_SIZE]; - -pub async fn send_recv_packet( - id: u64, - packet: socket::Packet, - port: u16, - #[cfg(feature = "encrypt")] - key: Vec, -) -> Result { - - let mut opts = RequestInit::new(); - opts.method("POST"); - opts.mode(RequestMode::Cors); - - let url = format!("http://usdpl{}.{}:{}/usdpl/call", id, socket::HOST_STR, port); - - #[allow(unused_variables)] - let (buffer, len) = dump_to_buffer(packet, #[cfg(feature = "encrypt")] key.as_slice())?; - let string: String = String::from_utf8_lossy(buffer.as_slice()).into(); - #[cfg(feature="debug")] - crate::imports::console_log(&format!("Dumped base64 `{}` len:{}", string, len)); - opts.body(Some(&string.into())); - - let request = Request::new_with_str_and_init(&url, &opts)?; - - //request.headers().set("Accept", "text/base64")?; - //.set("Authorization", "wasm TODO_KEY")?; - - let window = web_sys::window().unwrap(); - let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; - - let resp: Response = resp_value.dyn_into()?; - let text = JsFuture::from(resp.text()?).await?; - let string: JsString = text.dyn_into()?; - - let rust_str = string.as_string().unwrap(); - #[cfg(feature="debug")] - crate::imports::console_log(&format!("Received base64 `{}` len:{}", rust_str, rust_str.len())); - - #[cfg(not(feature = "encrypt"))] - {Ok(socket::Packet::load_base64(rust_str.as_bytes()) - .map_err(super::convert::str_to_js)? - .0)} - - #[cfg(feature = "encrypt")] - {Ok(socket::Packet::load_encrypted(rust_str.as_bytes(), key.as_slice(), &NONCE) - .map_err(super::convert::str_to_js)? - .0)} -} - -pub async fn send_call( - id: u64, - packet: socket::Packet, - port: u16, - #[cfg(feature = "encrypt")] - key: Vec, -) -> Result, JsValue> { - let packet = send_recv_packet(id, packet, port, #[cfg(feature = "encrypt")] key).await?; - - match packet - { - socket::Packet::CallResponse(resp) => Ok(resp.response), - _ => { - //imports::console_warn(&format!("USDPL warning: Got non-call-response message from {}", resp.url())); - Err("Expected call response message, got something else".into()) - } - } -} - -#[cfg(feature = "encrypt")] -fn dump_to_buffer(packet: socket::Packet, key: &[u8]) -> Result<(Vec, usize), JsValue> { - let mut buffer = Vec::with_capacity(socket::PACKET_BUFFER_SIZE); - //buffer.extend_from_slice(&[0u8; socket::PACKET_BUFFER_SIZE]); - let len = packet - .dump_encrypted(&mut buffer, key, &NONCE) - .map_err(super::convert::str_to_js)?; - Ok((buffer, len)) -} - -#[cfg(not(feature = "encrypt"))] -fn dump_to_buffer(packet: socket::Packet) -> Result<(Vec, usize), JsValue> { - let mut buffer = String::with_capacity(socket::PACKET_BUFFER_SIZE); - //buffer.extend_from_slice(&[0u8; socket::PACKET_BUFFER_SIZE]); - let len = packet - .dump_base64(&mut buffer) - .map_err(super::convert::str_to_js)?; - Ok((buffer.as_bytes().to_vec(), len)) -} diff --git a/usdpl-front/src/console_logs.rs b/usdpl-front/src/console_logs.rs new file mode 100644 index 0000000..2b14974 --- /dev/null +++ b/usdpl-front/src/console_logs.rs @@ -0,0 +1,52 @@ +pub(crate) struct BuiltInLogger { + min_level: log::Level, +} + +impl BuiltInLogger { + pub const fn new(min: log::Level) -> Self { + Self { + min_level: min, + } + } +} + +impl log::Log for BuiltInLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= self.min_level + } + + fn log(&self, record: &log::Record) { + if self.enabled(record.metadata()) { + match record.level() { + log::Level::Error => web_sys::console::error_1(&fmt_msg(record).into()), + log::Level::Warn => web_sys::console::warn_1(&fmt_msg(record).into()), + log::Level::Info => web_sys::console::log_1(&fmt_msg(record).into()), + log::Level::Debug => web_sys::console::debug_1(&fmt_msg(record).into()), + log::Level::Trace => web_sys::console::debug_1(&fmt_msg(record).into()), + } + } + } + + fn flush(&self) {} +} + +fn fmt_msg(record: &log::Record) -> String { + #[cfg(feature = "debug")] + { format!("[{}]({}) {}", record.level(), file_line_info(record), record.args()) } + #[cfg(not(feature = "debug"))] + { format!("[{}]({}) {}", record.level(), module_line_info(record), record.args()) } +} + +#[cfg(feature = "debug")] +fn file_line_info(record: &log::Record) -> String { + let filepath = record.file().unwrap_or(""); + let line = record.line().map(|l| l.to_string()).unwrap_or_else(|| "line?".to_string()); + format!("{}:{}", filepath, line) +} + +#[cfg(not(feature = "debug"))] +fn module_line_info(record: &log::Record) -> String { + let target = record.target(); + let line = record.line().map(|l| l.to_string()).unwrap_or_else(|| "line?".to_string()); + format!("{}:{}", target, line) +} diff --git a/usdpl-front/src/convert.rs b/usdpl-front/src/convert.rs index b074ae6..a59e6fa 100644 --- a/usdpl-front/src/convert.rs +++ b/usdpl-front/src/convert.rs @@ -1,10 +1,10 @@ -use js_sys::JsString; -use js_sys::JSON::{parse, stringify}; +//use js_sys::JsString; +//use js_sys::JSON::{parse, stringify}; use wasm_bindgen::prelude::JsValue; -use usdpl_core::serdes::Primitive; +//use usdpl_core::serdes::Primitive; -pub(crate) fn primitive_to_js(primitive: Primitive) -> JsValue { +/*pub(crate) fn primitive_to_js(primitive: Primitive) -> JsValue { match primitive { Primitive::Empty => JsValue::null(), Primitive::String(s) => JsValue::from_str(&s), @@ -33,8 +33,16 @@ pub(crate) fn js_to_primitive(val: JsValue) -> Primitive { } else { Primitive::Empty } -} +}*/ -pub(crate) fn str_to_js(s: S) -> JsString { +/*pub(crate) fn str_to_js(s: S) -> JsString { s.to_string().into() +}*/ + +pub(crate) fn js_to_str(js: JsValue) -> String { + if let Some(s) = js.as_string() { + s + } else { + format!("{:?}", js) + } } diff --git a/usdpl-front/src/imports.rs b/usdpl-front/src/imports.rs deleted file mode 100644 index 8e13b48..0000000 --- a/usdpl-front/src/imports.rs +++ /dev/null @@ -1,16 +0,0 @@ -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -extern "C" { - #[cfg(feature = "debug")] - #[wasm_bindgen(js_namespace = console, js_name = log)] - pub fn console_log(s: &str); - - #[cfg(feature = "debug")] - #[wasm_bindgen(js_namespace = console, js_name = warn)] - pub fn console_warn(s: &str); - - #[cfg(feature = "debug")] - #[wasm_bindgen(js_namespace = console, js_name = error)] - pub fn console_error(s: &str); -} diff --git a/usdpl-front/src/lib.rs b/usdpl-front/src/lib.rs index 3516977..ebaf687 100644 --- a/usdpl-front/src/lib.rs +++ b/usdpl-front/src/lib.rs @@ -5,29 +5,33 @@ //! #![warn(missing_docs)] -mod connection; +mod client_handler; +pub use client_handler::WebSocketHandler; +mod console_logs; mod convert; -mod imports; +pub mod wasm; -use std::sync::atomic::{AtomicU64, Ordering}; +#[allow(missing_docs)] +pub mod _helpers { + pub use js_sys; + pub use wasm_bindgen; + pub use wasm_bindgen_futures; + pub use log; + pub use futures; + pub use nrpc; +} -use js_sys::Array; use wasm_bindgen::prelude::*; -use usdpl_core::{socket::Packet, RemoteCall}; -//const REMOTE_CALL_ID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); -//const REMOTE_PORT: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(31337); +#[cfg(feature = "debug")] +const DEFAULT_MIN_LEVEL: log::Level = log::Level::Trace; +#[cfg(not(feature = "debug"))] +const DEFAULT_MIN_LEVEL: log::Level = log::Level::Info; -static mut CTX: UsdplContext = UsdplContext { - port: 31337, - id: AtomicU64::new(0), - #[cfg(feature = "encrypt")] - key: Vec::new(), -}; +const DEFAULT_LOGGER: console_logs::BuiltInLogger = console_logs::BuiltInLogger::new(DEFAULT_MIN_LEVEL); static mut CACHE: Option> = None; -#[cfg(feature = "translate")] static mut TRANSLATIONS: Option>> = None; #[cfg(feature = "encrypt")] @@ -35,49 +39,42 @@ fn encryption_key() -> Vec { hex::decode(obfstr::obfstr!(env!("USDPL_ENCRYPTION_KEY"))).unwrap() } -//#[wasm_bindgen] -#[derive(Debug)] -struct UsdplContext { - port: u16, - id: AtomicU64, - #[cfg(feature = "encrypt")] - key: Vec, -} - -fn get_port() -> u16 { - unsafe { CTX.port } -} - -#[cfg(feature = "encrypt")] -fn get_key() -> Vec { - unsafe { CTX.key.clone() } -} - -fn increment_id() -> u64 { - let atomic = unsafe { &CTX.id }; - atomic.fetch_add(1, Ordering::SeqCst) -} +static INIT_DONE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); /// Initialize the front-end library -#[wasm_bindgen] -pub fn init_usdpl(port: u16) { - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); - //REMOTE_PORT.store(port, std::sync::atomic::Ordering::SeqCst); - unsafe { - CTX = UsdplContext { - port: port, - id: AtomicU64::new(0), - #[cfg(feature = "encrypt")] - key: encryption_key(), - }; - } +//#[wasm_bindgen] +pub fn init_usdpl() { + if !INIT_DONE.swap(true, std::sync::atomic::Ordering::SeqCst) { + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); - unsafe { - CACHE = Some(std::collections::HashMap::new()); + log::set_logger(&DEFAULT_LOGGER) + .map_err(|e| web_sys::console::error_1(&format!("Failed to setup USDPL logger: {}", e).into())) + .unwrap_or(()); + log::set_max_level(log::LevelFilter::Trace); + log::debug!("init_usdpl() log configured"); + + unsafe { + CACHE = Some(std::collections::HashMap::new()); + } + + log::info!("USDPL init succeeded: {}", build_info()); + } else { + log::info!("USDPL init was re-attempted"); } } +fn build_info() -> String { + format!("{} v{} ({}) for {} by {}, more: {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_LICENSE"), + target_usdpl(), + env!("CARGO_PKG_AUTHORS"), + env!("CARGO_PKG_REPOSITORY"), + ) +} + /// Get the targeted plugin framework, or "any" if unknown #[wasm_bindgen] pub fn target_usdpl() -> String { @@ -94,7 +91,11 @@ pub fn version_usdpl() -> String { #[wasm_bindgen] pub fn set_value(key: String, value: JsValue) -> JsValue { unsafe { - CACHE.as_mut().unwrap().insert(key, value).unwrap_or(JsValue::NULL) + CACHE + .as_mut() + .unwrap() + .insert(key, value) + .unwrap_or(JsValue::NULL) } } @@ -102,90 +103,12 @@ pub fn set_value(key: String, value: JsValue) -> JsValue { #[wasm_bindgen] pub fn get_value(key: String) -> JsValue { unsafe { - CACHE.as_ref().unwrap().get(&key).map(|x| x.to_owned()).unwrap_or(JsValue::UNDEFINED) - } -} - -/// Call a function on the back-end. -/// Returns null (None) if this fails for any reason. -#[wasm_bindgen] -pub async fn call_backend(name: String, parameters: Vec) -> JsValue { - #[cfg(feature = "debug")] - imports::console_log(&format!( - "call_backend({}, [params; {}])", - name, - parameters.len() - )); - let next_id = increment_id(); - let mut params = Vec::with_capacity(parameters.len()); - for val in parameters { - params.push(convert::js_to_primitive(val)); - } - let port = get_port(); - #[cfg(feature = "debug")] - imports::console_log(&format!("USDPL: Got port {}", port)); - let results = connection::send_call( - next_id, - Packet::Call(RemoteCall { - id: next_id, - function: name.clone(), - parameters: params, - }), - port, - #[cfg(feature = "encrypt")] - get_key() - ) - .await; - let results = match results { - Ok(x) => x, - #[allow(unused_variables)] - Err(e) => { - #[cfg(feature = "debug")] - imports::console_error(&format!("USDPL: Got error while calling {}: {:?}", name, e)); - return JsValue::NULL; - } - }; - let results_js = Array::new_with_length(results.len() as _); - let mut i = 0; - for item in results { - results_js.set(i as _, convert::primitive_to_js(item)); - i += 1; - } - results_js.into() -} - -/// Initialize translation strings for the front-end -#[wasm_bindgen] -pub async fn init_tr(locale: String) { - let next_id = increment_id(); - match connection::send_recv_packet( - next_id, - Packet::Language(locale.clone()), - get_port(), - #[cfg(feature = "encrypt")] - get_key() - ).await { - Ok(Packet::Translations(translations)) => { - #[cfg(feature = "debug")] - imports::console_log(&format!("USDPL: Got translations for {}", locale)); - // convert translations into map - let mut tr_map = std::collections::HashMap::with_capacity(translations.len()); - for (key, val) in translations { - tr_map.insert(key, val); - } - unsafe { TRANSLATIONS = Some(tr_map) } - }, - Ok(_) => { - #[cfg(feature = "debug")] - imports::console_error(&format!("USDPL: Got wrong packet response for init_tr")); - unsafe { TRANSLATIONS = None } - }, - #[allow(unused_variables)] - Err(e) => { - #[cfg(feature = "debug")] - imports::console_error(&format!("USDPL: Got wrong error for init_tr: {:#?}", e)); - unsafe { TRANSLATIONS = None } - } + CACHE + .as_ref() + .unwrap() + .get(&key) + .map(|x| x.to_owned()) + .unwrap_or(JsValue::UNDEFINED) } } diff --git a/usdpl-front/src/wasm/arrays.rs b/usdpl-front/src/wasm/arrays.rs new file mode 100644 index 0000000..45dd935 --- /dev/null +++ b/usdpl-front/src/wasm/arrays.rs @@ -0,0 +1,94 @@ +use js_sys::Array; + +use super::{FromWasmable, IntoWasmable}; + +macro_rules! numbers_array { + ($num_ty: ident) => { + impl FromWasmable for Vec<$num_ty> { + fn from_wasm(js: Array) -> Self { + let mut result = Vec::with_capacity(js.length() as usize); + js.for_each(&mut |val, _index, _arr| { + // according to MDN, this is guaranteed to be in order so index can be ignored + if let Some(val) = val.as_f64() { + result.push(val as $num_ty); + } + }); + result + } + } + + impl IntoWasmable for Vec<$num_ty> { + fn into_wasm(self) -> Array { + let result = Array::new(); + for val in self { + result.push(&val.into()); + } + result + } + } + }; +} + +numbers_array! { f64 } +numbers_array! { f32 } + +numbers_array! { isize } +numbers_array! { usize } + +numbers_array! { i8 } +numbers_array! { i16 } +numbers_array! { i32 } +numbers_array! { i64 } +numbers_array! { i128 } + +numbers_array! { u8 } +numbers_array! { u16 } +numbers_array! { u32 } +numbers_array! { u64 } +numbers_array! { u128 } + +impl FromWasmable for Vec { + fn from_wasm(js: Array) -> Self { + let mut result = Vec::with_capacity(js.length() as usize); + js.for_each(&mut |val, _index, _arr| { + // according to MDN, this is guaranteed to be in order so index can be ignored + if let Some(val) = val.as_string() { + result.push(val); + } + }); + result + } +} + +impl IntoWasmable for Vec { + fn into_wasm(self) -> Array { + let result = Array::new(); + for val in self { + result.push(&val.into()); + } + result + } +} + +impl FromWasmable for Vec { + fn from_wasm(js: Array) -> Self { + let mut result = Vec::with_capacity(js.length() as usize); + js.for_each(&mut |val, _index, _arr| { + // according to MDN, this is guaranteed to be in order so index can be ignored + if let Some(val) = val.as_bool() { + result.push(val); + } + }); + result + } +} + +impl IntoWasmable for Vec { + fn into_wasm(self) -> Array { + let result = Array::new(); + for val in self { + result.push(&val.into()); + } + result + } +} diff --git a/usdpl-front/src/wasm/js_function_stream.rs b/usdpl-front/src/wasm/js_function_stream.rs new file mode 100644 index 0000000..4bd5c02 --- /dev/null +++ b/usdpl-front/src/wasm/js_function_stream.rs @@ -0,0 +1,74 @@ +use core::pin::Pin; +use core::future::Future; + +use futures::{Stream, task::{Poll, Context}}; +use wasm_bindgen_futures::JsFuture; +use wasm_bindgen::JsValue; +use js_sys::{Function, Promise}; + +use nrpc::ServiceError; +use super::FromWasmStreamableType; +use crate::convert::js_to_str; + +/// futures::Stream wrapper for a JS async function that generates a new T-like value every call +pub struct JsFunctionStream { + function: Function, + promise: Option, + _idc: std::marker::PhantomData, +} + +impl JsFunctionStream { + /// Construct the function stream wrapper + pub fn from_function(f: Function) -> Self { + Self { + function: f, + promise: None, + _idc: std::marker::PhantomData::default(), + } + } +} + +impl Stream for JsFunctionStream { + type Item = Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_> + ) -> Poll> { + // this is horrible, I'm sorry + let js_poll = if let Some(mut promise) = self.promise.take() { + let mut pin = Pin::new(&mut promise); + JsFuture::poll(pin.as_mut(), cx) + } else { + let function_result = match self.function.call0(&JsValue::undefined()) { + Ok(x) => x, + Err(e) => return Poll::Ready(Some(Err(ServiceError::Method(s_to_err(format!("JS function call error: {}", js_to_str(e))))))) + }; + + let js_promise = Promise::from(function_result); + let mut js_future = JsFuture::from(js_promise); + let mut pin = Pin::new(&mut js_future); + let poll = JsFuture::poll(pin.as_mut(), cx); + self.promise = Some(js_future); + poll + }; + js_poll.map(|t| match t { + Ok(t) => { + if t.is_null() || t.is_undefined() { + None + } else { + Some(T::from_wasm_streamable(t).map_err(|e| ServiceError::Method(s_to_err(format!("JS type conversion error: {}", e))))) + } + }, + Err(e) => Some(Err(ServiceError::Method(s_to_err(format!("JS function promise error: {}", js_to_str(e)))))) + }) + } +} + +fn s_to_err(s: String) -> Box<(dyn std::error::Error + Send + Sync + 'static)> { + s.into() +} + +fn _check_service_stream(js_stream: JsFunctionStream) { + let _: nrpc::ServiceClientStream<'static, T> = Box::new(js_stream); +} diff --git a/usdpl-front/src/wasm/maps.rs b/usdpl-front/src/wasm/maps.rs new file mode 100644 index 0000000..d7dcbcd --- /dev/null +++ b/usdpl-front/src/wasm/maps.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; + +use js_sys::Map; + +use super::{FromWasmable, IntoWasmable}; + +macro_rules! numbers_map { + ($num_ty: ident) => { + impl FromWasmable for HashMap { + fn from_wasm(js: Map) -> Self { + let mut result = HashMap::with_capacity(js.size() as usize); + js.for_each(&mut |key, val| { + if let Some(key) = key.as_string() { + if let Some(val) = val.as_f64() { + result.insert(key, val as $num_ty); + } + } + }); + result + } + } + + impl IntoWasmable for HashMap { + fn into_wasm(self) -> Map { + let result = Map::new(); + for (key, val) in self { + result.set(&key.into(), &val.into()); + } + result + } + } + }; +} + +numbers_map! { f64 } +numbers_map! { f32 } + +numbers_map! { isize } +numbers_map! { usize } + +numbers_map! { i8 } +numbers_map! { i16 } +numbers_map! { i32 } +numbers_map! { i64 } +numbers_map! { i128 } + +numbers_map! { u8 } +numbers_map! { u16 } +numbers_map! { u32 } +numbers_map! { u64 } +numbers_map! { u128 } + +impl FromWasmable for HashMap { + fn from_wasm(js: Map) -> Self { + let mut result = HashMap::with_capacity(js.size() as usize); + js.for_each(&mut |key, val| { + if let Some(key) = key.as_string() { + if let Some(val) = val.as_string() { + result.insert(key, val); + } + } + }); + result + } +} + +impl IntoWasmable for HashMap { + fn into_wasm(self) -> Map { + let result = Map::new(); + for (key, val) in self { + result.set(&key.into(), &val.into()); + } + result + } +} + +impl FromWasmable for HashMap { + fn from_wasm(js: Map) -> Self { + let mut result = HashMap::with_capacity(js.size() as usize); + js.for_each(&mut |key, val| { + if let Some(key) = key.as_string() { + if let Some(val) = val.as_bool() { + result.insert(key, val); + } + } + }); + result + } +} + +impl IntoWasmable for HashMap { + fn into_wasm(self) -> Map { + let result = Map::new(); + for (key, val) in self { + result.set(&key.into(), &val.into()); + } + result + } +} diff --git a/usdpl-front/src/wasm/mod.rs b/usdpl-front/src/wasm/mod.rs new file mode 100644 index 0000000..17bf56b --- /dev/null +++ b/usdpl-front/src/wasm/mod.rs @@ -0,0 +1,11 @@ +//! WASM <-> Rust interop utilities +mod arrays; +mod js_function_stream; +mod maps; +mod streaming; +mod trivials; +mod wasm_traits; + +pub use js_function_stream::JsFunctionStream; +pub use wasm_traits::*; +pub use streaming::*; diff --git a/usdpl-front/src/wasm/streaming.rs b/usdpl-front/src/wasm/streaming.rs new file mode 100644 index 0000000..6c18ff8 --- /dev/null +++ b/usdpl-front/src/wasm/streaming.rs @@ -0,0 +1,192 @@ +use wasm_bindgen::JsValue; + +/// Convert Rust type to WASM-compatible type involved in nRPC streaming +pub trait IntoWasmStreamableType { + /// Required method + fn into_wasm_streamable(self) -> JsValue; +} + +#[derive(Debug)] +/// Conversion error from FromWasmStreamableType +pub enum WasmStreamableConversionError { + /// JSValue underlying type is incorrect + UnexpectedType { + /// Expected Javascript type + expected: JsType, + /// Actual Javascript type + got: JsType, + }, +} + +impl core::fmt::Display for WasmStreamableConversionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::UnexpectedType { expected, got } => write!(f, "Unexpected type {}, expected {}", expected, got), + } + } +} + +impl std::error::Error for WasmStreamableConversionError {} + +/// Approximation of all possible JS types detectable through Wasm +#[allow(missing_docs)] +#[derive(Debug)] +pub enum JsType { + Number, + String, + Bool, + Array, + BigInt, + Function, + Symbol, + Undefined, + Null, + Object, + Unknown, +} + +impl core::fmt::Display for JsType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Number => write!(f, "number"), + Self::String => write!(f, "string"), + Self::Bool => write!(f, "boolean"), + Self::Array => write!(f, "array"), + Self::BigInt => write!(f, "bigint"), + Self::Function => write!(f, "function"), + Self::Symbol => write!(f, "symbol"), + Self::Undefined => write!(f, "undefined"), + Self::Null => write!(f, "null"), + Self::Object => write!(f, "object"), + Self::Unknown => write!(f, ""), + } + } +} + +impl JsType { + /// Guess the JS type of the parameter. + /// This is not guaranteed to be correct, but is intended to give more information + /// in debug and error messages + pub fn guess(js: &JsValue) -> JsType { + if js.as_f64().is_some() { + Self::Number + } else if js.as_string().is_some() { + Self::String + } else if js.as_bool().is_some() { + Self::Bool + } else if js.is_array() { + Self::Array + } else if js.is_bigint() { + Self::BigInt + } else if js.is_function() { + Self::Function + } else if js.is_symbol() { + Self::Symbol + } else if js.is_undefined() { + Self::Undefined + } else if js.is_null() { + Self::Null + } else if js.is_object() { + Self::Object + } else { + Self::Unknown + } + } +} + +/// Convert WASM-compatible type involved in nRPC streaming to Rust-centric type +pub trait FromWasmStreamableType: Sized { + /// Required method + fn from_wasm_streamable(js: JsValue) -> Result; +} + +macro_rules! trivial_convert_number { + ($ty: ty) => { + impl FromWasmStreamableType for $ty { + fn from_wasm_streamable(js: JsValue) -> Result { + if let Some(num) = js.as_f64() { + Ok(num as $ty) + } else { + Err(WasmStreamableConversionError::UnexpectedType { + expected: JsType::Number, + got: JsType::guess(&js), + }) + } + } + } + + impl IntoWasmStreamableType for $ty { + fn into_wasm_streamable(self) -> JsValue { + self.into() + } + } + }; +} + +trivial_convert_number! { f64 } +trivial_convert_number! { f32 } + +trivial_convert_number! { isize } +trivial_convert_number! { usize } + +trivial_convert_number! { i8 } +trivial_convert_number! { i16 } +trivial_convert_number! { i32 } +trivial_convert_number! { i64 } +trivial_convert_number! { i128 } + +trivial_convert_number! { u8 } +trivial_convert_number! { u16 } +trivial_convert_number! { u32 } +trivial_convert_number! { u64 } +trivial_convert_number! { u128 } + +impl FromWasmStreamableType for String { + fn from_wasm_streamable(js: JsValue) -> Result { + if let Some(s) = js.as_string() { + Ok(s) + } else { + Err(WasmStreamableConversionError::UnexpectedType { + expected: JsType::String, + got: JsType::guess(&js), + }) + } + } +} + +impl IntoWasmStreamableType for String { + fn into_wasm_streamable(self) -> JsValue { + self.into() + } +} + +impl FromWasmStreamableType for bool { + fn from_wasm_streamable(js: JsValue) -> Result { + if let Some(b) = js.as_bool() { + Ok(b) + } else { + Err(WasmStreamableConversionError::UnexpectedType { + expected: JsType::Bool, + got: JsType::guess(&js), + }) + } + } +} + +impl IntoWasmStreamableType for bool { + fn into_wasm_streamable(self) -> JsValue { + self.into() + } +} + +impl FromWasmStreamableType for () { + fn from_wasm_streamable(_js: JsValue) -> Result { + Ok(()) + } +} + +impl IntoWasmStreamableType for () { + fn into_wasm_streamable(self) -> JsValue { + JsValue::undefined() + } +} diff --git a/usdpl-front/src/wasm/trivials.rs b/usdpl-front/src/wasm/trivials.rs new file mode 100644 index 0000000..3187e26 --- /dev/null +++ b/usdpl-front/src/wasm/trivials.rs @@ -0,0 +1,40 @@ +use super::{FromWasmable, IntoWasmable}; + +macro_rules! trivial_convert { + ($ty: ty) => { + impl FromWasmable<$ty> for $ty { + fn from_wasm(js: $ty) -> Self { + js + } + } + + impl IntoWasmable<$ty> for $ty { + fn into_wasm(self) -> $ty { + self + } + } + }; +} + +trivial_convert! { f64 } +trivial_convert! { f32 } + +trivial_convert! { isize } +trivial_convert! { usize } + +trivial_convert! { i8 } +trivial_convert! { i16 } +trivial_convert! { i32 } +trivial_convert! { i64 } +trivial_convert! { i128 } + +trivial_convert! { u8 } +trivial_convert! { u16 } +trivial_convert! { u32 } +trivial_convert! { u64 } +trivial_convert! { u128 } + +trivial_convert! { bool } +trivial_convert! { String } + +trivial_convert! { () } diff --git a/usdpl-front/src/wasm/wasm_traits.rs b/usdpl-front/src/wasm/wasm_traits.rs new file mode 100644 index 0000000..ec1ec9f --- /dev/null +++ b/usdpl-front/src/wasm/wasm_traits.rs @@ -0,0 +1,40 @@ +/// A Rust type which supports Into/FromWasmAbi or WasmDescribe +pub trait KnownWasmCompatible {} + +/// Convert Rust type to WASM-compatible type +pub trait IntoWasmable { + /// Required method + fn into_wasm(self) -> T; +} + +/// Convert WASM-compatible type to Rust-centric type +pub trait FromWasmable { + /// Required method + fn from_wasm(js: T) -> Self; +} + +impl KnownWasmCompatible for f64 {} +impl KnownWasmCompatible for f32 {} + +impl KnownWasmCompatible for isize {} +impl KnownWasmCompatible for usize {} + +impl KnownWasmCompatible for i8 {} +impl KnownWasmCompatible for i16 {} +impl KnownWasmCompatible for i32 {} +impl KnownWasmCompatible for i64 {} +impl KnownWasmCompatible for i128 {} + +impl KnownWasmCompatible for u8 {} +impl KnownWasmCompatible for u16 {} +impl KnownWasmCompatible for u32 {} +impl KnownWasmCompatible for u64 {} +impl KnownWasmCompatible for u128 {} + +impl KnownWasmCompatible for bool {} +impl KnownWasmCompatible for String {} + +impl KnownWasmCompatible for () {} + +impl KnownWasmCompatible for js_sys::Map {} +impl KnownWasmCompatible for js_sys::Array {}