diff --git a/.editorconfig b/.editorconfig index a329623..1b6b56f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,7 +16,7 @@ end_of_line = lf end_of_line = crlf # Config files -[*.{yml,toml}] +[*.{yaml,yml,toml}] end_of_line = lf indent_size = 2 diff --git a/.vscode/settings.json b/.vscode/settings.json index a1a73c6..8180411 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "chacha", "chrono", "ciphertext", + "codegen", "dalek", "diffie", "exif", @@ -25,6 +26,7 @@ "Repr", "reqwest", "rocketpack", + "rpfs", "secretmanager", "Seedable", "serde", @@ -37,6 +39,7 @@ "testresult", "thiserror", "tsid", + "unzigzag", "urlencoding", "utoipa", "varint" diff --git a/Cargo.lock b/Cargo.lock index ad3d439..55e21f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + [[package]] name = "anyhow" version = "1.0.98" @@ -744,6 +794,12 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bindgen" version = "0.69.5" @@ -993,6 +1049,47 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + [[package]] name = "cmake" version = "0.1.54" @@ -1002,6 +1099,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -2320,6 +2423,12 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.12.1" @@ -2425,7 +2534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -2490,6 +2599,40 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "logos" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax 0.8.5", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" +dependencies = [ + "logos-codegen", +] + [[package]] name = "lru" version = "0.12.5" @@ -2882,6 +3025,24 @@ dependencies = [ "uuid", ] +[[package]] +name = "omnius-core-rocketpack-compiler" +version = "0.1.0" +dependencies = [ + "clap", + "logos", + "omnius-core-base", + "serde", + "serde_yaml_ng", + "testresult", + "thiserror 2.0.14", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "tracing-subscriber", +] + [[package]] name = "omnius-core-testkit" version = "0.1.0" @@ -2901,6 +3062,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -3932,6 +4099,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap 2.10.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serial_test" version = "3.2.0" @@ -4453,6 +4633,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "terminal_size" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" +dependencies = [ + "rustix 1.0.8", + "windows-sys 0.60.2", +] + [[package]] name = "testcontainers" version = "0.25.0" @@ -4984,6 +5174,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -5029,6 +5225,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.18.0" diff --git a/Cargo.toml b/Cargo.toml index 4b0124a..a3df61b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,9 @@ members = [ "modules/omnikit", "modules/rocketpack", "modules/testkit", + "entrypoints/rocketpack-compiler", ] +exclude = ["entrypoints/rocketpack-compiled-example/rust"] resolver = "3" [workspace.package] @@ -28,6 +30,8 @@ omnius-core-omnikit = { path = "./modules/omnikit" } omnius-core-rocketpack = { path = "./modules/rocketpack" } omnius-core-testkit = { path = "./modules/testkit" } +omnius-core-rocketpack-compiler = { path = "./entrypoints/rocketpack-compiler" } + aws-smithy-runtime-api = "1.8.7" aws-config = "1.8.4" aws-sdk-secretsmanager = "1.83.0" @@ -106,3 +110,9 @@ ed25519-dalek = { version = "=3.0.0-pre.0", features = [ "serde", "pkcs8", ] } +logos = "0.15.1" +clap = { version = "4.5.44", features = ["wrap_help", "derive"] } +serde_yaml_ng = "0.10.0" + +[workspace.lints.clippy] +uninlined_format_args = "allow" diff --git a/entrypoints/rocketpack-compiled-example/rocketpack.yaml b/entrypoints/rocketpack-compiled-example/rocketpack.yaml new file mode 100644 index 0000000..4e4d27a --- /dev/null +++ b/entrypoints/rocketpack-compiled-example/rocketpack.yaml @@ -0,0 +1,52 @@ +version: 1 + +sources: + - base_dir: rpfs + includes: + - "**/*.rpf" + excludes: + - "**/*.test.rpf" + +generators: + + - id: csharp + plugin: rocketpack-csharp + options: + target_framework: net8.0 + visibility: public + partial_types: true + base_namespace: Company.Project + file_extension: .g.cs + targets: + example/first.rpf: + out: sharp/gen/src/example/first + example/*.rpf: + dir: sharp/gen/src/example/second + options: + visibility: internal + + - id: rust + plugin: rocketpack-rust + options: + edition: 2024 + visibility: public + targets: + example/first.rpf: + out: rust/gen/src/example/first + example/*.rpf: + dir: rust/gen/src/example/second + options: + visibility: internal + + - id: swift + plugin: rocketpack-swift + options: + swift_version: 6.x + visibility: public + targets: + example/first.rpf: + out: swift/gen/src/example/first + example/*.rpf: + dir: swift/gen/src/example/second + options: + visibility: internal diff --git a/entrypoints/rocketpack-compiled-example/rust/.gitignore b/entrypoints/rocketpack-compiled-example/rust/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/entrypoints/rocketpack-compiled-example/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/entrypoints/rocketpack-compiled-example/rust/Cargo.lock b/entrypoints/rocketpack-compiled-example/rust/Cargo.lock new file mode 100644 index 0000000..acd5664 --- /dev/null +++ b/entrypoints/rocketpack-compiled-example/rust/Cargo.lock @@ -0,0 +1,1291 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "omnius-core-base" +version = "0.1.0" +dependencies = [ + "async-trait", + "base64", + "chrono", + "hex", + "parking_lot", + "rand_chacha", + "rand_core", + "serde", + "serde_json", + "tokio", + "tokio-postgres", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "omnius-core-rocketpack" +version = "0.1.0" +dependencies = [ + "async-trait", + "base64", + "chrono", + "hex", + "omnius-core-base", + "parking_lot", + "rand", + "rand_chacha", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-postgres", + "tokio-util", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "postgres-protocol" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" +dependencies = [ + "bytes", + "chrono", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rocketpack-compiled-example" +version = "0.1.0" +dependencies = [ + "omnius-core-base", + "omnius-core-rocketpack", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/entrypoints/rocketpack-compiled-example/rust/Cargo.toml b/entrypoints/rocketpack-compiled-example/rust/Cargo.toml new file mode 100644 index 0000000..f727d43 --- /dev/null +++ b/entrypoints/rocketpack-compiled-example/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rocketpack-compiled-example" +version = "0.1.0" +edition = "2024" +authors = ["Lyrise "] + +[dependencies] +omnius-core-base = { path = "../../../modules/base" } +omnius-core-rocketpack = { path = "../../../modules/rocketpack" } + +[dev-dependencies] diff --git a/entrypoints/rocketpack-compiled-example/rust/src/main.rs b/entrypoints/rocketpack-compiled-example/rust/src/main.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/entrypoints/rocketpack-compiled-example/rust/src/main.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/entrypoints/rocketpack-compiler/Cargo.toml b/entrypoints/rocketpack-compiler/Cargo.toml new file mode 100644 index 0000000..25d7e10 --- /dev/null +++ b/entrypoints/rocketpack-compiler/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "omnius-core-rocketpack-compiler" +version = "0.1.0" +edition = { workspace = true } +authors = { workspace = true } + +[features] +default = [] +stable-test = [] + +[dependencies] +omnius-core-base = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +logos = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +clap = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +tokio-stream = { workspace = true } +serde_yaml_ng = { workspace = true } + +[dev-dependencies] +testresult = { workspace = true } + +[lints.rust] +unused = "allow" + +[lints.clippy] +uninlined_format_args = "allow" +unused_async = "allow" +unused_collect = "allow" +unused_iter_cloned = "allow" +unused_peekable = "allow" +unused_self = "allow" +unused_unit = "allow" +redundant_pattern_matching = "allow" diff --git a/entrypoints/rocketpack-compiler/data/rocketpack.yaml b/entrypoints/rocketpack-compiler/data/rocketpack.yaml new file mode 100644 index 0000000..04c3e7c --- /dev/null +++ b/entrypoints/rocketpack-compiler/data/rocketpack.yaml @@ -0,0 +1,55 @@ +version: 1 + +sources: + - base_dir: rpfs + includes: + - "**/*.rpf" + excludes: + - "**/*.test.rpf" + +generators: + + - id: csharp + plugin: rocketpack-csharp + options: + target_framework: net8.0 + visibility: public + partial_types: true + base_namespace: Company.Project + file_extension: .g.cs + targets: + - pattern: example/first.rpf + options: + dir: csharp/gen/src/example/first + - pattern: example/*.rpf + options: + dir: csharp/gen/src/example/second + visibility: internal + + - id: rust + plugin: rocketpack-rust + options: + edition: 2024 + visibility: public + targets: + - pattern: example/first.rpf + options: + dir: rust/gen/src/example/first + - pattern: example/*.rpf + options: + dir: rust/gen/src/example/second + visibility: internal + + - id: swift + plugin: rocketpack-swift + options: + swift_version: 6.x + visibility: public + targets: + - pattern: example/first.rpf + options: + dir: swift/gen/src/example/first + - pattern: example/*.rpf + options: + dir: swift/gen/src/example/second + visibility: internal diff --git a/entrypoints/rocketpack-compiler/data/rpfs/example.rpf b/entrypoints/rocketpack-compiler/data/rpfs/example.rpf new file mode 100644 index 0000000..b46d948 --- /dev/null +++ b/entrypoints/rocketpack-compiler/data/rpfs/example.rpf @@ -0,0 +1,73 @@ +edition 2025; +package omnius::demo::v1; + +// comment1 +/* comment2 */ + +struct PrimitiveShowcase1 { + @1 bool_field: bool; + @2 u8_field: u8; + @3 i16_field: i16; + @4 i32_field: i32; + @5 i12_field: i128; + @6 u16_field: u16; + @7 u32_field: u32; + @8 u64_field: u64; + @9 u12_field: u128; + @10 f32_field: f32; + @11 f64_field: f64; + @12 string_field: string; + @13 bytes_field: bytes; + @14 vec_field_1: Vec; + @15 vec_field_2: Vec; + @16 vec_field_3: Vec; + @17 map_field_1: Map; + @18 map_field_2: Map; + @19 map_vec_field_1: Map>; + @20 map_vec_field_2: Map>; + @21 slice_field: [i64; 4]; + @22 struct_field: SimpleMessage; +} + +struct PrimitiveShowcase2 { + @1 bool_field: Option; + @2 u8_field: Option; + @3 i16_field: Option; + @4 i32_field: Option; + @5 i12_field: Option; + @6 u16_field: Option; + @7 u32_field: Option; + @8 u64_field: Option; + @9 u12_field: Option; + @10 f32_field: Option; + @11 f64_field: Option; + @12 string_field: Option; + @13 bytes_field: Option; + @14 vec_field_1: Option>; + @15 vec_field_2: Option>; + @16 vec_field_3: Option>; + @17 map_field_1: Option>; + @18 map_field_2: Option>; + @19 map_vec_field_1: Option>>; + @20 map_vec_field_2: Option>>; + @21 struct_field: Option; +} + +enum PrimitiveShowcase3 { + @1 First; + @2 Second(entity: string, payload: ByteBuffer); + @3 Third { + @1 entity: string; + @2 status: Status; + @4 retries: u32 = 0; + @5 struct_field: Option; + }; +} + +struct SimpleMessage { + @1 bool_field: Option; +} + + +type ByteList = Vec; +const MAX_SAMPLE_SIZE: u32 = 1_048_576; diff --git a/entrypoints/rocketpack-compiler/src/codegen.rs b/entrypoints/rocketpack-compiler/src/codegen.rs new file mode 100644 index 0000000..708d613 --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/codegen.rs @@ -0,0 +1,16 @@ +use crate::{config::AppConfig, error::CodegenError}; + +mod rust; + +pub async fn generate(conf: AppConfig) -> Result<(), CodegenError> { + for generator_conf in conf.generators { + match generator_conf.plugin.as_str() { + "rocketpack-rust" => rust::generate(&conf.sources, &generator_conf).await?, + "rocketpack-csharp" => {} + "rocketpack-swift" => {} + _ => {} + } + } + + Ok(()) +} diff --git a/entrypoints/rocketpack-compiler/src/codegen/rust.rs b/entrypoints/rocketpack-compiler/src/codegen/rust.rs new file mode 100644 index 0000000..8ec81cd --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/codegen/rust.rs @@ -0,0 +1,859 @@ +use std::fmt::Write as _; + +use crate::{config::*, error::*, parser::ast::*}; + +pub async fn generate(_sources: &[SourceConfig], _conf: &GeneratorConfig) -> Result<(), CodegenError> { + todo!() +} +/* +/// エントリ:ASTからRustコードを生成 +fn generate_rust_code_(file: &File) -> String { + let mut out = String::new(); + writeln!( + &mut out, + "// @generated by RocketPack rpf codegen\n#![allow(dead_code,unused_imports)]\nuse crate::rocketpack_rpf::runtime as rp;\n" + ) + .ok(); + + // package を module に展開 + let mut depth = 0usize; + if let Some(pkg) = &file.package { + for seg in &pkg.value.segments { + let name = &seg.value; + writeln!(&mut out, "pub mod {} {{", sanitize_ident(name)).ok(); + depth += 1; + } + writeln!(&mut out, "use rp::*;").ok(); + } else { + writeln!(&mut out, "use rp::*;").ok(); + } + + // 各アイテム + for it in &file.items { + match it { + Item::Struct(s) => { + write_struct(&mut out, s); + write_struct_encode_impl(&mut out, s); + write_struct_decode_impl(&mut out, s); + } + Item::Enum(e) => { + write_enum(&mut out, e); + write_enum_encode_impl(&mut out, e); + write_enum_decode_impl(&mut out, e); + } + Item::TypeAlias(t) => { + write_type_alias(&mut out, t); + } + Item::Const(c) => { + write_const(&mut out, c); + } + } + writeln!(&mut out).ok(); + } + + // 閉じモジュール + for _ in 0..depth { + writeln!(&mut out, "}}").ok(); + } + + out +} + +// ====== struct ====== + +fn write_struct(buf: &mut String, s: &Struct) { + writeln!(buf, "#[derive(Debug, Clone, Default, PartialEq)]").ok(); + writeln!(buf, "pub struct {} {{", s.name.value).ok(); + for f in &s.fields { + let ty = render_type(&f.ty.value); + writeln!(buf, " pub {}: {},", sanitize_ident(&f.name.value), ty).ok(); + } + writeln!(buf, "}}").ok(); +} + +fn write_struct_encode_impl(buf: &mut String, s: &Struct) { + writeln!(buf, "impl rp::RpEncode for {} {{", s.name.value).ok(); + writeln!(buf, " fn encode_into(&self, __buf: &mut Vec) {{").ok(); + + // フィールド番号昇順 + let mut fs = s.fields.clone(); + fs.sort_by_key(|f| f.tag.value); + for f in fs { + let tag = f.tag.value; + let name = sanitize_ident(&f.name.value); + gen_encode_stmt(buf, "__buf", tag, &format!("self.{}", name), &f.ty.value, 1, None); + } + + writeln!(buf, " }}").ok(); + writeln!(buf, "}}").ok(); +} + +// ====== enum ====== + +fn write_enum(buf: &mut String, e: &Enum) { + writeln!(buf, "#[derive(Debug, Clone, PartialEq)]").ok(); + writeln!(buf, "pub enum {} {{", e.name.value).ok(); + for v in &e.variants { + match &v.kind { + VariantKind::Unit => { + writeln!(buf, " {},", v.name.value).ok(); + } + VariantKind::Tuple(params) => { + // Rustのタプルvariantは名前を保持できないため、struct-likeに落とす + write!(buf, " {} {{ ", v.name.value).ok(); + for (i, (n, t)) in params.iter().enumerate() { + if i > 0 { + write!(buf, ", ").ok(); + } + write!(buf, "{}: {}", sanitize_ident(&n.value), render_type(&t.value)).ok(); + } + writeln!(buf, " }},").ok(); + } + VariantKind::Record(fields) => { + write!(buf, " {} {{ ", v.name.value).ok(); + for (i, f) in fields.iter().enumerate() { + if i > 0 { + write!(buf, ", ").ok(); + } + write!(buf, "{}: {}", sanitize_ident(&f.name.value), render_type(&f.ty.value)).ok(); + } + writeln!(buf, " }},").ok(); + } + } + } + writeln!(buf, "}}").ok(); +} + +fn write_enum_encode_impl(buf: &mut String, e: &Enum) { + writeln!(buf, "impl rp::RpEncode for {} {{", e.name.value).ok(); + writeln!(buf, " fn encode_into(&self, __buf: &mut Vec) {{").ok(); + writeln!(buf, " match self {{").ok(); + + for v in &e.variants { + let vname = &v.name.value; + match &v.kind { + VariantKind::Unit => { + writeln!(buf, " Self::{} => {{", vname).ok(); + writeln!(buf, " rp::write_key(__buf, {}, rp::WT_LEN);", v.tag.value).ok(); + writeln!(buf, " rp::put_varint_u128(__buf, 0);").ok(); // 空メッセージ + writeln!(buf, " }}").ok(); + } + VariantKind::Tuple(params) => { + // tupleは @1.. の暗黙タグを振る + write!(buf, " Self::{} {{ ", vname).ok(); + for (i, (n, _t)) in params.iter().enumerate() { + if i > 0 { + write!(buf, ", ").ok(); + } + write!(buf, "{}", sanitize_ident(&n.value)).ok(); + } + writeln!(buf, " }} => {{").ok(); + writeln!(buf, " let mut __inner = Vec::new();").ok(); + for (i, (_n, t)) in params.iter().enumerate() { + let param_name = sanitize_ident(&_n.value); + let tag = (i + 1) as u32; + gen_encode_stmt(buf, "__inner", tag, ¶m_name, &t.value, 3, Some("&".to_string())); + } + writeln!(buf, " rp::write_key(__buf, {}, rp::WT_LEN);", v.tag.value).ok(); + writeln!(buf, " rp::put_varint_u128(__buf, __inner.len() as u128);").ok(); + writeln!(buf, " __buf.extend_from_slice(&__inner);").ok(); + writeln!(buf, " }}").ok(); + } + VariantKind::Record(fields) => { + // record内は明示タグを利用 + write!(buf, " Self::{} {{ ", vname).ok(); + for (i, f) in fields.iter().enumerate() { + if i > 0 { + write!(buf, ", ").ok(); + } + write!(buf, "{}", sanitize_ident(&f.name.value)).ok(); + } + writeln!(buf, " }} => {{").ok(); + writeln!(buf, " let mut __inner = Vec::new();").ok(); + for f in fields { + let tag = f.tag.value; + let nm = sanitize_ident(&f.name.value); + gen_encode_stmt(buf, "__inner", tag, &nm, &f.ty.value, 3, Some("&".to_string())); + } + writeln!(buf, " rp::write_key(__buf, {}, rp::WT_LEN);", v.tag.value).ok(); + writeln!(buf, " rp::put_varint_u128(__buf, __inner.len() as u128);").ok(); + writeln!(buf, " __buf.extend_from_slice(&__inner);").ok(); + writeln!(buf, " }}").ok(); + } + } + } + + writeln!(buf, " }}").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, "}}").ok(); +} + +fn write_struct_decode_impl(buf: &mut String, s: &Struct) { + writeln!(buf, "impl rp::RpDecode for {} {{", s.name.value).ok(); + writeln!(buf, " fn decode_from_bytes(__bytes: &[u8]) -> Result {{").ok(); + writeln!(buf, " let mut __value = Self::default();").ok(); + writeln!(buf, " __value.merge_from_bytes(__bytes)?;").ok(); + writeln!(buf, " Ok(__value)").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " fn merge_from_bytes(&mut self, __bytes: &[u8]) -> Result<(), rp::DecodeError> {{").ok(); + writeln!(buf, " let mut __dec = rp::Decoder::new(__bytes);").ok(); + + let array_helpers: Vec<(String, String, u64)> = s + .fields + .iter() + .filter_map(|f| { + if let Type::Array(_, len) = &f.ty.value { + let field = sanitize_ident(&f.name.value); + Some((field.clone(), format!("__idx_{}", field), *len)) + } else { + None + } + }) + .collect(); + + for (_, idx, _) in &array_helpers { + writeln!(buf, " let mut {} = 0usize;", idx).ok(); + } + + writeln!(buf, " while let Some((__field, __wt)) = __dec.next_field()? {{").ok(); + writeln!(buf, " match __field {{").ok(); + + for field in &s.fields { + let field_ident = sanitize_ident(&field.name.value); + let array_info = array_helpers.iter().find(|(name, _, _)| name == &field_ident).map(|(_, idx, len)| (idx.clone(), *len)); + write_struct_field_branch(buf, field, array_info); + } + + writeln!(buf, " _ => __dec.skip_field(__wt)?,").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " Ok(())").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, "}}").ok(); +} + +fn write_struct_field_branch(buf: &mut String, field: &Field, array_info: Option<(String, u64)>) { + let tag = field.tag.value; + let field_ident = sanitize_ident(&field.name.value); + let expected = expected_wire_type(&field.ty.value); + writeln!(buf, " {} => {{", tag).ok(); + writeln!(buf, "{}if __wt != {} {{", indent(5), expected).ok(); + writeln!(buf, "{}__dec.skip_field(__wt)?;", indent(6)).ok(); + writeln!(buf, "{}continue;", indent(6)).ok(); + writeln!(buf, "{}{}", indent(5), "}").ok(); + let array_idx = array_info.as_ref().map(|(idx, len)| (idx.as_str(), *len)); + write_field_decode_body(buf, 5, &format!("self.{}", field_ident), &field.ty.value, "__dec", &field_ident, array_idx); + writeln!(buf, " }}").ok(); +} + +fn write_field_decode_body(buf: &mut String, indent_level: usize, target_expr: &str, ty: &Type, decoder_ident: &str, field_ident: &str, array_idx: Option<(&str, u64)>) { + match ty { + Type::Option(inner) => match inner.as_ref() { + Type::Path(_) => { + let value_var = format!("__value_{}", field_ident); + write_decode_value(buf, indent_level, &value_var, inner.as_ref(), decoder_ident); + writeln!(buf, "{}{} = Some({});", indent(indent_level), target_expr, value_var).ok(); + } + Type::Vec(elem) => { + let item_var = format!("__item_{}", field_ident); + write_decode_value(buf, indent_level, &item_var, elem.as_ref(), decoder_ident); + let pad = indent(indent_level); + writeln!(buf, "{}if let Some(vec) = {}.as_mut() {{", pad, target_expr).ok(); + writeln!(buf, "{}vec.push({item_var});", indent(indent_level + 1)).ok(); + writeln!(buf, "{}}} else {{", pad).ok(); + writeln!(buf, "{}let mut vec = Vec::new();", indent(indent_level + 1)).ok(); + writeln!(buf, "{}vec.push({item_var});", indent(indent_level + 1)).ok(); + writeln!(buf, "{}{} = Some(vec);", indent(indent_level + 1), target_expr).ok(); + writeln!(buf, "{}}}", pad).ok(); + } + Type::Map(key_ty, value_ty) => { + let pad = indent(indent_level); + writeln!(buf, "{}if {}.is_none() {{", pad, target_expr).ok(); + writeln!(buf, "{}{} = Some(Default::default());", indent(indent_level + 1), target_expr).ok(); + writeln!(buf, "{}}}", pad).ok(); + writeln!(buf, "{}if let Some(map) = {}.as_mut() {{", pad, target_expr).ok(); + write_decode_map_field(buf, indent_level + 1, "map", field_ident, key_ty, value_ty, decoder_ident); + writeln!(buf, "{}}}", pad).ok(); + } + Type::Option(_) => { + writeln!(buf, "{}panic!(\"Option> decoding is not supported\");", indent(indent_level)).ok(); + } + Type::Array(_, _) => { + writeln!(buf, "{}panic!(\"Option<[T; N]> decoding is not supported\");", indent(indent_level)).ok(); + } + }, + Type::Vec(inner) => { + let value_var = format!("__item_{}", field_ident); + write_decode_value(buf, indent_level, &value_var, inner.as_ref(), decoder_ident); + writeln!(buf, "{}{}.push({});", indent(indent_level), target_expr, value_var).ok(); + } + Type::Array(inner, len) => { + let (idx_name, _) = array_idx.expect("array index helper not found"); + let value_var = format!("__item_{}", field_ident); + write_decode_value(buf, indent_level, &value_var, inner.as_ref(), decoder_ident); + writeln!(buf, "{}if {} < {} {{", indent(indent_level), idx_name, len).ok(); + writeln!(buf, "{} {}[{}] = {};", indent(indent_level + 1), target_expr, idx_name, value_var).ok(); + writeln!(buf, "{} {} += 1;", indent(indent_level + 1), idx_name).ok(); + writeln!(buf, "{}{}", indent(indent_level), "}").ok(); + } + Type::Map(key_ty, value_ty) => { + write_decode_map_field(buf, indent_level, target_expr, field_ident, key_ty, value_ty, decoder_ident); + } + Type::Path(_) => { + let value_var = format!("__value_{}", field_ident); + write_decode_value(buf, indent_level, &value_var, ty, decoder_ident); + writeln!(buf, "{}{} = {};", indent(indent_level), target_expr, value_var).ok(); + } + } +} + +fn write_decode_map_field(buf: &mut String, indent_level: usize, target_expr: &str, field_ident: &str, key_ty: &Type, value_ty: &Type, decoder_ident: &str) { + let pad = indent(indent_level); + let key_type = render_type(key_ty); + let value_type = render_type(value_ty); + let key_tmp = format!("__key_{}", field_ident); + writeln!(buf, "{}let __entry_bytes = {}.read_len_bytes()?;", pad, decoder_ident).ok(); + writeln!(buf, "{}let mut __entry_dec = rp::Decoder::new(__entry_bytes);", pad).ok(); + writeln!(buf, "{}let mut __entry_key: Option<{}> = None;", pad, key_type).ok(); + writeln!(buf, "{}let mut __entry_val: {} = Default::default();", pad, value_type).ok(); + writeln!(buf, "{}let mut __entry_has_val = false;", pad).ok(); + let array_idx_name = if matches!(value_ty, Type::Array(_, _)) { + Some(format!("__entry_idx_{}", field_ident)) + } else { + None + }; + if let Some(name) = &array_idx_name { + writeln!(buf, "{}let mut {} = 0usize;", pad, name).ok(); + } + writeln!(buf, "{}while let Some((__entry_field, __entry_wt)) = __entry_dec.next_field()? {{", pad).ok(); + writeln!(buf, "{} match __entry_field {{", pad).ok(); + + let key_expected = expected_wire_type(key_ty); + writeln!(buf, "{} 1 => {{", indent(indent_level + 2)).ok(); + writeln!(buf, "{}if __entry_wt != {} {{", indent(indent_level + 3), key_expected).ok(); + writeln!(buf, "{}__entry_dec.skip_field(__entry_wt)?;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}continue;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}{}", indent(indent_level + 3), "}").ok(); + write_decode_value(buf, indent_level + 3, &key_tmp, key_ty, "__entry_dec"); + writeln!(buf, "{}__entry_key = Some({});", indent(indent_level + 3), key_tmp).ok(); + writeln!(buf, "{} }}", indent(indent_level + 2)).ok(); + + let value_expected = expected_wire_type(value_ty); + writeln!(buf, "{} 2 => {{", indent(indent_level + 2)).ok(); + writeln!(buf, "{}if __entry_wt != {} {{", indent(indent_level + 3), value_expected).ok(); + writeln!(buf, "{}__entry_dec.skip_field(__entry_wt)?;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}continue;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}{}", indent(indent_level + 3), "}").ok(); + match value_ty { + Type::Path(_) => { + let val_tmp = format!("__val_{}", field_ident); + write_decode_value(buf, indent_level + 3, &val_tmp, value_ty, "__entry_dec"); + writeln!(buf, "{}__entry_val = {val_tmp};", indent(indent_level + 3)).ok(); + } + Type::Option(inner) => { + let inner_tmp = format!("__opt_{}", field_ident); + write_decode_value(buf, indent_level + 3, &inner_tmp, inner.as_ref(), "__entry_dec"); + writeln!(buf, "{}__entry_val = Some({inner_tmp});", indent(indent_level + 3)).ok(); + } + Type::Vec(inner) => { + let item_tmp = format!("__item_{}", field_ident); + write_decode_value(buf, indent_level + 3, &item_tmp, inner.as_ref(), "__entry_dec"); + writeln!(buf, "{}__entry_val.push({item_tmp});", indent(indent_level + 3)).ok(); + } + Type::Array(inner, len) => { + let idx_name = array_idx_name.as_ref().expect("array idx exists"); + let item_tmp = format!("__item_{}", field_ident); + write_decode_value(buf, indent_level + 3, &item_tmp, inner.as_ref(), "__entry_dec"); + writeln!(buf, "{}if {} < {} {{", indent(indent_level + 3), idx_name, len).ok(); + writeln!(buf, "{} __entry_val[{}] = {item_tmp};", indent(indent_level + 4), idx_name).ok(); + writeln!(buf, "{} {} += 1;", indent(indent_level + 4), idx_name).ok(); + writeln!(buf, "{}{}", indent(indent_level + 3), "}").ok(); + } + Type::Map(k, v) => { + let nested_ident = format!("{}_nested", field_ident); + write_decode_map_field(buf, indent_level + 3, "__entry_val", &nested_ident, k.as_ref(), v.as_ref(), "__entry_dec"); + } + } + writeln!(buf, "{}__entry_has_val = true;", indent(indent_level + 3)).ok(); + writeln!(buf, "{} }}", indent(indent_level + 2)).ok(); + + writeln!(buf, "{} _ => __entry_dec.skip_field(__entry_wt)?,", indent(indent_level + 2)).ok(); + writeln!(buf, "{} }}", pad).ok(); + writeln!(buf, "{}{}", pad, "}").ok(); + writeln!(buf, "{}if let Some(__entry_key) = __entry_key {{", pad).ok(); + writeln!(buf, "{} if __entry_has_val {{", indent(indent_level + 1)).ok(); + writeln!(buf, "{} {}.insert(__entry_key, __entry_val);", indent(indent_level + 2), target_expr).ok(); + writeln!(buf, "{} }}", indent(indent_level + 1)).ok(); + writeln!(buf, "{}}}", pad).ok(); +} + +fn write_decode_value(buf: &mut String, indent_level: usize, value_ident: &str, ty: &Type, decoder_ident: &str) { + let pad = indent(indent_level); + match ty { + Type::Path(p) => { + if let Some(kind) = builtin_kind(p) { + match kind { + BuiltinKind::Bool => { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_bool()?;").ok(); + } + BuiltinKind::U(bits) => { + if bits <= 64 { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_varint_u64()? as u{bits};").ok(); + } else { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_varint_u128()?;").ok(); + } + } + BuiltinKind::I(bits) => { + if bits == 128 { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_i128()?;").ok(); + } else { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_i128()? as i{bits};").ok(); + } + } + BuiltinKind::F32 => { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_f32()?;").ok(); + } + BuiltinKind::F64 => { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_f64()?;").ok(); + } + BuiltinKind::String => { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_string()?;").ok(); + } + BuiltinKind::Bytes => { + writeln!(buf, "{pad}let {value_ident} = {decoder_ident}.read_bytes_vec()?;").ok(); + } + } + } else { + let ty_str = render_path_type(p); + writeln!(buf, "{pad}let {value_ident} = {ty_str}::decode_from_bytes({decoder_ident}.read_len_bytes()?)?;").ok(); + } + } + _ => unreachable!("write_decode_value expects a path-like type"), + } +} + +fn write_enum_decode_impl(buf: &mut String, e: &Enum) { + writeln!(buf, "impl rp::RpDecode for {} {{", e.name.value).ok(); + writeln!(buf, " fn decode_from_bytes(__bytes: &[u8]) -> Result {{").ok(); + writeln!(buf, " let mut __dec = rp::Decoder::new(__bytes);").ok(); + writeln!(buf, " let mut __result: Option = None;").ok(); + writeln!(buf, " while let Some((__field, __wt)) = __dec.next_field()? {{").ok(); + writeln!(buf, " match __field {{").ok(); + + for variant in &e.variants { + write_enum_variant_branch(buf, e, variant); + } + + writeln!(buf, " _ => __dec.skip_field(__wt)?,").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " match __result {{").ok(); + writeln!(buf, " Some(v) => Ok(v),").ok(); + writeln!(buf, " None => Err(rp::DecodeError::Missing(\"{} variant\")),", e.name.value).ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, " fn merge_from_bytes(&mut self, __bytes: &[u8]) -> Result<(), rp::DecodeError> {{").ok(); + writeln!(buf, " *self = Self::decode_from_bytes(__bytes)?;").ok(); + writeln!(buf, " Ok(())").ok(); + writeln!(buf, " }}").ok(); + writeln!(buf, "}}").ok(); +} + +fn write_enum_variant_branch(buf: &mut String, e: &Enum, variant: &Variant) { + let tag = variant.tag.value; + let variant_ident = &variant.name.value; + writeln!(buf, " {} => {{", tag).ok(); + writeln!(buf, "{}if __wt != rp::WT_LEN {{", indent(5)).ok(); + writeln!(buf, "{}__dec.skip_field(__wt)?;", indent(6)).ok(); + writeln!(buf, "{}continue;", indent(6)).ok(); + writeln!(buf, "{}{}", indent(5), "}").ok(); + writeln!(buf, "{}let __payload = __dec.read_len_bytes()?;", indent(5)).ok(); + match &variant.kind { + VariantKind::Unit => { + writeln!(buf, "{}let __value = Self::{};", indent(5), variant_ident).ok(); + } + VariantKind::Tuple(params) => { + write_tuple_variant_body(buf, 5, &e.name.value, variant, params); + } + VariantKind::Record(fields) => { + write_record_variant_body(buf, 5, variant, fields); + } + } + writeln!(buf, "{}__result = Some(__value);", indent(5)).ok(); + writeln!(buf, " }}").ok(); +} + +fn write_tuple_variant_body(buf: &mut String, indent_level: usize, enum_name: &str, variant: &Variant, params: &[(Spanned, Spanned)]) { + let pad = indent(indent_level); + writeln!(buf, "{}let mut __inner = rp::Decoder::new(__payload);", pad).ok(); + for (name_span, ty_span) in params { + let name = sanitize_ident(&name_span.value); + let ty_str = render_type(&ty_span.value); + writeln!(buf, "{}let mut __opt_{}: Option<{}> = None;", pad, name, ty_str).ok(); + } + writeln!(buf, "{}while let Some((__inner_field, __inner_wt)) = __inner.next_field()? {{", pad).ok(); + writeln!(buf, "{} match __inner_field {{", pad).ok(); + for (idx, (name_span, ty_span)) in params.iter().enumerate() { + let tag = idx + 1; + let name = sanitize_ident(&name_span.value); + let expected = expected_wire_type(&ty_span.value); + writeln!(buf, "{} {} => {{", indent(indent_level + 2), tag).ok(); + writeln!(buf, "{}if __inner_wt != {} {{", indent(indent_level + 3), expected).ok(); + writeln!(buf, "{}__inner.skip_field(__inner_wt)?;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}continue;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}{}", indent(indent_level + 3), "}").ok(); + let value_var = format!("__value_{}", name); + write_decode_value(buf, indent_level + 3, &value_var, &ty_span.value, "__inner"); + writeln!(buf, "{}__opt_{} = Some({});", indent(indent_level + 3), name, value_var).ok(); + writeln!(buf, "{} }}", indent(indent_level + 2)).ok(); + } + writeln!(buf, "{} _ => __inner.skip_field(__inner_wt)?,", indent(indent_level + 2)).ok(); + writeln!(buf, "{} }}", pad).ok(); + writeln!(buf, "{}{}", pad, "}").ok(); + + for (name_span, _) in params { + let name = sanitize_ident(&name_span.value); + let err_name = format!("{}::{}.{}", enum_name, variant.name.value, name_span.value); + writeln!(buf, "{}let {} = __opt_{}.ok_or(rp::DecodeError::Missing(\"{}\"))?;", pad, name, name, err_name).ok(); + } + + write!(buf, "{}let __value = Self::{} {{ ", pad, variant.name.value).ok(); + for (idx, (name_span, _)) in params.iter().enumerate() { + let name = sanitize_ident(&name_span.value); + if idx > 0 { + write!(buf, ", ").ok(); + } + write!(buf, "{}", name).ok(); + } + writeln!(buf, " }};").ok(); +} + +fn write_record_variant_body(buf: &mut String, indent_level: usize, variant: &Variant, fields: &[Field]) { + let pad = indent(indent_level); + writeln!(buf, "{}let mut __inner = rp::Decoder::new(__payload);", pad).ok(); + let mut array_helpers: Vec<(String, String, u64)> = Vec::new(); + for field in fields { + let field_ident = sanitize_ident(&field.name.value); + let ty_str = render_type(&field.ty.value); + writeln!(buf, "{}let mut {}: {} = Default::default();", pad, field_ident, ty_str).ok(); + if let Type::Array(_, len) = &field.ty.value { + let idx_name = format!("__idx_{}", field_ident); + writeln!(buf, "{}let mut {} = 0usize;", pad, idx_name).ok(); + array_helpers.push((field_ident.clone(), idx_name, *len)); + } + } + writeln!(buf, "{}while let Some((__inner_field, __inner_wt)) = __inner.next_field()? {{", pad).ok(); + writeln!(buf, "{} match __inner_field {{", pad).ok(); + for field in fields { + let field_ident = sanitize_ident(&field.name.value); + let array_info = array_helpers.iter().find(|(name, _, _)| name == &field_ident).map(|(_, idx, len)| (idx.as_str(), *len)); + write_variant_field_branch( + buf, + indent_level, + field.tag.value, + &field_ident, + &field.ty.value, + "__inner", + "__inner_wt", + &field_ident, + array_info, + ); + } + writeln!(buf, "{} _ => __inner.skip_field(__inner_wt)?,", indent(indent_level + 2)).ok(); + writeln!(buf, "{} }}", pad).ok(); + writeln!(buf, "{}{}", pad, "}").ok(); + + write!(buf, "{}let __value = Self::{} {{ ", pad, variant.name.value).ok(); + for (idx, field) in fields.iter().enumerate() { + let field_ident = sanitize_ident(&field.name.value); + if idx > 0 { + write!(buf, ", ").ok(); + } + write!(buf, "{}", field_ident).ok(); + } + writeln!(buf, " }};").ok(); +} + +fn write_variant_field_branch( + buf: &mut String, + indent_level: usize, + tag: u32, + field_ident: &str, + ty: &Type, + decoder_ident: &str, + wt_ident: &str, + target_expr: &str, + array_idx: Option<(&str, u64)>, +) { + let expected = expected_wire_type(ty); + writeln!(buf, "{}{} => {{", indent(indent_level + 2), tag).ok(); + writeln!(buf, "{}if {} != {} {{", indent(indent_level + 3), wt_ident, expected).ok(); + writeln!(buf, "{}{}.skip_field({})?;", indent(indent_level + 4), decoder_ident, wt_ident).ok(); + writeln!(buf, "{}continue;", indent(indent_level + 4)).ok(); + writeln!(buf, "{}{}", indent(indent_level + 3), "}").ok(); + write_field_decode_body(buf, indent_level + 3, target_expr, ty, decoder_ident, field_ident, array_idx); + writeln!(buf, "{}{}", indent(indent_level + 2), "}").ok(); +} + +fn expected_wire_type(ty: &Type) -> &'static str { + match ty { + Type::Option(inner) | Type::Vec(inner) | Type::Array(inner, _) => expected_wire_type(inner), + Type::Map(_, _) => "rp::WT_LEN", + Type::Path(p) => { + if let Some(kind) = builtin_kind(p) { + match kind { + BuiltinKind::Bool | BuiltinKind::U(_) | BuiltinKind::I(_) => "rp::WT_VARINT", + BuiltinKind::F32 => "rp::WT_32", + BuiltinKind::F64 => "rp::WT_64", + BuiltinKind::String | BuiltinKind::Bytes => "rp::WT_LEN", + } + } else { + "rp::WT_LEN" + } + } + } +} + +fn indent(level: usize) -> String { + " ".repeat(level) +} + +// ====== type / const ====== + +fn write_type_alias(buf: &mut String, t: &TypeAlias) { + writeln!(buf, "pub type {} = {};", t.name.value, render_type(&t.ty.value)).ok(); +} + +fn write_const(buf: &mut String, c: &Const) { + writeln!(buf, "pub const {}: {} = {};", c.name.value, render_type(&c.ty.value), render_literal(&c.value.value)).ok(); +} + +// ====== encode ステートメント生成 ====== + +fn gen_encode_stmt( + buf: &mut String, + dst: &str, + tag: u32, + expr: &str, + ty: &Type, + indent: usize, + // for enum/record inner where we already have bindings by ref + force_by_ref: Option, +) { + let pad = " ".repeat(indent); + match ty { + Type::Option(inner) => { + writeln!(buf, "{pad}if let Some(ref __v) = {expr} {{").ok(); + gen_encode_stmt(buf, dst, tag, "__v", inner, indent + 1, Some("&".to_string())); + writeln!(buf, "{pad}}}").ok(); + } + Type::Vec(inner) => { + writeln!(buf, "{pad}for __it in {expr}.iter() {{").ok(); + gen_encode_stmt(buf, dst, tag, "__it", inner, indent + 1, Some("&".to_string())); + writeln!(buf, "{pad}}}").ok(); + } + Type::Array(inner, _n) => { + writeln!(buf, "{pad}for __it in {expr}.iter() {{").ok(); + gen_encode_stmt(buf, dst, tag, "__it", inner, indent + 1, Some("&".to_string())); + writeln!(buf, "{pad}}}").ok(); + } + Type::Map(k, v) => { + writeln!(buf, "{pad}for (__k, __v) in {expr}.iter() {{").ok(); + writeln!(buf, "{pad} let mut __entry = Vec::new();").ok(); + // key -> @1 + gen_encode_stmt(buf, "__entry", 1, "__k", k, indent + 1, Some("&".to_string())); + // value -> @2 + gen_encode_stmt(buf, "__entry", 2, "__v", v, indent + 1, Some("&".to_string())); + writeln!(buf, "{pad} rp::write_key({dst}, {tag}, rp::WT_LEN);").ok(); + writeln!(buf, "{pad} rp::put_varint_u128({dst}, __entry.len() as u128);").ok(); + writeln!(buf, "{pad} {dst}.extend_from_slice(&__entry);").ok(); + writeln!(buf, "{pad}}}").ok(); + } + Type::Path(p) => { + if let Some(k) = builtin_kind(p) { + match k { + BuiltinKind::Bool => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_VARINT);").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}rp::put_varint_u64({dst}, if *{expr} {{1}} else {{0}});").ok(); + } else { + writeln!(buf, "{pad}rp::put_varint_u64({dst}, if {expr} {{1}} else {{0}});").ok(); + } + } + BuiltinKind::U(nbits) => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_VARINT);").ok(); + if nbits <= 64 { + if force_by_ref.is_some() { + writeln!(buf, "{pad}rp::put_varint_u64({dst}, *{expr} as u64);").ok(); + } else { + writeln!(buf, "{pad}rp::put_varint_u64({dst}, {expr} as u64);").ok(); + } + } else { + if force_by_ref.is_some() { + writeln!(buf, "{pad}rp::put_varint_u128({dst}, *{expr} as u128);").ok(); + } else { + writeln!(buf, "{pad}rp::put_varint_u128({dst}, {expr} as u128);").ok(); + } + } + } + BuiltinKind::I(_nbits) => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_VARINT);").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}let __zz = rp::zigzag_i128(*{expr} as i128);").ok(); + } else { + writeln!(buf, "{pad}let __zz = rp::zigzag_i128({expr} as i128);").ok(); + } + writeln!(buf, "{pad}rp::put_varint_u128({dst}, __zz);").ok(); + } + BuiltinKind::F32 => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_32);").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}{dst}.extend_from_slice(&(*{expr}).to_le_bytes());").ok(); + } else { + writeln!(buf, "{pad}{dst}.extend_from_slice(&({expr}).to_le_bytes());").ok(); + } + } + BuiltinKind::F64 => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_64);").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}{dst}.extend_from_slice(&(*{expr}).to_le_bytes());").ok(); + } else { + writeln!(buf, "{pad}{dst}.extend_from_slice(&({expr}).to_le_bytes());").ok(); + } + } + BuiltinKind::String => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_LEN);").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}let __b = (*{expr}).as_bytes();").ok(); + } else { + writeln!(buf, "{pad}let __b = ({expr}).as_bytes();").ok(); + } + writeln!(buf, "{pad}rp::write_len_delimited({dst}, __b);").ok(); + } + BuiltinKind::Bytes => { + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_LEN);").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}rp::write_len_delimited({dst}, &*{expr});").ok(); + } else { + writeln!(buf, "{pad}rp::write_len_delimited({dst}, &{expr});").ok(); + } + } + } + } else { + // message (struct/enum) + writeln!(buf, "{pad}let mut __tmp = Vec::new();").ok(); + if force_by_ref.is_some() { + writeln!(buf, "{pad}{expr}.encode_into(&mut __tmp);").ok(); + } else { + writeln!(buf, "{pad}{expr}.encode_into(&mut __tmp);").ok(); + } + writeln!(buf, "{pad}rp::write_key({dst}, {tag}, rp::WT_LEN);").ok(); + writeln!(buf, "{pad}rp::put_varint_u128({dst}, __tmp.len() as u128);").ok(); + writeln!(buf, "{pad}{dst}.extend_from_slice(&__tmp);").ok(); + } + } + } +} + +// ====== 型/リテラルのレンダリング ====== + +fn render_type(t: &Type) -> String { + match t { + Type::Path(p) => render_path_type(p), + Type::Option(inner) => format!("Option<{}>", render_type(inner)), + Type::Vec(inner) => format!("Vec<{}>", render_type(inner)), + Type::Map(k, v) => format!("std::collections::BTreeMap<{}, {}>", render_type(k), render_type(v)), + Type::Array(inner, n) => format!("[{}; {}]", render_type(inner), n), + } +} + +fn render_path_type(p: &Path) -> String { + if let Some(k) = builtin_kind(p) { + match k { + BuiltinKind::Bool => "bool".into(), + BuiltinKind::U(bits) => format!("u{bits}"), + BuiltinKind::I(bits) => format!("i{bits}"), + BuiltinKind::F32 => "f32".into(), + BuiltinKind::F64 => "f64".into(), + BuiltinKind::String => "String".into(), + BuiltinKind::Bytes => "Vec".into(), + } + } else { + let segs: Vec = p.segments.iter().map(|s| sanitize_ident(&s.value)).collect(); + segs.join("::") + } +} + +fn render_literal(l: &Literal) -> String { + match l { + Literal::Bool(b) => format!("{}", b), + Literal::Int(n) => format!("{}", n), + Literal::Float(f) => format!("{}", f), + Literal::String(s) => format!("{:?}", s), // Rustの文字列リテラルとしてエスケープ + Literal::Bytes(bytes) => render_bytes_literal(bytes), + } +} + +fn render_bytes_literal(bytes: &[u8]) -> String { + if bytes.is_empty() { + "Vec::::new()".into() + } else { + let inner = bytes.iter().map(|b| b.to_string()).collect::>().join(", "); + format!("vec![{}]", inner) + } +} + +#[derive(Copy, Clone)] +enum BuiltinKind { + Bool, + U(u32), + I(u32), + F32, + F64, + String, + Bytes, +} + +fn builtin_kind(p: &Path) -> Option { + if p.segments.len() != 1 { + return None; + } + let s = p.segments[0].value.as_str(); + Some(match s { + "bool" => BuiltinKind::Bool, + "u8" => BuiltinKind::U(8), + "u16" => BuiltinKind::U(16), + "u32" => BuiltinKind::U(32), + "u64" => BuiltinKind::U(64), + "u128" => BuiltinKind::U(128), + "i8" => BuiltinKind::I(8), + "i16" => BuiltinKind::I(16), + "i32" => BuiltinKind::I(32), + "i64" => BuiltinKind::I(64), + "i128" => BuiltinKind::I(128), + "f32" => BuiltinKind::F32, + "f64" => BuiltinKind::F64, + "string" => BuiltinKind::String, + "bytes" => BuiltinKind::Bytes, + _ => return None, + }) +} + +fn sanitize_ident(s: &str) -> String { + // ざっくり:キーワード衝突を避けたい時は末尾に '_' を付与 + match s { + "type" | "const" | "struct" | "enum" | "fn" | "mod" | "use" | "crate" | "super" | "self" | "match" | "loop" | "for" | "while" | "in" | "where" | "impl" | "trait" + | "move" | "async" | "await" | "ref" | "mut" | "pub" | "let" | "break" | "continue" | "return" => format!("{}_", s), + _ => s.to_string(), + } +} + +#[cfg(test)] +mod tests { + #[allow(unused)] + use super::*; + + #[test] + fn codegen_test() {} +} +*/ diff --git a/entrypoints/rocketpack-compiler/src/config.rs b/entrypoints/rocketpack-compiler/src/config.rs new file mode 100644 index 0000000..f1a3b87 --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/config.rs @@ -0,0 +1,72 @@ +use std::path::{Path, PathBuf}; + +use serde::Deserialize; +use serde_yaml_ng::{Mapping, from_str}; + +use crate::error::ConfigError; + +#[derive(Debug, Deserialize)] +pub struct AppConfig { + pub version: u32, + #[serde(default)] + pub sources: Vec, + #[serde(default)] + pub generators: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct SourceConfig { + #[serde(rename = "base_dir")] + pub base_dir: String, + #[serde(default)] + pub includes: Vec, + #[serde(default)] + pub excludes: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct GeneratorConfig { + pub id: String, + pub plugin: String, + #[serde(default)] + pub options: Option, + #[serde(default)] + pub targets: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct GeneratorTargetConfig { + pub pattern: String, + #[serde(default)] + pub options: Option, +} + +impl AppConfig { + pub async fn load(path: impl AsRef) -> Result { + let path_buf: PathBuf = path.as_ref().into(); + let contents = tokio::fs::read_to_string(&path_buf).await?; + Self::from_yaml(&contents) + } + + pub fn from_yaml(yaml: &str) -> Result { + Ok(from_str(yaml)?) + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use testresult::TestResult; + + use super::*; + + #[tokio::test] + async fn config_load_test() -> TestResult { + let config_path = Path::new("./data/rocketpack.yaml"); + let config = AppConfig::load(config_path).await?; + println!("{:?}", config); + + Ok(()) + } +} diff --git a/entrypoints/rocketpack-compiler/src/error.rs b/entrypoints/rocketpack-compiler/src/error.rs new file mode 100644 index 0000000..d7d8cbc --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/error.rs @@ -0,0 +1,120 @@ +use std::path::PathBuf; + +use crate::parser::ast::Span; + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum CodegenError { + #[error("unexpected token: {0}")] + Unexpected(&'static str), + + #[error("parse error")] + Parse(#[from] ParseErrorBundle), + + #[error("config error: {0}")] + Config(#[from] ConfigError), + + #[error("other error: {0}")] + Other(String), +} + +#[derive(Error, Debug)] +pub struct ParseErrorBundle { + path: PathBuf, + text: String, + errors: Vec, +} + +impl std::fmt::Display for ParseErrorBundle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + for error in self.errors.iter() { + let (line, column) = Self::offset_to_line_col(&self.text, error.span.start); + writeln!(f, "{path}:{line}:{column}: {error_kind}", path = self.path.display(), error_kind = error.kind)?; + if let Some((line_str, caret)) = Self::line_context(&self.text, error.span.start, error.span.end) { + writeln!(f, " {line_str}")?; + writeln!(f, " {caret}")?; + } + } + + Ok(()) + } +} + +impl ParseErrorBundle { + fn offset_to_line_col(source: &str, offset: usize) -> (usize, usize) { + let clamped = offset.min(source.len()); + let line = source[..clamped].chars().filter(|&c| c == '\n').count() + 1; + let line_start = source[..clamped].rfind('\n').map(|idx| idx + 1).unwrap_or(0); + let column = source[line_start..clamped].chars().count() + 1; + (line, column) + } + + fn line_context(source: &str, start: usize, end: usize) -> Option<(String, String)> { + if source.is_empty() { + return None; + } + let start = start.min(source.len()); + let end = end.min(source.len()); + + let line_start = source[..start].rfind('\n').map(|i| i + 1).unwrap_or(0); + let line_end = source[end..].find('\n').map(|i| end + i).unwrap_or_else(|| source.len()); + + let line_slice = &source[line_start..line_end]; + let prefix = &source[line_start..start]; + let highlight = &source[start..end]; + + let prefix_cols = prefix.chars().count(); + let highlight_cols = highlight.chars().count().max(1); + + let mut caret = String::new(); + caret.push_str(&" ".repeat(prefix_cols)); + caret.push('^'); + if highlight_cols > 1 { + caret.push_str(&"~".repeat(highlight_cols - 1)); + } + + Some((line_slice.to_string(), caret)) + } +} + +#[derive(Debug)] +pub struct ParseError { + pub kind: ParseErrorKind, + pub span: Span, +} + +impl ParseError { + pub fn new(kind: ParseErrorKind, start: usize, end: usize) -> Self { + Self { kind, span: Span { start, end } } + } +} + +#[derive(Error, Debug)] +pub enum ParseErrorKind { + #[error("unexpected token: {0}")] + Unexpected(&'static str), + + #[error("expected {expected}, found {found}")] + Expected { expected: &'static str, found: &'static str }, + + #[error("invalid number literal")] + InvalidNumber, + + #[error("duplicate attribute or item")] + Duplicate, + + #[error("unterminated block or structure")] + Unterminated, + + #[error("other error: {0}")] + Other(String), +} + +#[derive(Error, Debug)] +pub enum ConfigError { + #[error("設定ファイルを読み込めませんでした: {0}")] + Io(#[from] std::io::Error), + #[error("設定ファイルを解析できませんでした: {0}")] + Parse(#[from] serde_yaml_ng::Error), +} diff --git a/entrypoints/rocketpack-compiler/src/main.rs b/entrypoints/rocketpack-compiler/src/main.rs new file mode 100644 index 0000000..60199e4 --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/main.rs @@ -0,0 +1,95 @@ +use std::path::{Path, PathBuf}; + +use clap::{Parser, Subcommand}; +use tracing_subscriber::EnvFilter; + +use crate::{codegen::generate, config::AppConfig, error::CodegenError}; + +mod codegen; +mod config; +mod error; +mod parser; + +#[derive(Debug, Parser)] +#[command(author, version, about = "rocketpack format compiler", long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Debug, Subcommand)] +enum Commands { + Compile { + #[arg(value_name = "DIR", default_value = "./")] + dir: PathBuf, + }, +} + +#[tokio::main] +async fn main() { + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,sqlx=off")); + tracing_subscriber::fmt().with_env_filter(filter).with_target(false).init(); + + if let Err(_err) = run().await { + std::process::exit(1); + } +} + +async fn run() -> Result<(), CodegenError> { + let cli = Cli::parse(); + + match cli.command { + Commands::Compile { dir } => run_compile(&dir).await?, + } + + Ok(()) +} + +async fn run_compile(dir: &Path) -> Result<(), CodegenError> { + let conf = AppConfig::load(dir.join("rocketpack.yaml")).await?; + + if let Err(e) = generate(conf).await { + match e { + CodegenError::Unexpected(_) => todo!(), + CodegenError::Parse(parse_errors) => todo!(), + CodegenError::Config(config_error) => todo!(), + CodegenError::Other(_) => todo!(), + } + } + + todo!() +} + +// let input_path = cli.input; +// let source = match fs::read_to_string(&input_path) { +// Ok(contents) => contents, +// Err(err) => { +// eprintln!("読み込み失敗: {}: {}", input_path.display(), err); +// return Err(1); +// } +// }; + +// match generate(&source) { +// Ok(code) => { +// if let Some(path) = cli.output.as_deref() { +// if let Some(parent) = path.parent() { +// if let Err(err) = fs::create_dir_all(parent) { +// eprintln!("ディレクトリ作成失敗: {}: {}", parent.display(), err); +// return Err(1); +// } +// } +// if let Err(err) = fs::write(path, code) { +// eprintln!("書き込み失敗: {}: {}", path.display(), err); +// return Err(1); +// } +// } else { +// print!("{code}"); +// } +// Ok(()) +// } +// Err(errors) => { +// display_parse_errors(input_path.display().to_string(), &source, &errors); +// Err(1) +// } +// } +//} diff --git a/entrypoints/rocketpack-compiler/src/parser.rs b/entrypoints/rocketpack-compiler/src/parser.rs new file mode 100644 index 0000000..078d9ee --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/parser.rs @@ -0,0 +1,535 @@ +use crate::{ + error::{ParseError, ParseErrorKind}, + parser::{ast::*, lexer::*}, +}; + +pub mod ast; +pub mod lexer; + +pub fn parse(path: &Path) -> (File, Vec) { + let (tokens, lex_errors) = lexer::lex(""); + + if !lex_errors.is_empty() { + return (File::default(), lex_errors); + } + + let mut p = Parser::new(tokens); + let file = p.parse_file(); + (file, p.errors) +} + +pub struct Parser { + tokens: Vec, + i: usize, + pub errors: Vec, +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { tokens, errors: Vec::new(), i: 0 } + } + + // ===== 基本ユーティリティ ===== + + fn peek(&self) -> Option<&SpannedToken> { + self.tokens.get(self.i) + } + + fn nth(&self, n: usize) -> Option<&SpannedToken> { + self.tokens.get(self.i + n) + } + + fn bump(&mut self) -> Option { + let t = self.tokens.get(self.i).cloned(); + self.i += 1; + t + } + + fn at(&self, t: Token) -> bool { + self.peek().map(|x| std::mem::discriminant(&x.token)) == Some(std::mem::discriminant(&t)) + } + + fn expect(&mut self, want: Token, name: &'static str) -> Option { + if self.at(want.clone()) { + self.bump() + } else { + let (s, e) = self.error_here(ParseErrorKind::Expected { + expected: name, + found: self.peek_name(), + }); + Some(SpannedToken { + token: want, + span: Span { start: s, end: e }, + }) + } + } + + fn peek_name(&self) -> &'static str { + match self.peek().map(|t| &t.token) { + Some(Token::Ident(_)) => "identifier", + Some(Token::Int(_)) | Some(Token::Hex(_)) => "integer", + Some(Token::Float(_)) => "float", + Some(Token::Str(_)) => "string", + Some(Token::Bytes(_)) => "bytes", + Some(Token::At) => "@", + Some(Token::Semi) => ";", + Some(Token::Colon) => ":", + Some(Token::Comma) => ",", + Some(Token::Eq) => "=", + Some(Token::LBrace) => "{", + Some(Token::RBrace) => "}", + Some(Token::LParen) => "(", + Some(Token::RParen) => ")", + Some(Token::LBracket) => "[", + Some(Token::RBracket) => "]", + Some(Token::Lt) => "<", + Some(Token::Gt) => ">", + Some(Token::Dots) => "..", + Some(Token::PathSep) => "::", + None => "EOF", + } + } + + fn error_here(&mut self, kind: ParseErrorKind) -> (usize, usize) { + let (s, e) = if let Some(t) = self.peek() { (t.span.start, t.span.end) } else { (0, 0) }; + self.errors.push(ParseError::new(kind, s, e)); + (s, e) + } + + // ===== エントリ ===== + + pub fn parse_file(&mut self) -> File { + let mut file = File::default(); + + // 任意の順序でトップレベルを読み込む + while let Some(_) = self.peek() { + // キーワードは Ident で来るので先読みして判定 + if let Some(kw) = self.peek_keyword() { + match kw.as_str() { + "version" => { + if file.version.is_some() { + let (s, e) = self.error_here(ParseErrorKind::Duplicate); + // 食い進めておく + self.parse_version(); + // 破棄 + let _ = (s, e); + } else { + file.version = Some(self.parse_version()); + } + } + "package" => { + if file.package.is_some() { + let (s, e) = self.error_here(ParseErrorKind::Duplicate); + let _ = self.parse_package(); + let _ = (s, e); + } else { + file.package = Some(self.parse_package()); + } + } + "use" => { + file.uses.push(self.parse_use()); + } + "struct" => { + file.items.push(Item::Struct(self.parse_struct())); + } + "enum" => { + file.items.push(Item::Enum(self.parse_enum())); + } + "type" => { + file.items.push(Item::TypeAlias(self.parse_type_alias())); + } + "const" => { + file.items.push(Item::Const(self.parse_const())); + } + _ => { + // 未対応トップレベル + self.error_here(ParseErrorKind::Unexpected("unknown top-level")); + self.bump(); + } + } + } else { + // 何かしらのトークンを消費して前に進む + self.error_here(ParseErrorKind::Unexpected("unexpected token at top-level")); + self.bump(); + } + } + + file + } + + fn peek_keyword(&self) -> Option { + match self.peek()?.token.clone() { + Token::Ident(s) => Some(s), + _ => None, + } + } + + // ===== トップレベル要素 ===== + + fn parse_version(&mut self) -> Spanned { + let start = self.expect_ident_kw("version").span.start; + let version = self.expect_int_u32(); + self.expect(Token::Semi, ";"); + Spanned::new(version, start, self.prev_end()) + } + + fn parse_package(&mut self) -> Spanned { + let start = self.expect_ident_kw("package").span.start; + let path = self.parse_path(); + self.expect(Token::Semi, ";"); + Spanned::new(path, start, self.prev_end()) + } + + fn parse_use(&mut self) -> Use { + let _start = self.expect_ident_kw("use").span.start; + let path = Spanned::new(self.parse_path(), self.prev_start(), self.prev_end()); + // MVP: `as Alias` のみ対応 + let alias = if self.is_ident_kw("as") { + let a = self.expect_ident(); + Some(a) + } else { + None + }; + self.expect(Token::Semi, ";"); + Use { path, alias } + } + + fn parse_struct(&mut self) -> Struct { + let _kw = self.expect_ident_kw("struct"); + let name = self.expect_ident(); + self.expect(Token::LBrace, "{"); + let mut fields = Vec::new(); + while !self.at(Token::RBrace) && self.peek().is_some() { + if self.at(Token::At) { + fields.push(self.parse_field()); + } else { + self.error_here(ParseErrorKind::Unexpected("expected field or reserved")); + self.bump(); + } + } + self.expect(Token::RBrace, "}"); + Struct { name, fields } + } + + fn parse_enum(&mut self) -> Enum { + let _kw = self.expect_ident_kw("enum"); + let name = self.expect_ident(); + self.expect(Token::LBrace, "{"); + let mut variants = Vec::new(); + while !self.at(Token::RBrace) && self.peek().is_some() { + if self.at(Token::At) { + variants.push(self.parse_variant()); + } else { + self.error_here(ParseErrorKind::Unexpected("expected @tag for variant")); + self.bump(); + } + } + self.expect(Token::RBrace, "}"); + Enum { name, variants } + } + + fn parse_type_alias(&mut self) -> TypeAlias { + let _kw = self.expect_ident_kw("type"); + let name = self.expect_ident(); + self.expect(Token::Eq, "="); + let ty = self.expect_type(); + self.expect(Token::Semi, ";"); + TypeAlias { name, ty } + } + + fn parse_const(&mut self) -> Const { + let _kw = self.expect_ident_kw("const"); + let name = self.expect_ident(); + self.expect(Token::Colon, ":"); + let ty = self.expect_type(); + self.expect(Token::Eq, "="); + let value = self.expect_literal(); + self.expect(Token::Semi, ";"); + Const { name, ty, value } + } + + // ===== struct: field / reserved ===== + + fn parse_field(&mut self) -> Field { + let _at = self.expect(Token::At, "@").unwrap(); + let tag = self.expect_int_u32_spanned(); + let name = self.expect_ident(); + self.expect(Token::Colon, ":"); + let ty = self.expect_type(); + let default = if self.at(Token::Eq) { + self.bump(); + Some(self.expect_literal()) + } else { + None + }; + self.expect(Token::Semi, ";"); + Field { tag, name, ty, default } + } + + // ===== enum: variant ===== + + fn parse_variant(&mut self) -> Variant { + let _at = self.expect(Token::At, "@"); + let tag = self.expect_int_u32_spanned(); + let name = self.expect_ident(); + let kind = if self.at(Token::Semi) { + self.bump(); + VariantKind::Unit + } else if self.at(Token::LParen) { + let params = self.parse_tuple_params(); + self.expect(Token::Semi, ";"); + VariantKind::Tuple(params) + } else if self.at(Token::LBrace) { + // レコード: 中身は struct の field と同じ書式(@番号付き) + self.bump(); + let mut fields = Vec::new(); + while !self.at(Token::RBrace) && self.peek().is_some() { + if self.at(Token::At) { + fields.push(self.parse_field()); + } else { + self.error_here(ParseErrorKind::Unexpected("expected @tag inside record variant")); + self.bump(); + } + } + self.expect(Token::RBrace, "}"); + self.expect(Token::Semi, ";"); + VariantKind::Record(fields) + } else { + self.error_here(ParseErrorKind::Unexpected("expected ';', '(' or '{' after variant name")); + VariantKind::Unit + }; + Variant { tag, name, kind } + } + + fn parse_tuple_params(&mut self) -> Vec<(Spanned, Spanned)> { + self.expect(Token::LParen, "("); + let mut v = Vec::new(); + if !self.at(Token::RParen) { + loop { + let name = self.expect_ident(); + self.expect(Token::Colon, ":"); + let ty = self.expect_type(); + v.push((name, ty)); + if self.at(Token::Comma) { + self.bump(); + } else { + break; + } + } + } + self.expect(Token::RParen, ")"); + v + } + + // ===== 型 ===== + + fn expect_type(&mut self) -> Spanned { + let start = self.curr_start(); + let ty = self.parse_type_inner(); + let end = self.prev_end(); + Spanned::new(ty, start, end) + } + + fn parse_type_inner(&mut self) -> Type { + // Option / Vec / Map / [T;N] / Path + if self.is_ident_kw("Option") { + self.expect(Token::Lt, "<"); + let inner = self.expect_type(); + self.expect(Token::Gt, ">"); + Type::Option(Box::new(inner.value)) + } else if self.is_ident_kw("Vec") { + self.expect(Token::Lt, "<"); + let inner = self.expect_type(); + self.expect(Token::Gt, ">"); + Type::Vec(Box::new(inner.value)) + } else if matches!(self.peek_keyword().as_deref(), Some("map") | Some("Map")) { + let _ = self.expect_ident(); + self.expect(Token::Lt, "<"); + let k = self.expect_type(); + self.expect(Token::Comma, ","); + let v = self.expect_type(); + self.expect(Token::Gt, ">"); + Type::Map(Box::new(k.value), Box::new(v.value)) + } else if self.at(Token::LBracket) { + self.bump(); + let inner = self.expect_type(); + self.expect(Token::Semi, ";"); + let n = self.expect_int_u64(); + self.expect(Token::RBracket, "]"); + Type::Array(Box::new(inner.value), n) + } else { + Type::Path(self.parse_path()) + } + } + + fn parse_path(&mut self) -> Path { + let mut segs = Vec::new(); + segs.push(self.expect_ident()); + while self.at(Token::PathSep) { + self.bump(); + segs.push(self.expect_ident()); + } + Path { segments: segs } + } + + // ===== リテラル ===== + + fn expect_literal(&mut self) -> Spanned { + let (start, val, end) = match self.peek().cloned() { + Some(SpannedToken { token: Token::Ident(s), span }) if s == "true" || s == "false" => { + self.bump(); + (span.start, Literal::Bool(s == "true"), span.end) + } + Some(SpannedToken { token: Token::Int(n), span }) => { + self.bump(); + (span.start, Literal::Int(n), span.end) + } + Some(SpannedToken { token: Token::Hex(n), span }) => { + self.bump(); + (span.start, Literal::Int(n), span.end) + } + Some(SpannedToken { token: Token::Float(f), span }) => { + self.bump(); + (span.start, Literal::Float(f), span.end) + } + Some(SpannedToken { token: Token::Str(s), span }) => { + self.bump(); + (span.start, Literal::String(s), span.end) + } + Some(SpannedToken { token: Token::Bytes(b), span }) => { + self.bump(); + (span.start, Literal::Bytes(b), span.end) + } + _ => { + let (s, e) = self.error_here(ParseErrorKind::Expected { + expected: "literal", + found: self.peek_name(), + }); + (s, Literal::Int(0), e) + } + }; + Spanned::new(val, start, end) + } + + // ===== 諸々の expect/状態 ===== + + fn expect_ident(&mut self) -> Spanned { + match self.bump() { + Some(t) => match t.token { + Token::Ident(s) => Spanned::new(s, t.span.start, t.span.end), + _ => { + let (s, e) = self.error_here(ParseErrorKind::Expected { + expected: "identifier", + found: self.peek_name(), + }); + Spanned::new("_".to_string(), s, e) + } + }, + None => { + let (s, e) = self.error_here(ParseErrorKind::Expected { + expected: "identifier", + found: "EOF", + }); + Spanned::new("_".to_string(), s, e) + } + } + } + + fn is_ident_kw(&mut self, kw: &str) -> bool { + if let Some(SpannedToken { token: Token::Ident(s), .. }) = self.peek() { + if s == kw { + self.bump(); + return true; + } + } + false + } + fn expect_ident_kw(&mut self, kw: &'static str) -> Spanned { + let t = self.expect_ident(); + if t.value != kw { + self.errors.push(ParseError::new( + ParseErrorKind::Expected { + expected: kw, + found: "identifier", + }, + t.span.start, + t.span.end, + )); + } + t + } + + fn expect_int_u32(&mut self) -> u32 { + match self.bump() { + Some(t) => match t.token { + Token::Int(n) | Token::Hex(n) => n as u32, + _ => { + self.error_here(ParseErrorKind::Expected { + expected: "integer", + found: self.peek_name(), + }); + 0 + } + }, + None => { + self.error_here(ParseErrorKind::Expected { + expected: "integer", + found: "EOF", + }); + 0 + } + } + } + fn expect_int_u32_spanned(&mut self) -> Spanned { + match self.bump() { + Some(t) => match t.token { + Token::Int(n) | Token::Hex(n) => Spanned::new(n as u32, t.span.start, t.span.end), + _ => { + let (s, e) = self.error_here(ParseErrorKind::Expected { + expected: "integer", + found: self.peek_name(), + }); + Spanned::new(0, s, e) + } + }, + None => { + let (s, e) = self.error_here(ParseErrorKind::Expected { + expected: "integer", + found: "EOF", + }); + Spanned::new(0, s, e) + } + } + } + fn expect_int_u64(&mut self) -> u64 { + match self.bump() { + Some(t) => match t.token { + Token::Int(n) | Token::Hex(n) => n as u64, + _ => { + self.error_here(ParseErrorKind::Expected { + expected: "integer", + found: self.peek_name(), + }); + 0 + } + }, + None => { + self.error_here(ParseErrorKind::Expected { + expected: "integer", + found: "EOF", + }); + 0 + } + } + } + + fn curr_start(&self) -> usize { + self.peek().map(|t| t.span.start).unwrap_or(0) + } + fn prev_start(&self) -> usize { + if self.i == 0 { 0 } else { self.tokens[self.i - 1].span.start } + } + fn prev_end(&self) -> usize { + if self.i == 0 { 0 } else { self.tokens[self.i - 1].span.end } + } +} diff --git a/entrypoints/rocketpack-compiler/src/parser/ast.rs b/entrypoints/rocketpack-compiler/src/parser/ast.rs new file mode 100644 index 0000000..6298c3e --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/parser/ast.rs @@ -0,0 +1,130 @@ +#[derive(Debug, Clone)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +#[derive(Debug, Clone)] +pub struct Spanned { + pub value: T, + pub span: Span, +} + +impl Spanned { + pub fn new(value: T, start: usize, end: usize) -> Self { + Self { value, span: Span { start, end } } + } +} + +// ===== トップレベル ===== + +#[derive(Debug, Clone, Default)] +pub struct File { + pub version: Option>, + pub package: Option>, + pub uses: Vec, + pub items: Vec, +} + +#[derive(Debug, Clone)] +pub struct Use { + pub path: Spanned, + pub alias: Option>, +} + +#[derive(Debug, Clone)] +pub enum Item { + Struct(Struct), + Enum(Enum), + TypeAlias(TypeAlias), + Const(Const), +} + +impl std::fmt::Display for Item { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Item::Struct(s) => write!(f, "struct {}", s.name.value), + Item::Enum(e) => write!(f, "enum {}", e.name.value), + Item::TypeAlias(t) => write!(f, "type {}", t.name.value), + Item::Const(c) => write!(f, "const {}", c.name.value), + } + } +} + +// ===== 型表現 ===== + +#[derive(Debug, Clone)] +pub struct Path { + pub segments: Vec>, // a::b::C +} + +#[derive(Debug, Clone)] +pub enum Type { + Path(Path), // bool, u32, MyType, pkg::T + Option(Box), + Vec(Box), + Map(Box, Box), + Array(Box, u64), // [T; N] +} + +#[derive(Debug, Clone)] +pub enum Literal { + Bool(bool), + Int(u128), + Float(f64), + String(String), + Bytes(Vec), +} + +// ===== struct ===== + +#[derive(Debug, Clone)] +pub struct Struct { + pub name: Spanned, + pub fields: Vec, +} + +#[derive(Debug, Clone)] +pub struct Field { + pub tag: Spanned, // @N + pub name: Spanned, + pub ty: Spanned, + pub default: Option>, +} + +// ===== enum ===== + +#[derive(Debug, Clone)] +pub struct Enum { + pub name: Spanned, + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct Variant { + pub tag: Spanned, + pub name: Spanned, + pub kind: VariantKind, +} + +#[derive(Debug, Clone)] +pub enum VariantKind { + Unit, // @1 Foo; + Tuple(Vec<(Spanned, Spanned)>), // @2 Bar(x: T, y: U); + Record(Vec), // @3 Baz { @1 x: T; ... } +} + +// ===== type alias / const ===== + +#[derive(Debug, Clone)] +pub struct TypeAlias { + pub name: Spanned, + pub ty: Spanned, +} + +#[derive(Debug, Clone)] +pub struct Const { + pub name: Spanned, + pub ty: Spanned, + pub value: Spanned, +} diff --git a/entrypoints/rocketpack-compiler/src/parser/lexer.rs b/entrypoints/rocketpack-compiler/src/parser/lexer.rs new file mode 100644 index 0000000..8b1f59b --- /dev/null +++ b/entrypoints/rocketpack-compiler/src/parser/lexer.rs @@ -0,0 +1,231 @@ +use logos::Logos; + +use crate::{ + error::{ParseError, ParseErrorKind}, + parser::ast::Span, +}; + +#[derive(Logos, Debug, Clone, PartialEq)] +#[logos(skip r"[ \t\r\n\f]+")] +#[logos(skip r"//[^\n]*")] +#[logos(skip r"/\*([^*]|\*[^/])*\*/")] +pub enum Token { + // Separators / punctuation + #[token("@")] + At, + #[token(";")] + Semi, + #[token(":")] + Colon, + #[token(",")] + Comma, + #[token("=")] + Eq, + #[token("{")] + LBrace, + #[token("}")] + RBrace, + #[token("(")] + LParen, + #[token(")")] + RParen, + #[token("[")] + LBracket, + #[token("]")] + RBracket, + #[token("<")] + Lt, + #[token(">")] + Gt, + #[token("..")] + Dots, + #[token("::")] + PathSep, + + // Literals + #[regex(r#""([^"\\]|\\.)*""#, parse_string)] + Str(String), + + #[regex(r#"b"([^"\\]|\\.)*""#, parse_bytes)] + Bytes(Vec), + + #[regex(r"[0-9][_0-9]*", parse_int)] + Int(u128), + #[regex(r"0x[0-9A-Fa-f][_0-9A-Fa-f]*", parse_hex)] + Hex(u128), + #[regex(r"[0-9][_0-9]*\.[0-9][_0-9]*([eE][+-]?[0-9][_0-9]*)?", parse_float)] + Float(f64), + + // Identifiers + #[regex(r"[A-Za-z_][A-Za-z0-9_]*", parse_ident, priority = 2)] + Ident(String), +} + +fn parse_string(lex: &mut logos::Lexer) -> Option { + let slice = lex.slice(); + let raw = &slice[1..slice.len() - 1]; + Some(parse_string_literal(raw)) +} + +fn parse_bytes(lex: &mut logos::Lexer) -> Option> { + let slice = lex.slice(); + let raw = &slice[2..slice.len() - 1]; + Some(parse_bytes_literal(raw)) +} + +fn parse_string_literal(s: &str) -> String { + let mut out = String::new(); + let mut it = s.chars(); + while let Some(c) = it.next() { + if c == '\\' { + match it.next() { + Some('n') => out.push('\n'), + Some('r') => out.push('\r'), + Some('t') => out.push('\t'), + Some('"') => out.push('"'), + Some('\\') => out.push('\\'), + Some('x') => { + let hi = it.next(); + let lo = it.next(); + if let (Some(hi), Some(lo)) = (hi, lo) { + if let Some(val) = hex_pair(hi, lo) { + if let Some(ch) = char::from_u32(val as u32) { + out.push(ch); + continue; + } + } + out.push('\\'); + out.push('x'); + out.push(hi); + out.push(lo); + } else { + out.push('\\'); + out.push('x'); + if let Some(hi) = hi { + out.push(hi); + } + if let Some(lo) = lo { + out.push(lo); + } + } + } + Some(x) => { + out.push('\\'); + out.push(x); + } + None => out.push('\\'), + } + } else { + out.push(c); + } + } + out +} + +fn parse_bytes_literal(s: &str) -> Vec { + let mut out = Vec::new(); + let mut it = s.chars(); + while let Some(c) = it.next() { + if c == '\\' { + match it.next() { + Some('n') => out.push(b'\n'), + Some('r') => out.push(b'\r'), + Some('t') => out.push(b'\t'), + Some('"') => out.push(b'"'), + Some('\\') => out.push(b'\\'), + Some('x') => { + let hi = it.next(); + let lo = it.next(); + if let (Some(hi), Some(lo)) = (hi, lo) { + if let Some(val) = hex_pair(hi, lo) { + out.push(val); + continue; + } + out.push(b'\\'); + out.push(b'x'); + out.push(hi as u8); + out.push(lo as u8); + } else { + out.push(b'\\'); + out.push(b'x'); + if let Some(hi) = hi { + out.push(hi as u8); + } + if let Some(lo) = lo { + out.push(lo as u8); + } + } + } + Some(x) => { + out.push(b'\\'); + out.push(x as u8); + } + None => out.push(b'\\'), + } + } else { + let mut buf = [0u8; 4]; + let encoded = c.encode_utf8(&mut buf); + out.extend_from_slice(encoded.as_bytes()); + } + } + out +} + +fn hex_pair(hi: char, lo: char) -> Option { + let high = hex_digit(hi)?; + let low = hex_digit(lo)?; + Some((high << 4) | low) +} + +fn hex_digit(c: char) -> Option { + match c { + '0'..='9' => Some(c as u8 - b'0'), + 'a'..='f' => Some(10 + (c as u8 - b'a')), + 'A'..='F' => Some(10 + (c as u8 - b'A')), + _ => None, + } +} + +fn parse_int(lex: &mut logos::Lexer) -> Option { + let s = lex.slice().replace('_', ""); + s.parse().ok() +} +fn parse_hex(lex: &mut logos::Lexer) -> Option { + let s = lex.slice().trim_start_matches("0x").replace('_', ""); + u128::from_str_radix(&s, 16).ok() +} +fn parse_float(lex: &mut logos::Lexer) -> Option { + let s = lex.slice().replace('_', ""); + s.parse().ok() +} +fn parse_ident(lex: &mut logos::Lexer) -> Option { + Some(lex.slice().to_owned()) +} + +#[derive(Debug, Clone)] +pub struct SpannedToken { + pub token: Token, + pub span: Span, +} + +pub fn lex(input: &str) -> (Vec, Vec) { + let mut errors = Vec::new(); + let mut lexer = Token::lexer(input); + let mut out = Vec::new(); + while let Some(result) = lexer.next() { + let range = lexer.span(); + match result { + Ok(tok) => out.push(SpannedToken { + token: tok, + span: Span { + start: range.start, + end: range.end, + }, + }), + Err(_) => { + errors.push(ParseError::new(ParseErrorKind::Unexpected("invalid token"), range.start, range.end)); + } + } + } + (out, errors) +} diff --git a/modules/omnikit/src/error.rs b/modules/omnikit/src/error.rs index 710dea3..2093519 100644 --- a/modules/omnikit/src/error.rs +++ b/modules/omnikit/src/error.rs @@ -97,9 +97,15 @@ impl std::fmt::Display for ErrorKind { } } -impl From for Error { - fn from(e: omnius_core_rocketpack::Error) -> Error { - Error::from_error(e, ErrorKind::SerdeError).with_message("rocket pack error") +impl From for Error { + fn from(e: omnius_core_rocketpack::RocketPackEncoderError) -> Error { + Error::from_error(e, ErrorKind::SerdeError).with_message("rocket pack encode error") + } +} + +impl From for Error { + fn from(e: omnius_core_rocketpack::RocketPackDecoderError) -> Error { + Error::from_error(e, ErrorKind::SerdeError).with_message("rocket pack decode error") } } diff --git a/modules/omnikit/src/model/omni_addr.rs b/modules/omnikit/src/model/omni_addr.rs index 4966527..b7d4691 100644 --- a/modules/omnikit/src/model/omni_addr.rs +++ b/modules/omnikit/src/model/omni_addr.rs @@ -64,8 +64,11 @@ impl OmniAddr { } } -impl From<&str> for OmniAddr { - fn from(value: &str) -> Self { +impl From for OmniAddr +where + T: AsRef, +{ + fn from(value: T) -> Self { OmniAddr::new(value) } } diff --git a/modules/omnikit/src/model/omni_agreement.rs b/modules/omnikit/src/model/omni_agreement.rs index 2f000aa..e74d98e 100644 --- a/modules/omnikit/src/model/omni_agreement.rs +++ b/modules/omnikit/src/model/omni_agreement.rs @@ -1,5 +1,6 @@ use bitflags::bitflags; use chrono::{DateTime, Utc}; +use omnius_core_rocketpack::primitive::Timestamp64; use rand::TryRngCore; use rand_core::OsRng; @@ -19,9 +20,12 @@ impl std::fmt::Display for OmniAgreementAlgorithmType { } } -impl From<&str> for OmniAgreementAlgorithmType { - fn from(value: &str) -> Self { - match value { +impl From for OmniAgreementAlgorithmType +where + T: AsRef, +{ + fn from(value: T) -> Self { + match value.as_ref() { "x25519" => OmniAgreementAlgorithmType::X25519, _ => OmniAgreementAlgorithmType::None, } @@ -39,14 +43,14 @@ impl OmniAgreementAlgorithmType { #[derive(Debug, Clone, PartialEq, Eq)] pub struct OmniAgreement { - pub created_time: DateTime, pub algorithm_type: OmniAgreementAlgorithmType, pub secret_key: Vec, pub public_key: Vec, + pub created_time: DateTime, } impl OmniAgreement { - pub fn new(created_time: DateTime, algorithm_type: OmniAgreementAlgorithmType) -> Result { + pub fn new(algorithm_type: OmniAgreementAlgorithmType, created_time: DateTime) -> Result { let secret_key = x25519_dalek::StaticSecret::random_from_rng(&mut OsRng.unwrap_err()); let public_key = x25519_dalek::PublicKey::from(&secret_key); @@ -54,26 +58,26 @@ impl OmniAgreement { let public_key = public_key.as_bytes().to_vec(); Ok(Self { - created_time, algorithm_type, secret_key, public_key, + created_time, }) } pub fn gen_agreement_public_key(&self) -> OmniAgreementPublicKey { OmniAgreementPublicKey { - created_time: self.created_time, algorithm_type: self.algorithm_type.clone(), public_key: self.public_key.clone(), + created_time: self.created_time, } } pub fn gen_agreement_private_key(&self) -> OmniAgreementPrivateKey { OmniAgreementPrivateKey { - created_time: self.created_time, algorithm_type: self.algorithm_type.clone(), secret_key: self.secret_key.clone(), + created_time: self.created_time, } } @@ -98,103 +102,166 @@ impl OmniAgreement { } } -impl RocketMessage for OmniAgreement { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_timestamp64(value.created_time.into()); - writer.put_str(value.algorithm_type.as_str()); - writer.put_bytes(&value.secret_key); - writer.put_bytes(&value.public_key); +impl RocketPackStruct for OmniAgreement { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(4)?; + + encoder.write_u64(0)?; + encoder.write_string(value.algorithm_type.as_str())?; + + encoder.write_u64(1)?; + encoder.write_bytes(&value.secret_key)?; + + encoder.write_u64(2)?; + encoder.write_bytes(&value.public_key)?; + + encoder.write_u64(3)?; + encoder.write_struct(&Timestamp64::from(value.created_time))?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let created_time = reader - .get_timestamp64()? - .to_date_time() - .ok_or_else(|| RocketPackError::new(RocketPackErrorKind::InvalidFormat).with_message("invalid timestamp64"))?; - let algorithm_type = OmniAgreementAlgorithmType::from(reader.get_string(1024)?.as_str()); - let secret_key = reader.get_bytes(1024)?; - let public_key = reader.get_bytes(1024)?; + let mut algorithm_type: OmniAgreementAlgorithmType = OmniAgreementAlgorithmType::None; + let mut secret_key: Vec = Vec::new(); + let mut public_key: Vec = Vec::new(); + let mut created_time: DateTime = DateTime::::from_timestamp(0, 0).unwrap(); + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => algorithm_type = OmniAgreementAlgorithmType::from(decoder.read_string()?), + 1 => secret_key = decoder.read_bytes_vec()?, + 2 => public_key = decoder.read_bytes_vec()?, + 3 => { + created_time = decoder + .read_struct::()? + .to_date_time() + .ok_or(RocketPackDecoderError::Other("created_time parse error"))? + } + _ => decoder.skip_field()?, + } + } Ok(Self { - created_time, algorithm_type, secret_key, public_key, + created_time, }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct OmniAgreementPublicKey { - pub created_time: DateTime, pub algorithm_type: OmniAgreementAlgorithmType, pub public_key: Vec, + pub created_time: DateTime, } -impl RocketMessage for OmniAgreementPublicKey { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_timestamp64(value.created_time.into()); - writer.put_str(value.algorithm_type.as_str()); - writer.put_bytes(&value.public_key); +impl RocketPackStruct for OmniAgreementPublicKey { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(3)?; + + encoder.write_u64(0)?; + encoder.write_string(value.algorithm_type.as_str())?; + + encoder.write_u64(1)?; + encoder.write_bytes(&value.public_key)?; + + encoder.write_u64(2)?; + encoder.write_struct(&Timestamp64::from(value.created_time))?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let created_time = reader - .get_timestamp64()? - .to_date_time() - .ok_or_else(|| RocketPackError::new(RocketPackErrorKind::InvalidFormat).with_message("invalid timestamp64"))?; - let algorithm_type = OmniAgreementAlgorithmType::from(reader.get_string(1024)?.as_str()); - let public_key = reader.get_bytes(1024)?; + let mut algorithm_type: OmniAgreementAlgorithmType = OmniAgreementAlgorithmType::None; + let mut public_key: Vec = Vec::new(); + let mut created_time: DateTime = DateTime::::from_timestamp(0, 0).unwrap(); + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => algorithm_type = OmniAgreementAlgorithmType::from(decoder.read_string()?), + 1 => public_key = decoder.read_bytes_vec()?, + 2 => { + created_time = decoder + .read_struct::()? + .to_date_time() + .ok_or(RocketPackDecoderError::Other("created_time parse error"))? + } + _ => decoder.skip_field()?, + } + } Ok(Self { - created_time, algorithm_type, public_key, + created_time, }) } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct OmniAgreementPrivateKey { - pub created_time: DateTime, pub algorithm_type: OmniAgreementAlgorithmType, pub secret_key: Vec, + pub created_time: DateTime, } -impl RocketMessage for OmniAgreementPrivateKey { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_timestamp64(value.created_time.into()); - writer.put_str(value.algorithm_type.as_str()); - writer.put_bytes(&value.secret_key); +impl RocketPackStruct for OmniAgreementPrivateKey { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(3)?; + + encoder.write_u64(0)?; + encoder.write_string(value.algorithm_type.as_str())?; + + encoder.write_u64(1)?; + encoder.write_bytes(&value.secret_key)?; + + encoder.write_u64(2)?; + encoder.write_struct(&Timestamp64::from(value.created_time))?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let created_time = reader - .get_timestamp64()? - .to_date_time() - .ok_or_else(|| RocketPackError::new(RocketPackErrorKind::InvalidFormat).with_message("invalid timestamp64"))?; - let algorithm_type = OmniAgreementAlgorithmType::from(reader.get_string(1024)?.as_str()); - let secret_key = reader.get_bytes(1024)?; + let mut algorithm_type: OmniAgreementAlgorithmType = OmniAgreementAlgorithmType::None; + let mut secret_key: Vec = Vec::new(); + let mut created_time: DateTime = DateTime::::from_timestamp(0, 0).unwrap(); + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => algorithm_type = OmniAgreementAlgorithmType::from(decoder.read_string()?), + 1 => secret_key = decoder.read_bytes_vec()?, + 2 => { + created_time = decoder + .read_struct::()? + .to_date_time() + .ok_or(RocketPackDecoderError::Other("created_time parse error"))? + } + _ => decoder.skip_field()?, + } + } Ok(Self { - created_time, algorithm_type, secret_key, + created_time, }) } } @@ -207,8 +274,9 @@ mod tests { #[tokio::test] async fn simple_test() -> TestResult { - let agreement1 = OmniAgreement::new(Utc::now(), OmniAgreementAlgorithmType::X25519)?; - let agreement2 = OmniAgreement::new(Utc::now(), OmniAgreementAlgorithmType::X25519)?; + let example_time: DateTime = DateTime::parse_from_rfc3339("2000-01-01T01:01:01Z")?.to_utc(); + let agreement1 = OmniAgreement::new(OmniAgreementAlgorithmType::X25519, example_time)?; + let agreement2 = OmniAgreement::new(OmniAgreementAlgorithmType::X25519, example_time)?; let public_key1 = agreement1.gen_agreement_public_key(); let private_key1 = agreement1.gen_agreement_private_key(); diff --git a/modules/omnikit/src/model/omni_hash.rs b/modules/omnikit/src/model/omni_hash.rs index c6eb4b4..7784fe0 100644 --- a/modules/omnikit/src/model/omni_hash.rs +++ b/modules/omnikit/src/model/omni_hash.rs @@ -77,20 +77,35 @@ impl FromStr for OmniHash { } } -impl RocketMessage for OmniHash { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_u32(value.typ.bits()); - writer.put_bytes(&value.value); +impl RocketPackStruct for OmniHash { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(2)?; + + encoder.write_u64(0)?; + encoder.write_u32(value.typ.bits())?; + + encoder.write_u32(1)?; + encoder.write_bytes(&value.value)?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let typ = OmniHashAlgorithmType::from_bits_truncate(reader.get_u32()?); - let value = reader.get_bytes(1024)?; + let mut typ: OmniHashAlgorithmType = OmniHashAlgorithmType::None; + let mut value: Vec = Vec::new(); + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => typ = OmniHashAlgorithmType::from_bits_truncate(decoder.read_u32()?), + 1 => value = decoder.read_bytes_vec()?, + _ => decoder.skip_field()?, + } + } Ok(Self { typ, value }) } diff --git a/modules/omnikit/src/model/omni_sign.rs b/modules/omnikit/src/model/omni_sign.rs index 0964bfc..8ef3c25 100644 --- a/modules/omnikit/src/model/omni_sign.rs +++ b/modules/omnikit/src/model/omni_sign.rs @@ -7,8 +7,6 @@ use rand::TryRngCore; use rand_core::OsRng; use sha3::{Digest, Sha3_256}; -use omnius_core_rocketpack::{Result as RocketPackResult, RocketMessage, RocketMessageReader, RocketMessageWriter}; - use crate::{prelude::*, service::converter::OmniBase}; bitflags! { @@ -25,9 +23,12 @@ impl std::fmt::Display for OmniSignType { } } -impl From<&str> for OmniSignType { - fn from(value: &str) -> Self { - match value { +impl From for OmniSignType +where + T: AsRef, +{ + fn from(value: T) -> Self { + match value.as_ref() { "ed25519_sha3_256_base64url" => OmniSignType::Ed25519_Sha3_256_Base64Url, _ => OmniSignType::None, } @@ -79,22 +80,40 @@ impl OmniSigner { } } -impl RocketMessage for OmniSigner { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_str(value.typ.to_string().as_str()); - writer.put_str(&value.name); - writer.put_bytes(&value.key); +impl RocketPackStruct for OmniSigner { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(3)?; + + encoder.write_u64(0)?; + encoder.write_string(value.typ.as_str())?; + + encoder.write_u32(1)?; + encoder.write_string(&value.name)?; + + encoder.write_u32(2)?; + encoder.write_bytes(&value.key)?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let typ = OmniSignType::from(reader.get_string(1024)?.as_str()); - let name = reader.get_string(1024)?.parse()?; - let key = reader.get_bytes(1024)?; + let mut typ: OmniSignType = OmniSignType::None; + let mut name: String = "".to_string(); + let mut key: Vec = Vec::new(); + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => typ = OmniSignType::from(decoder.read_string()?), + 1 => name = decoder.read_string()?, + 2 => key = decoder.read_bytes_vec()?, + _ => decoder.skip_field()?, + } + } Ok(Self { typ, name, key }) } @@ -148,24 +167,45 @@ impl OmniCert { } } -impl RocketMessage for OmniCert { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_str(value.typ.to_string().as_str()); - writer.put_str(&value.name); - writer.put_bytes(&value.public_key); - writer.put_bytes(&value.value); +impl RocketPackStruct for OmniCert { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(4)?; + + encoder.write_u64(0)?; + encoder.write_string(value.typ.as_str())?; + + encoder.write_u32(1)?; + encoder.write_string(&value.name)?; + + encoder.write_u32(2)?; + encoder.write_bytes(&value.public_key)?; + + encoder.write_u32(3)?; + encoder.write_bytes(&value.value)?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let typ = OmniSignType::from(reader.get_string(1024)?.as_str()); - let name = reader.get_string(1024)?.parse()?; - let public_key = reader.get_bytes(1024)?; - let value = reader.get_bytes(1024)?; + let mut typ: OmniSignType = OmniSignType::None; + let mut name: String = "".to_string(); + let mut public_key: Vec = Vec::new(); + let mut value: Vec = Vec::new(); + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => typ = OmniSignType::from(decoder.read_string()?), + 1 => name = decoder.read_string()?, + 2 => public_key = decoder.read_bytes_vec()?, + 3 => value = decoder.read_bytes_vec()?, + _ => decoder.skip_field()?, + } + } Ok(Self { typ, name, public_key, value }) } diff --git a/modules/omnikit/src/prelude.rs b/modules/omnikit/src/prelude.rs index 238d3d7..e49c535 100644 --- a/modules/omnikit/src/prelude.rs +++ b/modules/omnikit/src/prelude.rs @@ -1,5 +1,5 @@ #[allow(unused)] -pub use omnius_core_rocketpack::{Error as RocketPackError, ErrorKind as RocketPackErrorKind, Result as RocketPackResult, RocketMessage, RocketMessageReader, RocketMessageWriter}; +pub use omnius_core_rocketpack::{FieldType, RocketPackBytesDecoder, RocketPackDecoder, RocketPackDecoderError, RocketPackEncoder, RocketPackEncoderError, RocketPackStruct}; #[allow(unused)] pub use crate::error::{Error, ErrorKind}; diff --git a/modules/omnikit/src/service/connection/secure/v1/auth.rs b/modules/omnikit/src/service/connection/secure/v1/auth.rs index 228df48..e157346 100644 --- a/modules/omnikit/src/service/connection/secure/v1/auth.rs +++ b/modules/omnikit/src/service/connection/secure/v1/auth.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use chrono::Utc; use hkdf::Hkdf; -use omnius_core_rocketpack::RocketMessage; use parking_lot::Mutex; use sha3::{Digest, Sha3_256}; use tokio::{ @@ -81,8 +80,8 @@ where hash_algorithm_type: HashAlgorithmType::Sha3_256, }; let other_profile = { - self.sender.lock().await.send(my_profile.export()?).await?; - ProfileMessage::import(&mut self.receiver.lock().await.recv().await?)? + self.sender.lock().await.send(my_profile.export()?.into()).await?; + ProfileMessage::import(&self.receiver.lock().await.recv().await?)? }; let key_exchange_algorithm_type = my_profile.key_exchange_algorithm_type.clone() & other_profile.key_exchange_algorithm_type.clone(); @@ -93,20 +92,20 @@ where let (other_sign, secret) = match key_exchange_algorithm_type { KeyExchangeAlgorithmType::X25519 => { let now = self.clock.now(); - let my_agreement = OmniAgreement::new(now, OmniAgreementAlgorithmType::X25519)?; + let my_agreement = OmniAgreement::new(OmniAgreementAlgorithmType::X25519, now)?; let other_agreement_public_key = { - self.sender.lock().await.send(my_agreement.gen_agreement_public_key().export()?).await?; - OmniAgreementPublicKey::import(&mut self.receiver.lock().await.recv().await?)? + self.sender.lock().await.send(my_agreement.gen_agreement_public_key().export()?.into()).await?; + OmniAgreementPublicKey::import(&self.receiver.lock().await.recv().await?)? }; if let Some(my_signer) = self.signer.as_ref() { let my_hash = Self::gen_hash(&my_profile, &my_agreement.gen_agreement_public_key(), &hash_algorithm_type)?; let my_sign = my_signer.sign(&my_hash)?; - self.sender.lock().await.send(my_sign.export()?).await?; + self.sender.lock().await.send(my_sign.export()?.into()).await?; } let other_sign = if other_profile.auth_type == AuthType::Sign { - let other_cert = OmniCert::import(&mut self.receiver.lock().await.recv().await?)?; + let other_cert = OmniCert::import(&self.receiver.lock().await.recv().await?)?; let other_hash = Self::gen_hash(&other_profile, &other_agreement_public_key, &hash_algorithm_type)?; other_cert.verify(&other_hash)?; diff --git a/modules/omnikit/src/service/connection/secure/v1/message.rs b/modules/omnikit/src/service/connection/secure/v1/message.rs index bf8841a..bd16eb1 100644 --- a/modules/omnikit/src/service/connection/secure/v1/message.rs +++ b/modules/omnikit/src/service/connection/secure/v1/message.rs @@ -45,7 +45,6 @@ bitflags! { } } -// TODO impl std::fmt::Display for KeyExchangeAlgorithmType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let typ = match self { @@ -164,28 +163,55 @@ pub(crate) struct ProfileMessage { pub hash_algorithm_type: HashAlgorithmType, } -impl RocketMessage for ProfileMessage { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_bytes(&value.session_id); - writer.put_u32(value.auth_type.bits()); - writer.put_u32(value.key_exchange_algorithm_type.bits()); - writer.put_u32(value.key_derivation_algorithm_type.bits()); - writer.put_u32(value.cipher_algorithm_type.bits()); - writer.put_u32(value.hash_algorithm_type.bits()); +impl RocketPackStruct for ProfileMessage { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(6)?; + + encoder.write_u64(0)?; + encoder.write_bytes(&value.session_id)?; + + encoder.write_u64(1)?; + encoder.write_u32(value.auth_type.bits())?; + + encoder.write_u64(2)?; + encoder.write_u32(value.key_exchange_algorithm_type.bits())?; + + encoder.write_u64(3)?; + encoder.write_u32(value.key_derivation_algorithm_type.bits())?; + + encoder.write_u64(4)?; + encoder.write_u32(value.cipher_algorithm_type.bits())?; + + encoder.write_u64(5)?; + encoder.write_u32(value.hash_algorithm_type.bits())?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let session_id = reader.get_bytes(1024)?; - let auth_type = AuthType::from_bits_truncate(reader.get_u32()?); - let key_exchange_algorithm_type = KeyExchangeAlgorithmType::from_bits_truncate(reader.get_u32()?); - let key_derivation_algorithm_type = KeyDerivationAlgorithmType::from_bits_truncate(reader.get_u32()?); - let cipher_algorithm_type = CipherAlgorithmType::from_bits_truncate(reader.get_u32()?); - let hash_algorithm_type = HashAlgorithmType::from_bits_truncate(reader.get_u32()?); + let mut session_id: Vec = Vec::new(); + let mut auth_type = AuthType::None; + let mut key_exchange_algorithm_type = KeyExchangeAlgorithmType::None; + let mut key_derivation_algorithm_type = KeyDerivationAlgorithmType::None; + let mut cipher_algorithm_type = CipherAlgorithmType::None; + let mut hash_algorithm_type = HashAlgorithmType::None; + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => session_id = decoder.read_bytes_vec()?, + 1 => auth_type = AuthType::from_bits_truncate(decoder.read_u32()?), + 2 => key_exchange_algorithm_type = KeyExchangeAlgorithmType::from_bits_truncate(decoder.read_u32()?), + 3 => key_derivation_algorithm_type = KeyDerivationAlgorithmType::from_bits_truncate(decoder.read_u32()?), + 4 => cipher_algorithm_type = CipherAlgorithmType::from_bits_truncate(decoder.read_u32()?), + 5 => hash_algorithm_type = HashAlgorithmType::from_bits_truncate(decoder.read_u32()?), + _ => decoder.skip_field()?, + } + } Ok(Self { session_id, @@ -218,7 +244,7 @@ mod tests { }; let b = p.export()?; - let p2 = ProfileMessage::import(&mut b.clone())?; + let p2 = ProfileMessage::import(&b.clone())?; assert_eq!(p, p2); diff --git a/modules/omnikit/src/service/remoting.rs b/modules/omnikit/src/service/remoting.rs index f7476b4..2261af9 100644 --- a/modules/omnikit/src/service/remoting.rs +++ b/modules/omnikit/src/service/remoting.rs @@ -79,18 +79,30 @@ mod tests { pub value: i32, } - impl RocketMessage for TestMessage { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_i32(value.value); + impl RocketPackStruct for TestMessage { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(1)?; + + encoder.write_u64(0)?; + encoder.write_i32(value.value)?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let value = reader.get_i32()?; + let count = decoder.read_map()?; + + let mut value: i32 = 0; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => value = decoder.read_i32()?, + _ => decoder.skip_field()?, + } + } Ok(Self { value }) } diff --git a/modules/omnikit/src/service/remoting/caller.rs b/modules/omnikit/src/service/remoting/caller.rs index ccf5048..e165e73 100644 --- a/modules/omnikit/src/service/remoting/caller.rs +++ b/modules/omnikit/src/service/remoting/caller.rs @@ -5,8 +5,6 @@ use tokio::{ sync::Mutex as TokioMutex, }; -use omnius_core_rocketpack::RocketMessage; - use crate::{ prelude::*, service::connection::codec::{FramedReceiver, FramedSend as _, FramedSender}, @@ -44,7 +42,7 @@ where version: OmniRemotingVersion::V1, function_id, }; - sender.lock().await.send(hello_message.export()?).await?; + sender.lock().await.send(hello_message.export()?.into()).await?; Ok(()) } diff --git a/modules/omnikit/src/service/remoting/hello_message.rs b/modules/omnikit/src/service/remoting/hello_message.rs index 8c72a39..d031aec 100644 --- a/modules/omnikit/src/service/remoting/hello_message.rs +++ b/modules/omnikit/src/service/remoting/hello_message.rs @@ -6,20 +6,27 @@ pub enum OmniRemotingVersion { V1, } +impl OmniRemotingVersion { + pub fn as_str(&self) -> &'static str { + match self { + &Self::V1 => "v1", + _ => "unknown", + } + } +} + impl std::fmt::Display for OmniRemotingVersion { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let typ = match self { - &OmniRemotingVersion::V1 => "v1", - _ => "unknown", - }; - write!(f, "{typ}") + write!(f, "{}", self.as_str()) } } -// TODO -impl From<&str> for OmniRemotingVersion { - fn from(value: &str) -> Self { - match value { +impl From for OmniRemotingVersion +where + T: AsRef, +{ + fn from(value: T) -> Self { + match value.as_ref() { "v1" => OmniRemotingVersion::V1, _ => OmniRemotingVersion::Unknown, } @@ -32,20 +39,35 @@ pub struct HelloMessage { pub function_id: u32, } -impl RocketMessage for HelloMessage { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> RocketPackResult<()> { - writer.put_str(&value.version.to_string()); - writer.put_u32(value.function_id); +impl RocketPackStruct for HelloMessage { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(2)?; + + encoder.write_u64(0)?; + encoder.write_string(value.version.as_str())?; + + encoder.write_u64(1)?; + encoder.write_u32(value.function_id)?; Ok(()) } - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> RocketPackResult + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result where Self: Sized, { - let version = OmniRemotingVersion::from(reader.get_string(1024)?.as_str()); - let function_id = reader.get_u32()?; + let mut version: OmniRemotingVersion = OmniRemotingVersion::Unknown; + let mut function_id: u32 = 0; + + let count = decoder.read_map()?; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => version = OmniRemotingVersion::from(decoder.read_string()?), + 1 => function_id = decoder.read_u32()?, + _ => decoder.skip_field()?, + } + } Ok(Self { version, function_id }) } diff --git a/modules/omnikit/src/service/remoting/listener.rs b/modules/omnikit/src/service/remoting/listener.rs index 49c1701..5e1ec54 100644 --- a/modules/omnikit/src/service/remoting/listener.rs +++ b/modules/omnikit/src/service/remoting/listener.rs @@ -5,8 +5,6 @@ use tokio::{ sync::Mutex as TokioMutex, }; -use omnius_core_rocketpack::RocketMessage; - use crate::{ prelude::*, service::connection::codec::{FramedReceiver, FramedRecv as _, FramedSender}, @@ -40,8 +38,8 @@ where } async fn handshake(receiver: Arc>>) -> Result { - let mut v = receiver.lock().await.recv().await?; - let hello_message = HelloMessage::import(&mut v)?; + let v = receiver.lock().await.recv().await?; + let hello_message = HelloMessage::import(&v)?; if hello_message.version == OmniRemotingVersion::V1 { return Ok(hello_message.function_id); diff --git a/modules/omnikit/src/service/remoting/stream.rs b/modules/omnikit/src/service/remoting/stream.rs index c69c5a5..6d186bc 100644 --- a/modules/omnikit/src/service/remoting/stream.rs +++ b/modules/omnikit/src/service/remoting/stream.rs @@ -30,20 +30,20 @@ where pub async fn send(&self, message: T) -> Result<()> where - T: RocketMessage + Send + Sync + 'static, + T: RocketPackStruct + Send + Sync + 'static, { let bytes = message.export()?; - self.sender.lock().await.send(bytes).await?; + self.sender.lock().await.send(bytes.into()).await?; Ok(()) } pub async fn recv(&self) -> Result where - T: RocketMessage + Send + Sync + 'static, + T: RocketPackStruct + Send + Sync + 'static, { - let mut bytes = self.receiver.lock().await.recv().await?; - let message = T::import(&mut bytes)?; + let bytes = self.receiver.lock().await.recv().await?; + let message = T::import(&bytes)?; Ok(message) } diff --git a/modules/rocketpack/docs/varint_spec.md b/modules/rocketpack/docs/varint_spec.md new file mode 100644 index 0000000..cf2e449 --- /dev/null +++ b/modules/rocketpack/docs/varint_spec.md @@ -0,0 +1,28 @@ +# RocketPack Varint Specification + +## Overview +RocketPack varints encode unsigned integers using a 1-byte header followed by an optional +little-endian payload. Values between `0x00` and `0x7F` (inclusive) are represented directly +by the header byte. Larger values use specific header markers (`0x80`–`0x84`) to signal how +many payload bytes follow. The design keeps small values compact while falling back to +fixed-width payloads for larger values. + +## Encoding Format + +| Header | Payload Length | Value Range | +| ------ | -------------- | ------------------------------------------------ | +| 0x00–0x7F | 0 bytes | Encoded value equals the header byte. | +| 0x80 | 1 byte | Next byte contains an unsigned 8-bit value. | +| 0x81 | 2 bytes | Next two bytes contain an unsigned 16-bit value. | +| 0x82 | 4 bytes | Next four bytes contain an unsigned 32-bit value. | +| 0x83 | 8 bytes | Next eight bytes contain an unsigned 64-bit value.| +| 0x84 | 8 bytes | Next eight bytes contain an unsigned 128-bit value.| + +All payloads are serialized in little-endian order. + +## Signed Integer Encoding +Signed integers are mapped to unsigned integers using ZigZag encoding prior to varint +serialization. ZigZag encoding converts an `n`-bit signed integer to an `n`-bit unsigned +integer by left-shifting the value by one bit and XOR-ing with the sign bit cast to +unsigned form. Decoding reverses this transformation after the varint is read. This allows +small negative values to retain a compact 1-byte representation. diff --git a/modules/rocketpack/src/empty_rocket_pack.rs b/modules/rocketpack/src/empty_rocket_pack.rs index 426da23..f936903 100644 --- a/modules/rocketpack/src/empty_rocket_pack.rs +++ b/modules/rocketpack/src/empty_rocket_pack.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{RocketMessage, RocketMessageReader, RocketMessageWriter, prelude::*}; +use crate::RocketPackStruct; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EmptyRocketMessage; -impl RocketMessage for EmptyRocketMessage { - fn pack(_writer: &mut RocketMessageWriter, _value: &Self, _depth: u32) -> Result<()> { +impl RocketPackStruct for EmptyRocketMessage { + fn pack(_encoder: &mut impl crate::RocketPackEncoder, _value: &Self) -> std::result::Result<(), crate::RocketPackEncoderError> { Ok(()) } - fn unpack(_reader: &mut RocketMessageReader, _depth: u32) -> Result + fn unpack(_decoder: &mut impl crate::RocketPackDecoder) -> std::result::Result where Self: Sized, { - Ok(Self {}) + Ok(Self) } } diff --git a/modules/rocketpack/src/error.rs b/modules/rocketpack/src/error.rs deleted file mode 100644 index 36f2086..0000000 --- a/modules/rocketpack/src/error.rs +++ /dev/null @@ -1,109 +0,0 @@ -use std::backtrace::Backtrace; - -use omnius_core_base::error::OmniError; - -pub struct Error { - kind: ErrorKind, - message: Option, - source: Option>, - backtrace: Backtrace, -} - -impl OmniError for Error { - type ErrorKind = ErrorKind; - - fn new(kind: Self::ErrorKind) -> Self { - Self { - kind, - message: None, - source: None, - backtrace: Backtrace::capture(), - } - } - - fn from_error>>(source: E, kind: Self::ErrorKind) -> Self { - Self { - kind, - message: None, - source: Some(source.into()), - backtrace: Backtrace::disabled(), - } - } - - fn kind(&self) -> &Self::ErrorKind { - &self.kind - } - - fn message(&self) -> Option<&str> { - self.message.as_deref() - } - - fn backtrace(&self) -> &Backtrace { - &self.backtrace - } - - fn with_message>(mut self, message: S) -> Self { - self.message = Some(message.into()); - self - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.source.as_ref().map(|s| &**s as &(dyn std::error::Error + 'static)) - } -} - -impl std::fmt::Debug for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - OmniError::fmt(self, f) - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - OmniError::fmt(self, f) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ErrorKind { - Unknown, - VarintError, - - InvalidFormat, - EndOfStream, - TooLarge, - TooDepth, -} - -impl std::fmt::Display for ErrorKind { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ErrorKind::Unknown => write!(fmt, "unknown"), - ErrorKind::VarintError => write!(fmt, "varint error"), - ErrorKind::InvalidFormat => write!(fmt, "invalid format"), - ErrorKind::EndOfStream => write!(fmt, "end of stream"), - ErrorKind::TooLarge => write!(fmt, "Too large"), - ErrorKind::TooDepth => write!(fmt, "Too depth"), - } - } -} - -impl From for Error { - fn from(_: std::convert::Infallible) -> Self { - Error::new(ErrorKind::Unknown) - } -} - -impl From for Error { - fn from(e: crate::primitive::VarintError) -> Self { - Error::from_error(e, ErrorKind::VarintError).with_message("varint error") - } -} - -impl From for Error { - fn from(e: std::array::TryFromSliceError) -> Self { - Error::from_error(e, ErrorKind::InvalidFormat).with_message("failed to convert slice to array") - } -} diff --git a/modules/rocketpack/src/field_type.rs b/modules/rocketpack/src/field_type.rs new file mode 100644 index 0000000..2b5d09c --- /dev/null +++ b/modules/rocketpack/src/field_type.rs @@ -0,0 +1,45 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum FieldType { + Bool, + U8, + U16, + U32, + U64, + I8, + I16, + I32, + I64, + F16, + F32, + F64, + Bytes, + String, + Array, + Map, + Unknown { major: u8, info: u8 }, +} + +impl std::fmt::Display for FieldType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FieldType::Bool => write!(f, "bool"), + FieldType::U8 => write!(f, "u8"), + FieldType::U16 => write!(f, "u16"), + FieldType::U32 => write!(f, "u32"), + FieldType::U64 => write!(f, "u64"), + FieldType::I8 => write!(f, "i8"), + FieldType::I16 => write!(f, "i16"), + FieldType::I32 => write!(f, "i32"), + FieldType::I64 => write!(f, "i64"), + FieldType::F16 => write!(f, "f16"), + FieldType::F32 => write!(f, "f32"), + FieldType::F64 => write!(f, "f64"), + FieldType::Bytes => write!(f, "bytes"), + FieldType::String => write!(f, "string"), + FieldType::Array => write!(f, "array"), + FieldType::Map => write!(f, "map"), + FieldType::Unknown { major, info } => write!(f, "unknown(major={major}, info={info})"), + } + } +} diff --git a/modules/rocketpack/src/lib.rs b/modules/rocketpack/src/lib.rs index 7ebce58..a5738ee 100644 --- a/modules/rocketpack/src/lib.rs +++ b/modules/rocketpack/src/lib.rs @@ -1,19 +1,14 @@ mod empty_rocket_pack; -mod error; +mod field_type; mod prelude; pub mod primitive; -mod rocket_message; -mod rocket_message_reader; -mod rocket_message_writer; - -mod result { - #[allow(unused)] - pub type Result = std::result::Result; -} +mod rocket_pack_codec_test; +mod rocket_pack_decoder; +mod rocket_pack_encoder; +mod rocket_pack_struct; pub use empty_rocket_pack::*; -pub use error::*; -pub use result::*; -pub use rocket_message::*; -pub use rocket_message_reader::*; -pub use rocket_message_writer::*; +pub use field_type::*; +pub use rocket_pack_decoder::*; +pub use rocket_pack_encoder::*; +pub use rocket_pack_struct::*; diff --git a/modules/rocketpack/src/prelude.rs b/modules/rocketpack/src/prelude.rs index 44839b2..e78b454 100644 --- a/modules/rocketpack/src/prelude.rs +++ b/modules/rocketpack/src/prelude.rs @@ -1,11 +1,5 @@ -#[allow(unused)] -pub use crate::error::{Error, ErrorKind}; - #[allow(unused)] pub use omnius_core_base::error::OmniError as _; -#[allow(unused)] -pub use crate::result::Result; - #[allow(unused)] pub use tracing::{debug, error, info, trace, warn}; diff --git a/modules/rocketpack/src/primitive.rs b/modules/rocketpack/src/primitive.rs index 44a8b07..6082498 100644 --- a/modules/rocketpack/src/primitive.rs +++ b/modules/rocketpack/src/primitive.rs @@ -1,5 +1,3 @@ mod timestamp; -mod varint; pub use timestamp::*; -pub use varint::*; diff --git a/modules/rocketpack/src/primitive/timestamp.rs b/modules/rocketpack/src/primitive/timestamp.rs index 56ab29c..416b72e 100644 --- a/modules/rocketpack/src/primitive/timestamp.rs +++ b/modules/rocketpack/src/primitive/timestamp.rs @@ -1,4 +1,6 @@ -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Timelike, Utc}; + +use crate::{RocketPackDecoder, RocketPackDecoderError, RocketPackEncoder, RocketPackEncoderError, RocketPackStruct}; pub struct Timestamp64 { pub seconds: i64, @@ -15,8 +17,25 @@ impl Timestamp64 { } impl From> for Timestamp64 { - fn from(t: DateTime) -> Timestamp64 { - Timestamp64 { seconds: t.timestamp() } + fn from(value: DateTime) -> Self { + Self::new(value.timestamp()) + } +} + +impl RocketPackStruct for Timestamp64 { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_i64(value.seconds)?; + + Ok(()) + } + + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result + where + Self: Sized, + { + let value = decoder.read_i64()?; + + Ok(Self::new(value)) } } @@ -36,50 +55,68 @@ impl Timestamp96 { } impl From> for Timestamp96 { - fn from(t: DateTime) -> Timestamp96 { - Timestamp96 { - seconds: t.timestamp(), - nanos: t.timestamp_subsec_nanos(), + fn from(value: DateTime) -> Self { + Self::new(value.timestamp(), value.nanosecond()) + } +} + +impl RocketPackStruct for Timestamp96 { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + encoder.write_map(2)?; + + encoder.write_u64(0)?; + encoder.write_i64(value.seconds)?; + + encoder.write_u64(1)?; + encoder.write_u32(value.nanos)?; + + Ok(()) + } + + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result + where + Self: Sized, + { + let count = decoder.read_map()?; + + let mut seconds: i64 = 0; + let mut nanos: u32 = 0; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => seconds = decoder.read_i64()?, + 1 => nanos = decoder.read_u32()?, + _ => decoder.skip_field()?, + } } + + Ok(Self::new(seconds, nanos)) } } #[cfg(test)] mod tests { - use chrono::Duration; + use chrono::{DateTime, Utc}; use testresult::TestResult; use super::*; #[test] - fn timestamp64_test() -> TestResult { - let t: DateTime = DateTime::parse_from_rfc3339("2000-01-01T00:00:00Z")?.into(); - - let ts1 = Timestamp64::new(946684800); - assert_eq!(ts1.seconds, 946684800); - assert_eq!(ts1.to_date_time(), Some(t)); - - let ts2 = Timestamp64::from(t); - assert_eq!(ts2.seconds, 946684800); - assert_eq!(ts2.to_date_time(), Some(t)); + fn normal_timestamp64_test() -> TestResult { + let example_time: DateTime = DateTime::parse_from_rfc3339("2000-01-01T01:01:01Z")?.to_utc(); + let t = Timestamp64::from(example_time); + let t2 = t.to_date_time().unwrap(); + assert_eq!(example_time, t2); Ok(()) } #[test] - fn timestamp96_test() -> TestResult { - let t: DateTime = DateTime::parse_from_rfc3339("2000-01-01T00:00:00Z")?.into(); - let t = t.checked_add_signed(Duration::nanoseconds(123456789)).unwrap(); - - let ts1 = Timestamp96::new(946684800, 123456789); - assert_eq!(ts1.seconds, 946684800); - assert_eq!(ts1.nanos, 123456789); - assert_eq!(ts1.to_date_time(), Some(t)); - - let ts2 = Timestamp96::from(t); - assert_eq!(ts2.seconds, 946684800); - assert_eq!(ts2.nanos, 123456789); - assert_eq!(ts2.to_date_time(), Some(t)); + fn normal_timestamp96_test() -> TestResult { + let example_time: DateTime = DateTime::parse_from_rfc3339("2000-01-01T01:01:01.001Z")?.to_utc(); + let t = Timestamp96::from(example_time); + let t2 = t.to_date_time().unwrap(); + assert_eq!(example_time, t2); Ok(()) } diff --git a/modules/rocketpack/src/primitive/varint.rs b/modules/rocketpack/src/primitive/varint.rs deleted file mode 100644 index 65db7b7..0000000 --- a/modules/rocketpack/src/primitive/varint.rs +++ /dev/null @@ -1,573 +0,0 @@ -use tokio_util::bytes::{Buf, BufMut}; - -#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)] -pub enum VarintError { - #[error("invalid header (value {value})")] - InvalidHeader { value: u8 }, - - #[error("end of input")] - EndOfInput, - - #[error("too small body (size: {size})")] - TooSmall { size: usize }, -} - -type Result = std::result::Result; - -pub struct Varint; - -impl Varint { - pub const MIN_INT7: u8 = 0x00; // 0 - pub const MAX_INT7: u8 = 0x7F; // 127 - - const INT8_CODE: u8 = 0x80; - const INT16_CODE: u8 = 0x81; - const INT32_CODE: u8 = 0x82; - const INT64_CODE: u8 = 0x83; - - pub fn put_u8(value: u8, writer: &mut impl BufMut) { - if value <= Self::MAX_INT7 { - writer.put_u8(value); - } else { - writer.put_u8(Self::INT8_CODE); - writer.put_u8(value); - } - } - - pub fn put_u16(value: u16, writer: &mut impl BufMut) { - if value <= Self::MAX_INT7 as u16 { - writer.put_u8(value as u8); - } else if value <= u8::MAX as u16 { - writer.put_u8(Self::INT8_CODE); - writer.put_u8(value as u8); - } else { - writer.put_u8(Self::INT16_CODE); - writer.put_u16_le(value); - } - } - - pub fn put_u32(value: u32, writer: &mut impl BufMut) { - if value <= Self::MAX_INT7 as u32 { - writer.put_u8(value as u8); - } else if value <= u8::MAX as u32 { - writer.put_u8(Self::INT8_CODE); - writer.put_u8(value as u8); - } else if value <= u16::MAX as u32 { - writer.put_u8(Self::INT16_CODE); - writer.put_u16_le(value as u16); - } else { - writer.put_u8(Self::INT32_CODE); - writer.put_u32_le(value); - } - } - - pub fn put_u64(value: u64, writer: &mut impl BufMut) { - if value <= Self::MAX_INT7 as u64 { - writer.put_u8(value as u8); - } else if value <= u8::MAX as u64 { - writer.put_u8(Self::INT8_CODE); - writer.put_u8(value as u8); - } else if value <= u16::MAX as u64 { - writer.put_u8(Self::INT16_CODE); - writer.put_u16_le(value as u16); - } else if value <= u32::MAX as u64 { - writer.put_u8(Self::INT32_CODE); - writer.put_u32_le(value as u32); - } else { - writer.put_u8(Self::INT64_CODE); - writer.put_u64_le(value); - } - } - - pub fn put_i8(value: i8, writer: &mut impl BufMut) { - let value: u8 = i8::cast_unsigned(value); - let value = (value << 1) ^ (value >> 7); - Self::put_u8(value, writer); - } - - pub fn put_i16(value: i16, writer: &mut impl BufMut) { - let value: u16 = i16::cast_unsigned(value); - let value = (value << 1) ^ (value >> 15); - Self::put_u16(value, writer); - } - - pub fn put_i32(value: i32, writer: &mut impl BufMut) { - let value: u32 = i32::cast_unsigned(value); - let value = (value << 1) ^ (value >> 31); - Self::put_u32(value, writer); - } - - pub fn put_i64(value: i64, writer: &mut impl BufMut) { - let value: u64 = i64::cast_unsigned(value); - let value = (value << 1) ^ (value >> 63); - Self::put_u64(value, writer); - } - - pub fn get_u8(reader: &mut impl Buf) -> Result { - let remaining = reader.remaining(); - if remaining == 0 { - return Err(VarintError::EndOfInput); - } - - let head = reader.get_u8(); - - if (head & 0x80) == 0 { - Ok(head) - } else if head == Self::INT8_CODE { - if remaining < 2 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u8()) - } else { - Err(VarintError::InvalidHeader { value: head }) - } - } - - pub fn get_u16(reader: &mut impl Buf) -> Result { - let remaining = reader.remaining(); - if remaining == 0 { - return Err(VarintError::EndOfInput); - } - - let head = reader.get_u8(); - - if (head & 0x80) == 0 { - Ok(head as u16) - } else if head == Self::INT8_CODE { - if remaining < 2 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u8() as u16) - } else if head == Self::INT16_CODE { - if remaining < 3 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u16_le()) - } else { - Err(VarintError::InvalidHeader { value: head }) - } - } - - pub fn get_u32(reader: &mut impl Buf) -> Result { - let remaining = reader.remaining(); - if remaining == 0 { - return Err(VarintError::EndOfInput); - } - - let head = reader.get_u8(); - - if (head & 0x80) == 0 { - Ok(head as u32) - } else if head == Self::INT8_CODE { - if remaining < 2 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u8() as u32) - } else if head == Self::INT16_CODE { - if remaining < 3 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u16_le() as u32) - } else if head == Self::INT32_CODE { - if remaining < 5 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u32_le()) - } else { - Err(VarintError::InvalidHeader { value: head }) - } - } - - pub fn get_u64(reader: &mut impl Buf) -> Result { - let remaining = reader.remaining(); - if remaining == 0 { - return Err(VarintError::EndOfInput); - } - - let head = reader.get_u8(); - - if (head & 0x80) == 0 { - Ok(head as u64) - } else if head == Self::INT8_CODE { - if remaining < 2 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u8() as u64) - } else if head == Self::INT16_CODE { - if remaining < 3 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u16_le() as u64) - } else if head == Self::INT32_CODE { - if remaining < 5 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u32_le() as u64) - } else if head == Self::INT64_CODE { - if remaining < 9 { - return Err(VarintError::TooSmall { size: remaining }); - } - Ok(reader.get_u64_le()) - } else { - Err(VarintError::InvalidHeader { value: head }) - } - } - - pub fn get_i8(reader: &mut impl Buf) -> Result { - let value = Self::get_u8(reader)?; - let value = (value << 7) ^ (value >> 1); - let value: i8 = u8::cast_signed(value); - Ok(value) - } - - pub fn get_i16(reader: &mut impl Buf) -> Result { - let value = Self::get_u16(reader)?; - let value = (value << 15) ^ (value >> 1); - let value: i16 = u16::cast_signed(value); - Ok(value) - } - - pub fn get_i32(reader: &mut impl Buf) -> Result { - let value = Self::get_u32(reader)?; - let value = (value << 31) ^ (value >> 1); - let value: i32 = u32::cast_signed(value); - Ok(value) - } - - pub fn get_i64(reader: &mut impl Buf) -> Result { - let value = Self::get_u64(reader)?; - let value = (value << 63) ^ (value >> 1); - let value: i64 = u64::cast_signed(value); - Ok(value) - } -} - -#[cfg(test)] -mod tests { - use rand::{Rng, SeedableRng}; - use rand_chacha::ChaCha20Rng; - use testresult::TestResult; - use tokio_util::bytes::BytesMut; - - use super::*; - - const INT8_CODE: u8 = 0x80; - const INT16_CODE: u8 = 0x81; - const INT32_CODE: u8 = 0x82; - const INT64_CODE: u8 = 0x83; - - #[test] - fn empty_data_get_test() -> TestResult { - let buf = BytesMut::new(); - - // 8 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u8(&mut buf), Err(VarintError::EndOfInput)); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i8(&mut buf), Err(VarintError::EndOfInput)); - } - - // 16 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u16(&mut buf), Err(VarintError::EndOfInput)); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i16(&mut buf), Err(VarintError::EndOfInput)); - } - - // 32 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u32(&mut buf), Err(VarintError::EndOfInput)); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i32(&mut buf), Err(VarintError::EndOfInput)); - } - - // 64 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf), Err(VarintError::EndOfInput)); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf), Err(VarintError::EndOfInput)); - } - - Ok(()) - } - - #[test] - fn broken_header_data_get_test() -> TestResult { - // 8 - { - let mut buf = BytesMut::new(); - buf.put_u8(INT16_CODE); - - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u8(&mut buf), Err(VarintError::InvalidHeader { value: INT16_CODE })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i8(&mut buf), Err(VarintError::InvalidHeader { value: INT16_CODE })); - } - } - - // 16 - { - let mut buf = BytesMut::new(); - buf.put_u8(INT32_CODE); - - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u16(&mut buf), Err(VarintError::InvalidHeader { value: INT32_CODE })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i16(&mut buf), Err(VarintError::InvalidHeader { value: INT32_CODE })); - } - } - - // 32 - { - let mut buf = BytesMut::new(); - buf.put_u8(INT64_CODE); - - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u32(&mut buf), Err(VarintError::InvalidHeader { value: INT64_CODE })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i32(&mut buf), Err(VarintError::InvalidHeader { value: INT64_CODE })); - } - } - - // 64 - { - let mut buf = BytesMut::new(); - buf.put_u8(INT64_CODE + 1); - - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf), Err(VarintError::InvalidHeader { value: INT64_CODE + 1 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf), Err(VarintError::InvalidHeader { value: INT64_CODE + 1 })); - } - } - - Ok(()) - } - - #[test] - fn broken_body_data_get_test() -> TestResult { - // INT8_CODE - { - let mut buf = BytesMut::new(); - buf.put_u8(INT8_CODE); - - // 8 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u8(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i8(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - - // 16 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u16(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i16(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - - // 32 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u32(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i32(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - - // 64 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf), Err(VarintError::TooSmall { size: 1 })); - } - } - - // INT16_CODE - { - let mut buf = BytesMut::new(); - buf.put_u8(INT16_CODE); - buf.put_bytes(0x00, 1); - - // 16 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u16(&mut buf), Err(VarintError::TooSmall { size: 2 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i16(&mut buf), Err(VarintError::TooSmall { size: 2 })); - } - - // 32 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u32(&mut buf), Err(VarintError::TooSmall { size: 2 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i32(&mut buf), Err(VarintError::TooSmall { size: 2 })); - } - - // 64 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf), Err(VarintError::TooSmall { size: 2 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf), Err(VarintError::TooSmall { size: 2 })); - } - } - - // INT32_CODE - { - let mut buf = BytesMut::new(); - buf.put_u8(INT32_CODE); - buf.put_bytes(0x00, 3); - - // 32 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u32(&mut buf), Err(VarintError::TooSmall { size: 4 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i32(&mut buf), Err(VarintError::TooSmall { size: 4 })); - } - - // 64 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf), Err(VarintError::TooSmall { size: 4 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf), Err(VarintError::TooSmall { size: 4 })); - } - } - - // INT64_CODE - { - let mut buf = BytesMut::new(); - buf.put_u8(INT64_CODE); - buf.put_bytes(0x00, 7); - - // 64 - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf), Err(VarintError::TooSmall { size: 8 })); - } - { - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf), Err(VarintError::TooSmall { size: 8 })); - } - } - - Ok(()) - } - - #[test] - fn random_test() -> TestResult { - let mut rng = ChaCha20Rng::from_seed(Default::default()); - - // 8 - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_u8(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u8(&mut buf)?, v); - } - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_i8(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i8(&mut buf)?, v); - } - - // 16 - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_u16(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u16(&mut buf)?, v); - } - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_i16(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i16(&mut buf)?, v); - } - - // 32 - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_u32(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u32(&mut buf)?, v); - } - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_i32(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i32(&mut buf)?, v); - } - - // 64 - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_u64(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_u64(&mut buf)?, v); - } - for _ in 0..32 { - let v = rng.random(); - let mut buf = BytesMut::new(); - Varint::put_i64(v, &mut buf); - let mut buf = buf.clone().freeze(); - assert_eq!(Varint::get_i64(&mut buf)?, v); - } - - Ok(()) - } -} diff --git a/modules/rocketpack/src/rocket_message.rs b/modules/rocketpack/src/rocket_message.rs deleted file mode 100644 index 502e292..0000000 --- a/modules/rocketpack/src/rocket_message.rs +++ /dev/null @@ -1,68 +0,0 @@ -use tokio_util::bytes::{Bytes, BytesMut}; - -use crate::{RocketMessageReader, RocketMessageWriter, prelude::*}; - -pub trait RocketMessage { - fn pack(writer: &mut RocketMessageWriter, value: &Self, depth: u32) -> Result<()>; - - fn unpack(reader: &mut RocketMessageReader, depth: u32) -> Result - where - Self: Sized; - - fn import(bytes: &mut Bytes) -> Result - where - Self: Sized, - { - let mut reader = RocketMessageReader::new(bytes); - Self::unpack(&mut reader, 0) - } - - fn export(&self) -> Result { - let mut bytes = BytesMut::new(); - let mut writer = RocketMessageWriter::new(&mut bytes); - Self::pack(&mut writer, self, 0)?; - Ok(bytes.freeze()) - } -} - -#[cfg(test)] -mod tests { - use testresult::TestResult; - - use super::*; - - #[tokio::test] - async fn simple_test() -> TestResult { - let a = TestMessage { value: 12345 }; - - let bytes = a.export()?; - let mut bytes_mut = bytes.clone(); - let b = TestMessage::import(&mut bytes_mut)?; - - assert_eq!(a, b); - - Ok(()) - } - - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct TestMessage { - pub value: i32, - } - - impl RocketMessage for TestMessage { - fn pack(writer: &mut RocketMessageWriter, value: &Self, _depth: u32) -> Result<()> { - writer.put_i32(value.value); - - Ok(()) - } - - fn unpack(reader: &mut RocketMessageReader, _depth: u32) -> Result - where - Self: Sized, - { - let value = reader.get_i32()?; - - Ok(Self { value }) - } - } -} diff --git a/modules/rocketpack/src/rocket_message_reader.rs b/modules/rocketpack/src/rocket_message_reader.rs deleted file mode 100644 index 2ce9608..0000000 --- a/modules/rocketpack/src/rocket_message_reader.rs +++ /dev/null @@ -1,140 +0,0 @@ -use tokio_util::bytes::{Buf, Bytes}; - -use crate::{ - prelude::*, - primitive::{Timestamp64, Timestamp96, Varint}, -}; - -pub struct RocketMessageReader<'a> { - reader: &'a mut Bytes, -} - -impl<'a> RocketMessageReader<'a> { - pub fn new(reader: &'a mut Bytes) -> Self { - RocketMessageReader { reader } - } - - pub fn get_bytes(&mut self, limit: usize) -> Result> { - let length = self.get_u32()? as usize; - if length > limit { - return Err(Error::new(ErrorKind::TooLarge).with_message(format!("length exceeded limit: {length} > {limit}"))); - } - - if length == 0 { - return Ok(Vec::new()); - } - - if self.reader.remaining() < length { - return Err(Error::new(ErrorKind::EndOfStream)); - } - - let mut result = vec![0u8; length]; - self.reader.copy_to_slice(&mut result); - Ok(result) - } - - pub fn get_string(&mut self, limit: usize) -> Result { - let result = self.get_bytes(limit)?; - String::from_utf8(result).map_err(|_| Error::new(ErrorKind::InvalidFormat).with_message("invalid utf-8")) - } - - pub fn get_timestamp64(&mut self) -> Result { - let seconds = self.get_i64()?; - Ok(Timestamp64::new(seconds)) - } - - pub fn get_timestamp96(&mut self) -> Result { - let seconds = self.get_i64()?; - let nanos = self.get_u32()?; - Ok(Timestamp96::new(seconds, nanos)) - } - - pub fn get_bool(&mut self) -> Result { - let result = self.get_u64()?; - Ok(result != 0) - } - - pub fn get_u8(&mut self) -> Result { - Ok(Varint::get_u8(self.reader)?) - } - - pub fn get_u16(&mut self) -> Result { - Ok(Varint::get_u16(self.reader)?) - } - - pub fn get_u32(&mut self) -> Result { - Ok(Varint::get_u32(self.reader)?) - } - - pub fn get_u64(&mut self) -> Result { - Ok(Varint::get_u64(self.reader)?) - } - - pub fn get_i8(&mut self) -> Result { - Ok(Varint::get_i8(self.reader)?) - } - - pub fn get_i16(&mut self) -> Result { - Ok(Varint::get_i16(self.reader)?) - } - - pub fn get_i32(&mut self) -> Result { - Ok(Varint::get_i32(self.reader)?) - } - - pub fn get_i64(&mut self) -> Result { - Ok(Varint::get_i64(self.reader)?) - } - - pub fn get_f32(&mut self) -> Result { - const SIZE: usize = 4; - - if self.reader.remaining() < SIZE { - return Err(Error::new(ErrorKind::EndOfStream)); - } - - let mut buffer = [0u8; SIZE]; - self.reader.copy_to_slice(&mut buffer); - Ok(f32::from_le_bytes(buffer)) - } - - pub fn get_f64(&mut self) -> Result { - const SIZE: usize = 8; - - if self.reader.remaining() < SIZE { - return Err(Error::new(ErrorKind::EndOfStream)); - } - - let mut buffer = [0u8; SIZE]; - self.reader.copy_to_slice(&mut buffer); - Ok(f64::from_le_bytes(buffer)) - } -} - -#[cfg(test)] -mod tests { - - use testresult::TestResult; - - use super::*; - - #[test] - fn get_bytes_err_too_large_test() -> TestResult { - let mut bytes = Bytes::from(hex::decode("02")?); - let mut reader = RocketMessageReader::new(&mut bytes); - - assert!(reader.get_bytes(1).is_err_and(|x| *x.kind() == ErrorKind::TooLarge)); - - Ok(()) - } - - #[test] - fn get_bytes_err_end_of_input_test() -> TestResult { - let mut bytes = Bytes::from(hex::decode("01")?); - let mut reader = RocketMessageReader::new(&mut bytes); - - assert!(reader.get_bytes(1).is_err_and(|x| *x.kind() == ErrorKind::EndOfStream)); - - Ok(()) - } -} diff --git a/modules/rocketpack/src/rocket_message_writer.rs b/modules/rocketpack/src/rocket_message_writer.rs deleted file mode 100644 index 19a268e..0000000 --- a/modules/rocketpack/src/rocket_message_writer.rs +++ /dev/null @@ -1,78 +0,0 @@ -use tokio_util::bytes::{BufMut, BytesMut}; - -use crate::primitive::{Timestamp64, Timestamp96, Varint}; - -pub struct RocketMessageWriter<'a> { - writer: &'a mut BytesMut, -} - -impl<'a> RocketMessageWriter<'a> { - pub fn new(writer: &'a mut BytesMut) -> Self { - Self { writer } - } - - pub fn put_bytes(&mut self, value: &[u8]) { - Varint::put_u32(value.len() as u32, self.writer); - self.writer.put_slice(value); - } - - pub fn put_str(&mut self, value: &str) { - Varint::put_u32(value.len() as u32, self.writer); - self.writer.put_slice(value.as_bytes()); - } - - pub fn put_timestamp64(&mut self, value: Timestamp64) { - self.put_i64(value.seconds); - } - - pub fn put_timestamp96(&mut self, value: Timestamp96) { - self.put_i64(value.seconds); - self.put_u32(value.nanos); - } - - pub fn put_bool(&mut self, value: bool) { - self.writer.put_u8(if value { 0x01 } else { 0x00 }); - } - - pub fn put_u8(&mut self, value: u8) { - Varint::put_u64(value as u64, self.writer); - } - - pub fn put_u16(&mut self, value: u16) { - Varint::put_u64(value as u64, self.writer); - } - - pub fn put_u32(&mut self, value: u32) { - Varint::put_u64(value as u64, self.writer); - } - - pub fn put_u64(&mut self, value: u64) { - Varint::put_u64(value, self.writer); - } - - pub fn put_i8(&mut self, value: i8) { - Varint::put_i64(value as i64, self.writer); - } - - pub fn put_i16(&mut self, value: i16) { - Varint::put_i64(value as i64, self.writer); - } - - pub fn put_i32(&mut self, value: i32) { - Varint::put_i64(value as i64, self.writer); - } - - pub fn put_i64(&mut self, value: i64) { - Varint::put_i64(value, self.writer); - } - - pub fn put_f32(&mut self, value: f32) { - let bytes = value.to_le_bytes(); - self.writer.put_slice(&bytes); - } - - pub fn put_f64(&mut self, value: f64) { - let bytes = value.to_le_bytes(); - self.writer.put_slice(&bytes); - } -} diff --git a/modules/rocketpack/src/rocket_pack_codec_test.rs b/modules/rocketpack/src/rocket_pack_codec_test.rs new file mode 100644 index 0000000..2b10404 --- /dev/null +++ b/modules/rocketpack/src/rocket_pack_codec_test.rs @@ -0,0 +1,440 @@ +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use testresult::TestResult; + + use crate::{FieldType, RocketPackBytesDecoder, RocketPackBytesEncoder, RocketPackDecoder, RocketPackDecoderError, RocketPackEncoder}; + + fn compose(major: u8, info: u8) -> u8 { + (major << 5) | info + } + + macro_rules! encode_test { + ($name:ident, $value:expr, $bytes:expr) => {{ + let mut buf = Vec::new(); + let mut encoder = RocketPackBytesEncoder::new(&mut buf); + encoder.$name($value)?; + assert_eq!(buf.as_slice(), $bytes, "encode hex: {}", hex::encode($bytes)); + }}; + } + + macro_rules! decode_test { + ($name:ident, $bytes:expr, $value:expr) => {{ + let mut decoder = RocketPackBytesDecoder::new($bytes); + let decoded = decoder.$name()?; + assert_eq!(decoded, $value, "decode hex: {}", hex::encode($bytes)); + }}; + } + + #[test] + fn normal_bool_test() -> TestResult { + let cases: Vec<(Vec, bool)> = vec![ + (vec![compose(7, 20)], false), + (vec![compose(7, 21)], true), // + ]; + + for (bytes, value) in cases { + encode_test!(write_bool, value, &bytes); + decode_test!(read_bool, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_u8_test() -> TestResult { + let cases: Vec<(Vec, u8)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX), + ]; + + for (bytes, value) in cases { + encode_test!(write_u8, value, &bytes); + decode_test!(read_u8, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_u16_test() -> TestResult { + let cases: Vec<(Vec, u16)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX as u16), + (vec![compose(0, 25), 255, 255], u16::MAX), + ]; + + for (bytes, value) in cases { + encode_test!(write_u16, value, &bytes); + decode_test!(read_u16, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_u32_test() -> TestResult { + let cases: Vec<(Vec, u32)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX as u32), + (vec![compose(0, 25), 255, 255], u16::MAX as u32), + (vec![compose(0, 26), 255, 255, 255, 255], u32::MAX), + ]; + + for (bytes, value) in cases { + encode_test!(write_u32, value, &bytes); + decode_test!(read_u32, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_u64_test() -> TestResult { + let cases: Vec<(Vec, u64)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX as u64), + (vec![compose(0, 25), 255, 255], u16::MAX as u64), + (vec![compose(0, 26), 255, 255, 255, 255], u32::MAX as u64), + (vec![compose(0, 27), 255, 255, 255, 255, 255, 255, 255, 255], u64::MAX), + ]; + + for (bytes, value) in cases { + encode_test!(write_u64, value, &bytes); + decode_test!(read_u64, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_i8_test() -> TestResult { + let cases: Vec<(Vec, i8)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 127], i8::MAX), + (vec![compose(1, 0)], -1), + (vec![compose(1, 23)], -24), + (vec![compose(1, 24), 24], -25), + (vec![compose(1, 24), 127], i8::MIN), + ]; + + for (bytes, value) in cases { + encode_test!(write_i8, value, &bytes); + decode_test!(read_i8, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_i16_test() -> TestResult { + let cases: Vec<(Vec, i16)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX as i16), + (vec![compose(0, 25), 127, 255], i16::MAX), + (vec![compose(1, 0)], -1), + (vec![compose(1, 23)], -24), + (vec![compose(1, 24), 24], -25), + (vec![compose(1, 24), 255], -((u8::MAX as i16) + 1)), + (vec![compose(1, 25), 1, 0], -((u8::MAX as i16) + 2)), + (vec![compose(1, 25), 127, 255], i16::MIN), + ]; + + for (bytes, value) in cases { + encode_test!(write_i16, value, &bytes); + decode_test!(read_i16, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_i32_test() -> TestResult { + let cases: Vec<(Vec, i32)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX as i32), + (vec![compose(0, 25), 255, 255], u16::MAX as i32), + (vec![compose(0, 26), 127, 255, 255, 255], i32::MAX), + (vec![compose(1, 0)], -1), + (vec![compose(1, 23)], -24), + (vec![compose(1, 24), 24], -25), + (vec![compose(1, 24), 255], -((u8::MAX as i32) + 1)), + (vec![compose(1, 25), 1, 0], -((u8::MAX as i32) + 2)), + (vec![compose(1, 25), 255, 255], -((u16::MAX as i32) + 1)), + (vec![compose(1, 26), 0, 1, 0, 0], -((u16::MAX as i32) + 2)), + (vec![compose(1, 26), 127, 255, 255, 255], i32::MIN), + ]; + + for (bytes, value) in cases { + encode_test!(write_i32, value, &bytes); + decode_test!(read_i32, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_i64_test() -> TestResult { + let cases: Vec<(Vec, i64)> = vec![ + (vec![compose(0, 0)], 0), + (vec![compose(0, 23)], 23), + (vec![compose(0, 24), 24], 24), + (vec![compose(0, 24), 255], u8::MAX as i64), + (vec![compose(0, 25), 255, 255], u16::MAX as i64), + (vec![compose(0, 26), 255, 255, 255, 255], u32::MAX as i64), + (vec![compose(0, 27), 127, 255, 255, 255, 255, 255, 255, 255], i64::MAX), + (vec![compose(1, 0)], -1), + (vec![compose(1, 23)], -24), + (vec![compose(1, 24), 24], -25), + (vec![compose(1, 24), 255], -((u8::MAX as i64) + 1)), + (vec![compose(1, 25), 1, 0], -((u8::MAX as i64) + 2)), + (vec![compose(1, 25), 255, 255], -((u16::MAX as i64) + 1)), + (vec![compose(1, 26), 0, 1, 0, 0], -((u16::MAX as i64) + 2)), + (vec![compose(1, 26), 255, 255, 255, 255], -((u32::MAX as i64) + 1)), + (vec![compose(1, 27), 0, 0, 0, 1, 0, 0, 0, 0], -((u32::MAX as i64) + 2)), + (vec![compose(1, 27), 127, 255, 255, 255, 255, 255, 255, 255], i64::MIN), + ]; + + for (bytes, value) in cases { + encode_test!(write_i64, value, &bytes); + decode_test!(read_i64, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_f32_test() -> TestResult { + let cases: Vec<(Vec, f32)> = vec![ + (vec![compose(7, 26), 0, 0, 0, 0], 0.0), // + ]; + + for (bytes, value) in cases { + encode_test!(write_f32, value, &bytes); + decode_test!(read_f32, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_f64_test() -> TestResult { + let cases: Vec<(Vec, f64)> = vec![ + (vec![compose(7, 27), 0, 0, 0, 0, 0, 0, 0, 0], 0.0), // + ]; + + for (bytes, value) in cases { + encode_test!(write_f64, value, &bytes); + decode_test!(read_f64, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_bytes_test() -> TestResult { + let cases: Vec<(Vec, Vec)> = vec![ + (vec![compose(2, 0)], vec![]), + (vec![compose(2, 1), 0], vec![0]), + (([vec![compose(2, 23)], vec![0; 23]].concat()), vec![0; 23]), + (([vec![compose(2, 24), 24], vec![0; 24]].concat()), vec![0; 24]), + ]; + + for (bytes, value) in cases { + encode_test!(write_bytes, &value, &bytes); + decode_test!(read_bytes_vec, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_string_test() -> TestResult { + let cases: Vec<(Vec, String)> = vec![ + (vec![compose(3, 0)], "".to_string()), + (vec![compose(3, 6), 65, 65, 66, 66, 67, 67], "AABBCC".to_string()), // + ]; + + for (bytes, value) in cases { + encode_test!(write_string, &value, &bytes); + decode_test!(read_string, &bytes, value); + } + + Ok(()) + } + + #[test] + fn normal_array_test() -> TestResult { + let cases: Vec<(Vec, usize)> = vec![ + (vec![compose(4, 1)], 1), // + ]; + + for (bytes, value) in cases { + encode_test!(write_array, value, &bytes); + decode_test!(read_array, &bytes, value as u64); + } + + Ok(()) + } + + #[test] + fn normal_map_test() -> TestResult { + let cases: Vec<(Vec, usize)> = vec![ + (vec![compose(5, 1)], 1), // + ]; + + for (bytes, value) in cases { + encode_test!(write_map, value, &bytes); + decode_test!(read_map, &bytes, value as u64); + } + + Ok(()) + } + + #[test] + fn normal_raw_len_bytes_test() -> TestResult { + let cases: Vec<(Vec, u64, u8)> = vec![ + (vec![compose(0, 0)], 0, 0), + (vec![compose(0, 23)], 23, 23), + (vec![compose(0, 24), 24], 24, 24), + (vec![compose(0, 24), 255], u8::MAX as u64, 24), + (vec![compose(0, 25), 255, 255], u16::MAX as u64, 25), + (vec![compose(0, 26), 255, 255, 255, 255], u32::MAX as u64, 26), + (vec![compose(0, 27), 255, 255, 255, 255, 255, 255, 255, 255], u64::MAX, 27), + ]; + + for (bytes, value, info) in cases { + let mut buf = Vec::new(); + let mut encoder = RocketPackBytesEncoder::new(&mut buf); + encoder.write_raw_len(0, value as usize)?; + assert_eq!(buf.as_slice(), bytes, "encode hex: {}", hex::encode(&bytes)); + + let mut decoder = RocketPackBytesDecoder::new(&bytes[1..]); + let decoded = decoder.read_raw_len(info)?; + assert_eq!(decoded, Some(value), "decode hex: {}", hex::encode(&bytes)); + } + + Ok(()) + } + + #[test] + fn normal_decoder_type_of_test() -> TestResult { + let cases: Vec<(Vec, FieldType)> = vec![ + (vec![compose(0, 0)], FieldType::U8), + (vec![compose(0, 24)], FieldType::U8), + (vec![compose(0, 25)], FieldType::U16), + (vec![compose(0, 26)], FieldType::U32), + (vec![compose(0, 27)], FieldType::U64), + (vec![compose(1, 0)], FieldType::U8), + (vec![compose(1, 24), 0], FieldType::I8), + (vec![compose(1, 25), 0], FieldType::I16), + (vec![compose(1, 26), 0], FieldType::I32), + (vec![compose(1, 27), 0], FieldType::I64), + (vec![compose(1, 24), 0x80], FieldType::I16), + (vec![compose(1, 25), 0x80], FieldType::I32), + (vec![compose(1, 26), 0x80], FieldType::I64), + (vec![compose(2, 0)], FieldType::Bytes), + (vec![compose(3, 0)], FieldType::String), + (vec![compose(4, 0)], FieldType::Array), + (vec![compose(5, 0)], FieldType::Map), + (vec![compose(7, 20)], FieldType::Bool), + (vec![compose(7, 21)], FieldType::Bool), + (vec![compose(7, 25)], FieldType::F16), + (vec![compose(7, 26)], FieldType::F32), + (vec![compose(7, 27)], FieldType::F64), + (vec![compose(7, 31)], FieldType::Unknown { major: 7, info: 31 }), + ]; + + for (bytes, typ) in cases { + let decoder = RocketPackBytesDecoder::new(&bytes); + assert_eq!(decoder.current_type()?, typ, "decode hex: {}", hex::encode(&bytes)); + } + + Ok(()) + } + + #[test] + fn normal_decoder_skip_field_test() -> TestResult { + let p0 = true; + let p1 = 1; + let p2 = 2; + let p3 = 3; + let p4 = 4; + let p5 = 5; + let p6 = 6; + let p7 = 7; + let p8 = 8; + let p9 = 9.5; + let p10 = 10.5; + let p11 = vec![0xAA, 0xBB, 0xCC]; + let p12 = "test".to_string(); + let p13 = ["test_0".to_string(), "test_1".to_string()]; + let p14 = BTreeMap::from([ + (0_u32, "test_value_0".to_string()), + (1_u32, "test_value_1".to_string()), + (2_u32, "test_value_2".to_string()), + ]); + + let mut buf = Vec::new(); + let mut encoder = RocketPackBytesEncoder::new(&mut buf); + + encoder.write_bool(p0)?; + encoder.write_u8(p1)?; + encoder.write_u16(p2)?; + encoder.write_u32(p3)?; + encoder.write_u64(p4)?; + encoder.write_i8(p5)?; + encoder.write_i16(p6)?; + encoder.write_i32(p7)?; + encoder.write_i64(p8)?; + encoder.write_f32(p9)?; + encoder.write_f64(p10)?; + encoder.write_bytes(p11.as_slice())?; + encoder.write_string(p12.as_str())?; + encoder.write_array(p13.len())?; + for v in p13.iter() { + encoder.write_string(v.as_str())?; + } + encoder.write_map(p14.len())?; + for v in p14.iter() { + encoder.write_u32(*v.0)?; + encoder.write_string(v.1.as_str())?; + } + + let mut decoder = RocketPackBytesDecoder::new(&buf); + + for _ in 0..=14 { + decoder.skip_field()?; + } + + assert_eq!(decoder.remaining(), 0); + + Ok(()) + } + + #[test] + fn truncated_negative_number_reports_eof() -> TestResult { + let bytes = vec![compose(1, 24)]; + let decoder = RocketPackBytesDecoder::new(&bytes); + + match decoder.current_type() { + Err(RocketPackDecoderError::UnexpectedEof) => Ok(()), + other => panic!("expected UnexpectedEof, got {other:?}"), + } + } +} diff --git a/modules/rocketpack/src/rocket_pack_decoder.rs b/modules/rocketpack/src/rocket_pack_decoder.rs new file mode 100644 index 0000000..3d3d029 --- /dev/null +++ b/modules/rocketpack/src/rocket_pack_decoder.rs @@ -0,0 +1,608 @@ +use thiserror::Error; + +use crate::{FieldType, RocketPackStruct}; + +type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum RocketPackDecoderError { + #[error("unexpected end of buffer")] + UnexpectedEof, + #[error("mismatch field type at position (position: {position}, type: {field_type})")] + MismatchFieldType { position: usize, field_type: FieldType }, + #[error("length overflow (position: {position})")] + LengthOverflow { position: usize }, + #[error("string is not valid UTF-8 (position: {position}, error: {error})")] + Utf8 { position: usize, error: std::str::Utf8Error }, + #[error("other decode error: {0}")] + Other(&'static str), +} + +pub trait RocketPackDecoder { + fn remaining(&self) -> usize; + fn position(&self) -> usize; + fn current_type(&self) -> Result; + fn read_bool(&mut self) -> Result; + fn read_u8(&mut self) -> Result; + fn read_u16(&mut self) -> Result; + fn read_u32(&mut self) -> Result; + fn read_u64(&mut self) -> Result; + fn read_i8(&mut self) -> Result; + fn read_i16(&mut self) -> Result; + fn read_i32(&mut self) -> Result; + fn read_i64(&mut self) -> Result; + fn read_f32(&mut self) -> Result; + fn read_f64(&mut self) -> Result; + fn read_bytes(&mut self) -> Result<&[u8]>; + fn read_bytes_vec(&mut self) -> Result>; + fn read_string(&mut self) -> Result; + fn read_array(&mut self) -> Result; + fn read_map(&mut self) -> Result; + fn read_struct(&mut self) -> Result; + fn skip_field(&mut self) -> Result<()>; +} + +pub struct RocketPackBytesDecoder<'a> { + buf: &'a [u8], + pos: usize, +} + +impl<'a> RocketPackBytesDecoder<'a> { + pub fn new(buf: &'a [u8]) -> Self { + Self { buf, pos: 0 } + } +} + +impl<'a> RocketPackDecoder for RocketPackBytesDecoder<'a> { + fn remaining(&self) -> usize { + self.buf.len().saturating_sub(self.pos) + } + + fn position(&self) -> usize { + self.pos + } + + fn current_type(&self) -> Result { + let (major, info) = self.decompose(self.current_raw_byte()?); + self.type_of(major, info) + } + + fn read_bool(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + Ok(match (major, info) { + (7, 20) => false, + (7, 21) => true, + _ => { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + }) + } + + fn read_u8(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + Ok(match (major, info) { + (0, 0..=23) => info, + (0, 24) => u8::from_be_bytes(self.read_raw_fixed_bytes()?), + _ => { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + }) + } + + fn read_u16(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + Ok(match (major, info) { + (0, 0..=23) => info as u16, + (0, 24) => u8::from_be_bytes(self.read_raw_fixed_bytes()?) as u16, + (0, 25) => u16::from_be_bytes(self.read_raw_fixed_bytes()?), + _ => { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + }) + } + + fn read_u32(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + Ok(match (major, info) { + (0, 0..=23) => info as u32, + (0, 24) => u8::from_be_bytes(self.read_raw_fixed_bytes()?) as u32, + (0, 25) => u16::from_be_bytes(self.read_raw_fixed_bytes()?) as u32, + (0, 26) => u32::from_be_bytes(self.read_raw_fixed_bytes()?), + _ => { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + }) + } + + fn read_u64(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + Ok(match (major, info) { + (0, 0..=23) => info as u64, + (0, 24) => u8::from_be_bytes(self.read_raw_fixed_bytes()?) as u64, + (0, 25) => u16::from_be_bytes(self.read_raw_fixed_bytes()?) as u64, + (0, 26) => u32::from_be_bytes(self.read_raw_fixed_bytes()?) as u64, + (0, 27) => u64::from_be_bytes(self.read_raw_fixed_bytes()?), + _ => { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + }) + } + + fn read_i8(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + match (major, info) { + (0, 0..=23) => return Ok(info as i8), + (0, 24) => return Ok(u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i8), + (1, 0..=23) => return Ok(-1 - (info as i8)), + (1, 24..=28) => { + // Determine the smallest signed integer type the value fits in. + if (self.current_raw_byte()? & 0x80) != 0x80 { + #[allow(clippy::single_match)] + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i8)), + _ => {} + } + } + } + _ => {} + } + + Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }) + } + + fn read_i16(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + match (major, info) { + (0, 0..=23) => return Ok(info as i16), + (0, 24) => return Ok(u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i16), + (0, 25) => return Ok(u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i16), + (1, 0..=23) => return Ok(-1 - (info as i16)), + (1, 24..=28) => { + // Determine the smallest signed integer type the value fits in. + if (self.current_raw_byte()? & 0x80) != 0x80 { + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i16)), + 25 => return Ok(-1 - (u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i16)), + _ => {} + } + } else { + #[allow(clippy::single_match)] + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i16)), + _ => {} + } + } + } + _ => {} + } + + Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }) + } + + fn read_i32(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + match (major, info) { + (0, 0..=23) => return Ok(info as i32), + (0, 24) => return Ok(u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i32), + (0, 25) => return Ok(u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i32), + (0, 26) => return Ok(u32::from_be_bytes(self.read_raw_fixed_bytes()?) as i32), + (1, 0..=23) => return Ok(-1 - (info as i32)), + (1, 24..=28) => { + // Determine the smallest signed integer type the value fits in. + if (self.current_raw_byte()? & 0x80) != 0x80 { + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i32)), + 25 => return Ok(-1 - (u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i32)), + 26 => return Ok(-1 - (u32::from_be_bytes(self.read_raw_fixed_bytes()?) as i32)), + _ => {} + } + } else { + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i32)), + 25 => return Ok(-1 - (u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i32)), + _ => {} + } + } + } + _ => {} + } + + Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }) + } + + fn read_i64(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + match (major, info) { + (0, 0..=23) => return Ok(info as i64), + (0, 24) => return Ok(u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i64), + (0, 25) => return Ok(u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i64), + (0, 26) => return Ok(u32::from_be_bytes(self.read_raw_fixed_bytes()?) as i64), + (0, 27) => return Ok(u64::from_be_bytes(self.read_raw_fixed_bytes()?) as i64), + (1, 0..=23) => return Ok(-1 - (info as i64)), + (1, 24..=28) => { + // Determine the smallest signed integer type the value fits in. + if (self.current_raw_byte()? & 0x80) != 0x80 { + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + 25 => return Ok(-1 - (u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + 26 => return Ok(-1 - (u32::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + 27 => return Ok(-1 - (u64::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + _ => {} + } + } else { + match info { + 24 => return Ok(-1 - (u8::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + 25 => return Ok(-1 - (u16::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + 26 => return Ok(-1 - (u32::from_be_bytes(self.read_raw_fixed_bytes()?) as i64)), + _ => {} + } + } + } + _ => {} + } + + Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }) + } + + fn read_f32(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + if (major, info) == (7, 26) { + return Ok(f32::from_be_bytes(self.read_raw_fixed_bytes()?)); + } + + Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }) + } + + fn read_f64(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + if (major, info) == (7, 27) { + return Ok(f64::from_be_bytes(self.read_raw_fixed_bytes()?)); + } + + Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }) + } + + fn read_bytes(&mut self) -> Result<&'a [u8]> { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + if major != 2 { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + + let Some(len) = self.read_raw_len(info)? else { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + }; + let len: usize = len.try_into().map_err(|_| RocketPackDecoderError::LengthOverflow { position: p })?; + self.read_raw_bytes(len) + } + + fn read_bytes_vec(&mut self) -> Result> { + Ok(self.read_bytes()?.to_vec()) + } + + fn read_string(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + if major != 3 { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + + let Some(len) = self.read_raw_len(info)? else { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + }; + let len: usize = len.try_into().map_err(|_| RocketPackDecoderError::LengthOverflow { position: p })?; + let bytes = self.read_raw_bytes(len)?; + + std::str::from_utf8(bytes) + .map(|n| n.to_owned()) + .map_err(|e| RocketPackDecoderError::Utf8 { position: p, error: e }) + } + + fn read_array(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + if major != 4 { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + + let Some(len) = self.read_raw_len(info)? else { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + }; + + Ok(len) + } + + fn read_map(&mut self) -> Result { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + if major != 5 { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + } + + let Some(len) = self.read_raw_len(info)? else { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + }; + + Ok(len) + } + + fn read_struct(&mut self) -> Result { + T::unpack(self) + } + + fn skip_field(&mut self) -> Result<()> { + let mut remain: u64 = 1; + + while remain > 0 { + let p = self.pos; + let v = self.read_raw_byte()?; + let (major, info) = self.decompose(v); + + let len = match major { + 0 | 1 => match info { + 0..=23 => Some(0), + 24 => Some(1), + 25 => Some(2), + 26 => Some(4), + 27 => Some(8), + 28 => Some(16), + _ => None, + }, + 2 | 3 => self.read_raw_len(info)?, + 4 => match self.read_raw_len(info)? { + Some(count) => { + remain = remain.checked_add(count).ok_or(RocketPackDecoderError::LengthOverflow { position: p })?; + Some(0) + } + _ => None, + }, + 5 => match self.read_raw_len(info)? { + Some(count) => { + let count = count.checked_mul(2).ok_or(RocketPackDecoderError::LengthOverflow { position: p })?; + remain = remain.checked_add(count).ok_or(RocketPackDecoderError::LengthOverflow { position: p })?; + Some(0) + } + _ => None, + }, + 7 => match info { + 20 | 21 => Some(0), + 25 => Some(2), + 26 => Some(4), + 27 => Some(8), + _ => None, + }, + _ => None, + }; + + let Some(len) = len else { + return Err(RocketPackDecoderError::MismatchFieldType { + position: p, + field_type: self.type_of(major, info)?, + }); + }; + let len: usize = len.try_into().map_err(|_| RocketPackDecoderError::LengthOverflow { position: p })?; + self.skip_raw_bytes(len)?; + + remain -= 1; + } + + Ok(()) + } +} + +impl<'a> RocketPackBytesDecoder<'a> { + fn is_eof(&self) -> bool { + self.pos >= self.buf.len() + } + + #[inline] + fn decompose(&self, v: u8) -> (u8, u8) { + let major = v >> 5; // major type + let info = v & 0b0001_1111; // additional information + (major, info) + } + + // major: major type + // info: additional information + fn type_of(&self, major: u8, info: u8) -> Result { + match (major, info) { + (0, 0..=23) => return Ok(FieldType::U8), + (0, 24) => return Ok(FieldType::U8), + (0, 25) => return Ok(FieldType::U16), + (0, 26) => return Ok(FieldType::U32), + (0, 27) => return Ok(FieldType::U64), + (1, 0..=23) => return Ok(FieldType::U8), + (1, 24..=28) => { + // Determine the smallest signed integer type the value fits in. + if (self.peek_raw_byte()? & 0x80) != 0x80 { + match info { + 24 => return Ok(FieldType::I8), + 25 => return Ok(FieldType::I16), + 26 => return Ok(FieldType::I32), + 27 => return Ok(FieldType::I64), + _ => {} + } + } else { + match info { + 24 => return Ok(FieldType::I16), + 25 => return Ok(FieldType::I32), + 26 => return Ok(FieldType::I64), + _ => {} + } + } + } + (2, _) => return Ok(FieldType::Bytes), + (3, _) => return Ok(FieldType::String), + (4, _) => return Ok(FieldType::Array), + (5, _) => return Ok(FieldType::Map), + (7, 20..=21) => return Ok(FieldType::Bool), + (7, 25) => return Ok(FieldType::F16), + (7, 26) => return Ok(FieldType::F32), + (7, 27) => return Ok(FieldType::F64), + _ => {} + } + + Ok(FieldType::Unknown { major, info }) + } + + pub(crate) fn read_raw_len(&mut self, info: u8) -> Result> { + Ok(match info { + 0..=23 => Some(info as u64), + 24 => Some(u8::from_be_bytes(self.read_raw_fixed_bytes()?) as u64), + 25 => Some(u16::from_be_bytes(self.read_raw_fixed_bytes()?) as u64), + 26 => Some(u32::from_be_bytes(self.read_raw_fixed_bytes()?) as u64), + 27 => Some(u64::from_be_bytes(self.read_raw_fixed_bytes()?)), + _ => None, + }) + } + + fn current_raw_byte(&self) -> Result { + if self.is_eof() { + return Err(RocketPackDecoderError::UnexpectedEof); + } + Ok(self.buf[self.pos]) + } + + fn peek_raw_byte(&self) -> Result { + if self.remaining() < 2 { + return Err(RocketPackDecoderError::UnexpectedEof); + } + Ok(self.buf[self.pos + 1]) + } + + fn read_raw_byte(&mut self) -> Result { + if self.remaining() < 1 { + return Err(RocketPackDecoderError::UnexpectedEof); + } + let v = self.buf[self.pos]; + self.pos += 1; + Ok(v) + } + + fn read_raw_fixed_bytes(&mut self) -> Result<[u8; N]> { + if self.remaining() < N { + return Err(RocketPackDecoderError::UnexpectedEof); + } + let mut out = [0u8; N]; + let end = self.pos + N; + out.copy_from_slice(&self.buf[self.pos..end]); + self.pos = end; + Ok(out) + } + + fn read_raw_bytes(&mut self, len: usize) -> Result<&'a [u8]> { + if self.remaining() < len { + return Err(RocketPackDecoderError::UnexpectedEof); + } + let end = self.pos + len; + let slice = &self.buf[self.pos..end]; + self.pos = end; + Ok(slice) + } + + fn skip_raw_bytes(&mut self, len: usize) -> Result<()> { + if self.remaining() < len { + return Err(RocketPackDecoderError::UnexpectedEof); + } + self.pos += len; + Ok(()) + } +} diff --git a/modules/rocketpack/src/rocket_pack_encoder.rs b/modules/rocketpack/src/rocket_pack_encoder.rs new file mode 100644 index 0000000..b7f08a3 --- /dev/null +++ b/modules/rocketpack/src/rocket_pack_encoder.rs @@ -0,0 +1,242 @@ +use std::io::Write; + +use thiserror::Error; + +use crate::RocketPackStruct; + +type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum RocketPackEncoderError { + #[error("I/O error occurred")] + IoError(#[from] std::io::Error), + #[error("length overflow")] + LengthOverflow { len: usize }, +} + +pub trait RocketPackEncoder { + fn write_bool(&mut self, value: bool) -> Result<()>; + fn write_u8(&mut self, value: u8) -> Result<()>; + fn write_u16(&mut self, value: u16) -> Result<()>; + fn write_u32(&mut self, value: u32) -> Result<()>; + fn write_u64(&mut self, value: u64) -> Result<()>; + fn write_i8(&mut self, value: i8) -> Result<()>; + fn write_i16(&mut self, value: i16) -> Result<()>; + fn write_i32(&mut self, value: i32) -> Result<()>; + fn write_i64(&mut self, value: i64) -> Result<()>; + fn write_f32(&mut self, value: f32) -> Result<()>; + fn write_f64(&mut self, value: f64) -> Result<()>; + fn write_bytes(&mut self, value: &[u8]) -> Result<()>; + fn write_string(&mut self, value: &str) -> Result<()>; + fn write_array(&mut self, len: usize) -> Result<()>; + fn write_map(&mut self, len: usize) -> Result<()>; + fn write_struct(&mut self, value: &T) -> Result<()>; +} + +pub struct RocketPackBytesEncoder { + writer: W, +} + +impl RocketPackBytesEncoder { + pub fn new(writer: W) -> Self { + Self { writer } + } +} + +impl RocketPackEncoder for RocketPackBytesEncoder { + fn write_bool(&mut self, value: bool) -> Result<()> { + self.write_raw_bytes(&[self.compose(7, if !value { 20 } else { 21 })]) + } + + fn write_u8(&mut self, value: u8) -> Result<()> { + if value <= 23 { + self.write_raw_bytes(&[self.compose(0, value)])?; + } else { + self.write_raw_bytes(&[self.compose(0, 24), value])?; + } + Ok(()) + } + + fn write_u16(&mut self, value: u16) -> Result<()> { + if value <= 23 { + self.write_raw_bytes(&[self.compose(0, value as u8)])?; + } else if value <= u8::MAX as u16 { + self.write_raw_bytes(&[self.compose(0, 24), value as u8])?; + } else { + self.write_raw_bytes(&[self.compose(0, 25)])?; + self.write_raw_bytes(value.to_be_bytes().as_slice())?; + } + Ok(()) + } + + fn write_u32(&mut self, value: u32) -> Result<()> { + if value <= 23 { + self.write_raw_bytes(&[self.compose(0, value as u8)])?; + } else if value <= u8::MAX as u32 { + self.write_raw_bytes(&[self.compose(0, 24), value as u8])?; + } else if value <= u16::MAX as u32 { + self.write_raw_bytes(&[self.compose(0, 25)])?; + self.write_raw_bytes((value as u16).to_be_bytes().as_slice())?; + } else { + self.write_raw_bytes(&[self.compose(0, 26)])?; + self.write_raw_bytes(value.to_be_bytes().as_slice())?; + } + Ok(()) + } + + fn write_u64(&mut self, value: u64) -> Result<()> { + if value <= 23 { + self.write_raw_bytes(&[self.compose(0, value as u8)])?; + } else if value <= u8::MAX as u64 { + self.write_raw_bytes(&[self.compose(0, 24), value as u8])?; + } else if value <= u16::MAX as u64 { + self.write_raw_bytes(&[self.compose(0, 25)])?; + self.write_raw_bytes((value as u16).to_be_bytes().as_slice())?; + } else if value <= u32::MAX as u64 { + self.write_raw_bytes(&[self.compose(0, 26)])?; + self.write_raw_bytes((value as u32).to_be_bytes().as_slice())?; + } else { + self.write_raw_bytes(&[self.compose(0, 27)])?; + self.write_raw_bytes(value.to_be_bytes().as_slice())?; + } + Ok(()) + } + + fn write_i8(&mut self, value: i8) -> Result<()> { + if value >= 0 { + self.write_u8(value as u8)?; + } else { + let v = (-1 - value) as u8; + if v <= 23 { + self.write_raw_bytes(&[self.compose(1, v)])?; + } else { + self.write_raw_bytes(&[self.compose(1, 24), v])?; + } + } + Ok(()) + } + + fn write_i16(&mut self, value: i16) -> Result<()> { + if value >= 0 { + self.write_u16(value as u16)?; + } else { + let v = (-1 - value) as u16; + if v <= 23 { + self.write_raw_bytes(&[self.compose(1, v as u8)])?; + } else if v <= u8::MAX as u16 { + self.write_raw_bytes(&[self.compose(1, 24), v as u8])?; + } else { + self.write_raw_bytes(&[self.compose(1, 25)])?; + self.write_raw_bytes(v.to_be_bytes().as_slice())?; + } + } + Ok(()) + } + + fn write_i32(&mut self, value: i32) -> Result<()> { + if value >= 0 { + self.write_u32(value as u32)?; + } else { + let v = (-1 - value) as u32; + if v <= 23 { + self.write_raw_bytes(&[self.compose(1, v as u8)])?; + } else if v <= u8::MAX as u32 { + self.write_raw_bytes(&[self.compose(1, 24), v as u8])?; + } else if v <= u16::MAX as u32 { + self.write_raw_bytes(&[self.compose(1, 25)])?; + self.write_raw_bytes((v as u16).to_be_bytes().as_slice())?; + } else { + self.write_raw_bytes(&[self.compose(1, 26)])?; + self.write_raw_bytes(v.to_be_bytes().as_slice())?; + } + } + Ok(()) + } + + fn write_i64(&mut self, value: i64) -> Result<()> { + if value >= 0 { + self.write_u64(value as u64)?; + } else { + let v = (-1 - value) as u64; + if v <= 23 { + self.write_raw_bytes(&[self.compose(1, v as u8)])?; + } else if v <= u8::MAX as u64 { + self.write_raw_bytes(&[self.compose(1, 24), v as u8])?; + } else if v <= u16::MAX as u64 { + self.write_raw_bytes(&[self.compose(1, 25)])?; + self.write_raw_bytes((v as u16).to_be_bytes().as_slice())?; + } else if v <= u32::MAX as u64 { + self.write_raw_bytes(&[self.compose(1, 26)])?; + self.write_raw_bytes((v as u32).to_be_bytes().as_slice())?; + } else { + self.write_raw_bytes(&[self.compose(1, 27)])?; + self.write_raw_bytes(v.to_be_bytes().as_slice())?; + } + } + Ok(()) + } + + fn write_f32(&mut self, value: f32) -> Result<()> { + self.write_raw_bytes(&[self.compose(7, 26)])?; + self.write_raw_bytes(value.to_be_bytes().as_slice()) + } + + fn write_f64(&mut self, value: f64) -> Result<()> { + self.write_raw_bytes(&[self.compose(7, 27)])?; + self.write_raw_bytes(value.to_be_bytes().as_slice()) + } + + fn write_bytes(&mut self, value: &[u8]) -> Result<()> { + self.write_raw_len(2, value.len())?; + self.write_raw_bytes(value) + } + + fn write_string(&mut self, value: &str) -> Result<()> { + self.write_raw_len(3, value.len())?; + self.write_raw_bytes(value.as_bytes()) + } + + fn write_array(&mut self, len: usize) -> Result<()> { + self.write_raw_len(4, len) + } + + fn write_map(&mut self, len: usize) -> Result<()> { + self.write_raw_len(5, len) + } + + fn write_struct(&mut self, value: &T) -> Result<()> { + T::pack(self, value) + } +} + +impl RocketPackBytesEncoder { + #[inline] + fn compose(&self, major: u8, info: u8) -> u8 { + (major << 5) | (info & 0b0001_1111) + } + + pub(crate) fn write_raw_len(&mut self, major: u8, len: usize) -> Result<()> { + let len: u64 = len.try_into().map_err(|_| RocketPackEncoderError::LengthOverflow { len })?; + + if len <= 23 { + self.write_raw_bytes(&[self.compose(major, len as u8)])?; + } else if len <= u8::MAX as u64 { + self.write_raw_bytes(&[self.compose(major, 24)])?; + self.write_raw_bytes((len as u8).to_be_bytes().as_slice())?; + } else if len <= u16::MAX as u64 { + self.write_raw_bytes(&[self.compose(major, 25)])?; + self.write_raw_bytes((len as u16).to_be_bytes().as_slice())?; + } else if len <= u32::MAX as u64 { + self.write_raw_bytes(&[self.compose(major, 26)])?; + self.write_raw_bytes((len as u32).to_be_bytes().as_slice())?; + } else { + self.write_raw_bytes(&[self.compose(major, 27)])?; + self.write_raw_bytes(len.to_be_bytes().as_slice())?; + } + Ok(()) + } + + fn write_raw_bytes(&mut self, value: &[u8]) -> Result<()> { + self.writer.write_all(value).map_err(RocketPackEncoderError::IoError) + } +} diff --git a/modules/rocketpack/src/rocket_pack_struct.rs b/modules/rocketpack/src/rocket_pack_struct.rs new file mode 100644 index 0000000..8fc388c --- /dev/null +++ b/modules/rocketpack/src/rocket_pack_struct.rs @@ -0,0 +1,257 @@ +use crate::{RocketPackBytesDecoder, RocketPackBytesEncoder, RocketPackDecoder, RocketPackDecoderError, RocketPackEncoder, RocketPackEncoderError}; + +#[allow(clippy::len_without_is_empty)] +pub trait RocketPackStruct { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError>; + + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result + where + Self: Sized; + + fn import(bytes: &[u8]) -> std::result::Result + where + Self: Sized, + { + let mut decoder = RocketPackBytesDecoder::new(bytes); + Self::unpack(&mut decoder) + } + + fn export(&self) -> std::result::Result, RocketPackEncoderError> { + let mut bytes: Vec = Vec::new(); + let mut encoder = RocketPackBytesEncoder::new(&mut bytes); + Self::pack(&mut encoder, self)?; + Ok(bytes) + } +} + +#[cfg(test)] +mod tests { + use std::{collections::BTreeMap, rc::Rc}; + + use crate::{RocketPackDecoder, RocketPackDecoderError, RocketPackEncoder, RocketPackEncoderError, RocketPackStruct}; + use testresult::TestResult; + + #[test] + fn normal_test() -> TestResult { + let mut value = NormalTestStruct { + p0: true, + p1: 1, + p2: 2, + p3: 3, + p4: 4, + p5: 5, + p6: 6, + p7: 7, + p8: 8, + p9: 9.5, + p10: 10.5, + p11: vec![0xAA, 0xBB, 0xCC], + p12: "test".to_string(), + p13: vec!["test_0".to_string(), "test_1".to_string()], + p14: BTreeMap::from([ + (0_u32, "test_value_0".to_string()), + (1_u32, "test_value_1".to_string()), + (2_u32, "test_value_2".to_string()), + ]), + p15: None, + }; + value.p15 = Some(Rc::new(value.clone())); + + let exported = value.export()?; + let imported = NormalTestStruct::import(exported.as_slice())?; + assert_normal_struct(&imported, &value); + + Ok(()) + } + + fn assert_normal_struct(actual: &NormalTestStruct, expected: &NormalTestStruct) { + assert_eq!(actual.p0, expected.p0); + assert_eq!(actual.p1, expected.p1); + assert_eq!(actual.p2, expected.p2); + assert_eq!(actual.p3, expected.p3); + assert_eq!(actual.p4, expected.p4); + assert_eq!(actual.p5, expected.p5); + assert_eq!(actual.p6, expected.p6); + assert_eq!(actual.p7, expected.p7); + assert_eq!(actual.p8, expected.p8); + assert!((actual.p9 - expected.p9).abs() < f32::EPSILON); + assert!((actual.p10 - expected.p10).abs() < f64::EPSILON); + assert_eq!(actual.p11, expected.p11); + assert_eq!(actual.p12, expected.p12); + assert_eq!(actual.p13, expected.p13); + assert_eq!(actual.p14, expected.p14); + + match (&actual.p15, &expected.p15) { + (Some(a), Some(b)) => assert_normal_struct(a.as_ref(), b.as_ref()), + (None, None) => {} + _ => panic!("p15 mismatch"), + } + } + + #[derive(Debug, Clone)] + struct NormalTestStruct { + pub p0: bool, + pub p1: u8, + pub p2: u16, + pub p3: u32, + pub p4: u64, + pub p5: i8, + pub p6: i16, + pub p7: i32, + pub p8: i64, + pub p9: f32, + pub p10: f64, + pub p11: Vec, + pub p12: String, + pub p13: Vec, + pub p14: BTreeMap, + pub p15: Option>, + } + + impl RocketPackStruct for NormalTestStruct { + fn pack(encoder: &mut impl RocketPackEncoder, value: &Self) -> std::result::Result<(), RocketPackEncoderError> { + let mut count = 15; + if value.p15.is_some() { + count += 1; + } + + encoder.write_map(count)?; + + encoder.write_u64(0)?; + encoder.write_bool(value.p0)?; + + encoder.write_u64(1)?; + encoder.write_u8(value.p1)?; + + encoder.write_u64(2)?; + encoder.write_u16(value.p2)?; + + encoder.write_u64(3)?; + encoder.write_u32(value.p3)?; + + encoder.write_u64(4)?; + encoder.write_u64(value.p4)?; + + encoder.write_u64(5)?; + encoder.write_i8(value.p5)?; + + encoder.write_u64(6)?; + encoder.write_i16(value.p6)?; + + encoder.write_u64(7)?; + encoder.write_i32(value.p7)?; + + encoder.write_u64(8)?; + encoder.write_i64(value.p8)?; + + encoder.write_u64(9)?; + encoder.write_f32(value.p9)?; + + encoder.write_u64(10)?; + encoder.write_f64(value.p10)?; + + encoder.write_u64(11)?; + encoder.write_bytes(value.p11.as_slice())?; + + encoder.write_u64(12)?; + encoder.write_string(value.p12.as_str())?; + + encoder.write_u64(13)?; + encoder.write_array(value.p13.len())?; + for v in value.p13.iter() { + encoder.write_string(v.as_str())?; + } + + encoder.write_u64(14)?; + encoder.write_map(value.p14.len())?; + for v in value.p14.iter() { + encoder.write_u32(*v.0)?; + encoder.write_string(v.1.as_str())?; + } + + if let Some(p15) = &value.p15 { + encoder.write_u64(15)?; + encoder.write_struct(p15.as_ref())?; + } + + Ok(()) + } + + fn unpack(decoder: &mut impl RocketPackDecoder) -> std::result::Result + where + Self: Sized, + { + let count = decoder.read_map()?; + + let mut p0: bool = false; + let mut p1: u8 = 0; + let mut p2: u16 = 0; + let mut p3: u32 = 0; + let mut p4: u64 = 0; + let mut p5: i8 = 0; + let mut p6: i16 = 0; + let mut p7: i32 = 0; + let mut p8: i64 = 0; + let mut p9: f32 = 0.0; + let mut p10: f64 = 0.0; + let mut p11: Vec = vec![]; + let mut p12: String = "".to_string(); + let mut p13: Vec = vec![]; + let mut p14: BTreeMap = BTreeMap::new(); + let mut p15: Option> = None; + + for _ in 0..count { + match decoder.read_u64()? { + 0 => p0 = decoder.read_bool()?, + 1 => p1 = decoder.read_u8()?, + 2 => p2 = decoder.read_u16()?, + 3 => p3 = decoder.read_u32()?, + 4 => p4 = decoder.read_u64()?, + 5 => p5 = decoder.read_i8()?, + 6 => p6 = decoder.read_i16()?, + 7 => p7 = decoder.read_i32()?, + 8 => p8 = decoder.read_i64()?, + 9 => p9 = decoder.read_f32()?, + 10 => p10 = decoder.read_f64()?, + 11 => p11 = decoder.read_bytes_vec()?, + 12 => p12 = decoder.read_string()?, + 13 => { + let count = decoder.read_array()?; + for _ in 0..count { + p13.push(decoder.read_string()?); + } + } + 14 => { + let count = decoder.read_map()?; + for _ in 0..count { + let key = decoder.read_u32()?; + let value = decoder.read_string()?; + p14.insert(key, value); + } + } + 15 => p15 = Some(Rc::new(decoder.read_struct::()?)), + _ => decoder.skip_field()?, + } + } + + Ok(Self { + p0, + p1, + p2, + p3, + p4, + p5, + p6, + p7, + p8, + p9, + p10, + p11, + p12, + p13, + p14, + p15, + }) + } + } +}