diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0bea764..71064f1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,19 +2,33 @@ name: Build and Test on: pull_request: - branches: [ "main" ] + branches: ["main"] env: CARGO_TERM_COLOR: always jobs: build: - runs-on: ubuntu-latest - + strategy: + matrix: + rust: + - "1.75.0" + - "1.76.0" + - "1.77.0" + - "stable" # stable is 1.77.1 + - "beta" # beta + - "nightly" # nightly steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v3 + - name: Set up Rust ${{ matrix.rust }} + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + - name: Build ${{ matrix.rust }} + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose + - name: e2e tests + run: cargo run -p e2e_tests diff --git a/Cargo.lock b/Cargo.lock index dcce311..0fb19a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -115,9 +115,9 @@ checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "arc-swap" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "async-stream" @@ -160,9 +160,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "aws-lc-rs" @@ -193,9 +193,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -234,7 +234,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", "itertools", @@ -259,9 +259,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[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" @@ -308,9 +317,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -331,9 +340,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.3" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", ] @@ -387,12 +396,30 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc16" version = "0.4.0" @@ -420,6 +447,16 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[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 = "cuckoofilter" version = "0.5.0" @@ -456,6 +493,16 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -483,6 +530,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "e2e_tests" +version = "0.1.0" +dependencies = [ + "async-trait", + "futures", + "jetstream", + "tokio", +] + [[package]] name = "either" version = "1.10.0" @@ -600,6 +657,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "genfs" version = "0.1.4" @@ -737,9 +804,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jetstream" @@ -748,7 +815,6 @@ dependencies = [ "anyhow", "async-stream", "async-trait", - "bindgen", "bytes", "clap", "colored", @@ -761,6 +827,7 @@ dependencies = [ "lazy_static", "libc", "parking_lot", + "paste", "pkg-config", "s2n-quic", "serde", @@ -778,15 +845,19 @@ dependencies = [ "tuple-map", "which 6.0.1", "x509-certificate", + "zerocopy", ] [[package]] name = "jetstream_wire_format_derive" version = "1.0.0" dependencies = [ + "convert_case", + "paste", "pretty_assertions", "proc-macro2", "quote", + "sha256", "syn", ] @@ -848,7 +919,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall", ] @@ -883,9 +954,9 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -928,7 +999,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "libc", "memoffset", @@ -1086,9 +1157,9 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" dependencies = [ "proc-macro2", "syn", @@ -1205,9 +1276,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1228,9 +1299,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "ring" @@ -1276,11 +1347,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1469,9 +1540,9 @@ dependencies = [ [[package]] name = "s2n-tls" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d49e7ff0a2f7fa23816ba41e8cd6a04d3e06c28d6a0bd6e572702c7a8be9abd" +checksum = "4910054601de496adaeb5688d696f15f0526c3f07359f7ab09828b9d4c8bddc1" dependencies = [ "errno", "hex", @@ -1482,9 +1553,9 @@ dependencies = [ [[package]] name = "s2n-tls-sys" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7252540b5c58110aec78ebaeac0fe7268ab6f5b301e78bed9f8085b858513ef2" +checksum = "a0c1aed56ea7a119a218d167e6b8d46f883aad63d3dfba8641e56e8464a4fbfc" dependencies = [ "aws-lc-rs", "cc", @@ -1527,6 +1598,30 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1553,9 +1648,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" @@ -1636,9 +1731,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1795,9 +1890,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1915,12 +2010,24 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d5919d7121237af683b7fa982450597b1eaa2643e597aec3b519e4e5ab3d62" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "untrusted" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 4b00ea0..b815394 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,9 +48,10 @@ parking_lot = "0.12.1" crc16 = "0.4.0" slog-envlogger = "2.2.0" tokio-vsock = "0.5.0" +zerocopy = { version = "0.7.32", features = ["derive", "simd"] } +paste = "1.0.14" [build-dependencies] -bindgen = "0.69" pkg-config = "0.3" which = "6.0.1" @@ -58,6 +59,6 @@ which = "6.0.1" [workspace] -members = [ +members = [ "e2e_tests", "third_party/p9_wire_format_derive", ] diff --git a/README.md b/README.md index 5a0e3e8..1563bca 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ -# JetStream [![crates.io](https://img.shields.io/crates/v/jetstream.svg)](https://crates.io/crates/jetstream) [![docs.rs](https://docs.rs/jetstream/badge.svg)](https://docs.rs/jetstream) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/rust.yml/badge.svg) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/release.yml/badge.svg) +# JetStream +[![crates.io](https://img.shields.io/crates/v/jetstream.svg)](https://crates.io/crates/jetstream) [![docs.rs](https://docs.rs/jetstream/badge.svg)](https://docs.rs/jetstream) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/rust.yml/badge.svg) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/release.yml/badge.svg) [![crates.io downloads](https://img.shields.io/crates/d/jetstream.svg)](https://crates.io/crates/jetstream) JetStream is an RPC framework built on top of [s2n-quic](https://crates.io/crates/s2n-quic) and [p9](https://crates.io/crates/p9). It's designed to be a high performance, low latency, secure, and reliable RPC framework. @@ -28,7 +29,6 @@ JetStream is not ready for production use. It's still in the early stages of dev - [jsonrpc](https://www.jsonrpc.org/) - [tarpc](https://crates.io/crates/tarpc) - ## Example [test](src/server/server_tests.rs) ```rust @@ -158,4 +158,4 @@ tokio::select! { ## [License](LICENSE) -BSD-3-Clause \ No newline at end of file +BSD-3-Clause diff --git a/e2e_tests/Cargo.toml b/e2e_tests/Cargo.toml new file mode 100644 index 0000000..31fc0b0 --- /dev/null +++ b/e2e_tests/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "e2e_tests" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait = "0.1.79" +futures = "0.3.30" +jetstream = { version = "*", path = ".." } +tokio = { version = "1.37.0", features = ["full"] } diff --git a/e2e_tests/src/main.rs b/e2e_tests/src/main.rs new file mode 100644 index 0000000..c89c784 --- /dev/null +++ b/e2e_tests/src/main.rs @@ -0,0 +1,75 @@ +use radar::{ + Radar, RadarProtocol, Rmessage, Tframe, Tmessage, Tversion, Version, + PROTOCOL_VERSION, +}; +pub use tokio::io::{AsyncRead, AsyncWrite}; + +#[jetstream::service] +mod radar { + + #[derive(JetStreamWireFormat)] + pub struct Version { + pub msize: u32, + pub version: String, + } + #[async_trait::async_trait] + pub trait Radar { + async fn version(&mut self, req: Version) -> Version; + fn ping(&mut self) -> u8; + } +} +struct MyRadar; + +impl Radar for MyRadar { + fn ping(&mut self) -> u8 { + 0 + } + + #[must_use] + #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] + fn version<'life0, 'async_trait>( + &'life0 mut self, + req: Version, + ) -> ::core::pin::Pin< + Box< + dyn ::core::future::Future + + ::core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { req }) + } +} + +impl RadarProtocol for MyRadar {} + +fn main() { + let mut r = MyRadar; + futures::executor::block_on(async { + let ver = r + .rpc(Tframe { + tag: 0, + msg: Tmessage::Version(Tversion { + tag: 0, + req: Version { + msize: 0, + version: PROTOCOL_VERSION.to_string(), + }, + }), + }) + .await; + + match ver.msg { + Rmessage::Version(v) => { + println!("Version: {:?}", v.1.version); + } + _ => { + println!("Unexpected response"); + } + } + }); +} diff --git a/rust-toolchain b/rust-toolchain index af70436..9c3a667 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "1.75.0" +channel = "1.75" components = [ "rustfmt", "clippy", "llvm-tools-preview", "rust-src" ] diff --git a/src/lib.rs b/src/lib.rs index 92e61a8..60c6311 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ //! //! -//! # JetStream [![crates.io](https://img.shields.io/crates/v/jetstream.svg)](https://crates.io/crates/jetstream) [![docs.rs](https://docs.rs/jetstream/badge.svg)](https://docs.rs/jetstream) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/rust.yml/badge.svg) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/release.yml/badge.svg) +//! # JetStream +//! [![crates.io](https://img.shields.io/crates/v/jetstream.svg)](https://crates.io/crates/jetstream) [![docs.rs](https://docs.rs/jetstream/badge.svg)](https://docs.rs/jetstream) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/rust.yml/badge.svg) ![Build Status](https://github.com/sevki/jetstream/actions/workflows/release.yml/badge.svg) [![crates.io downloads](https://img.shields.io/crates/d/jetstream.svg)](https://crates.io/crates/jetstream) //! //! //! JetStream is an RPC framework built on top of [s2n-quic](https://crates.io/crates/s2n-quic) and [p9](https://crates.io/crates/p9). It's designed to be a high performance, low latency, secure, and reliable RPC framework. @@ -43,6 +44,7 @@ #[macro_use] extern crate jetstream_wire_format_derive; + #[cfg(feature = "async")] pub mod async_wire_format; #[cfg(feature = "client")] @@ -52,12 +54,19 @@ pub mod filesystem; pub mod server; pub mod service; pub mod protocol; + pub mod ufs; pub mod log; pub use jetstream_wire_format_derive::JetStreamWireFormat; +pub use jetstream_wire_format_derive::service; + +pub use protocol::{Data, WireFormat, messages}; + +pub use service::Message; + #[macro_export] macro_rules! syscall { ($e:expr) => {{ diff --git a/src/protocol/messages.rs b/src/protocol/messages.rs index 9116ee1..abdb5b4 100644 --- a/src/protocol/messages.rs +++ b/src/protocol/messages.rs @@ -315,213 +315,7 @@ impl Tframe { } } -#[derive(Debug, JetStreamWireFormat)] -pub struct Tversion { - pub msize: u32, - pub version: String, -} -#[derive(Debug, JetStreamWireFormat)] -pub struct Tflush { - pub oldtag: u16, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Twalk { - pub fid: u32, - pub newfid: u32, - pub wnames: Vec, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tread { - pub fid: u32, - pub offset: u64, - pub count: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Twrite { - pub fid: u32, - pub offset: u64, - pub data: Data, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tclunk { - pub fid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tremove { - pub fid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tauth { - pub afid: u32, - pub uname: String, - pub aname: String, - pub n_uname: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tattach { - pub fid: u32, - pub afid: u32, - pub uname: String, - pub aname: String, - pub n_uname: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tstatfs { - pub fid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tlopen { - pub fid: u32, - pub flags: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tlcreate { - pub fid: u32, - pub name: String, - pub flags: u32, - pub mode: u32, - pub gid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tsymlink { - pub fid: u32, - pub name: String, - pub symtgt: String, - pub gid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tmknod { - pub dfid: u32, - pub name: String, - pub mode: u32, - pub major: u32, - pub minor: u32, - pub gid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Trename { - pub fid: u32, - pub dfid: u32, - pub name: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Treadlink { - pub fid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tgetattr { - pub fid: u32, - pub request_mask: u64, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tsetattr { - pub fid: u32, - pub valid: u32, - pub mode: u32, - pub uid: u32, - pub gid: u32, - pub size: u64, - pub atime_sec: u64, - pub atime_nsec: u64, - pub mtime_sec: u64, - pub mtime_nsec: u64, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Txattrwalk { - pub fid: u32, - pub newfid: u32, - pub name: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Txattrcreate { - pub fid: u32, - pub name: String, - pub attr_size: u64, - pub flags: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Treaddir { - pub fid: u32, - pub offset: u64, - pub count: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tfsync { - pub fid: u32, - pub datasync: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tlock { - pub fid: u32, - pub type_: u8, - pub flags: u32, - pub start: u64, - pub length: u64, - pub proc_id: u32, - pub client_id: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tgetlock { - pub fid: u32, - pub type_: u8, - pub start: u64, - pub length: u64, - pub proc_id: u32, - pub client_id: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tlink { - pub dfid: u32, - pub fid: u32, - pub name: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tmkdir { - pub dfid: u32, - pub name: String, - pub mode: u32, - pub gid: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Trenameat { - pub olddirfid: u32, - pub oldname: String, - pub newdirfid: u32, - pub newname: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Tunlinkat { - pub dirfd: u32, - pub name: String, - pub flags: u32, -} /// A message sent from a 9P server to a 9P client in response to a request from /// that client. Encapsulates a full frame. @@ -728,6 +522,589 @@ impl WireFormat for Rframe { } } +/// version -- negotiate protocol version +/// +/// ```text +/// size[4] Tversion tag[2] msize[4] version[s] +/// size[4] Rversion tag[2] msize[4] version[s] +/// ``` +/// +/// version establishes the msize, which is the maximum message size inclusive of the size value that can be handled by both client and server. +/// +/// It also establishes the protocol version. For 9P2000.L version must be the string 9P2000.L. +/// +/// See the Plan 9 manual page for [version(5)](http://9p.io/magic/man2html/5/version). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tversion { + pub msize: u32, + pub version: String, +} + +/// flush -- abort a message +/// +/// ```text +/// size[4] Tflush tag[2] oldtag[2] +/// size[4] Rflush tag[2] +/// ``` +/// +/// flush aborts an in-flight request referenced by oldtag, if any. +/// +/// See the Plan 9 manual page for [flush(5)](http://9p.io/magic/man2html/5/flush). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tflush { + pub oldtag: u16, +} + +/// walk -- descend a directory hierarchy +/// +/// ```text +/// size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s]) +/// size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13]) +/// ``` +/// +/// walk is used to descend a directory represented by fid using successive path elements provided in the wname array. If successful, newfid represents the new path. +/// +/// fid can be cloned to newfid by calling walk with nwname set to zero. +/// +/// See the Plan 9 manual page for [walk(5)](http://9p.io/magic/man2html/5/walk). +#[derive(Debug, JetStreamWireFormat)] +pub struct Twalk { + pub fid: u32, + pub newfid: u32, + pub wnames: Vec, +} + +/// attach -- attach to a file tree +/// +/// ```text +/// size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s] +/// size[4] Rattach tag[2] qid[13] +/// ``` +/// +/// attach associates the fid with the file tree rooted at aname. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tattach { + pub fid: u32, + pub afid: u32, + pub uname: String, + pub aname: String, + pub n_uname: u32, +} + +/// auth -- authenticate a user +/// +/// ```text +/// size[4] Tauth tag[2] afid[4] uname[s] aname[s] +/// size[4] Rauth tag[2] aqid[13] +/// ``` +/// +/// auth authenticates the user named uname to access the file tree with the root named aname. +/// +/// afid is used as the fid in the attach message that follows auth. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tauth { + pub afid: u32, + pub uname: String, + pub aname: String, + pub n_uname: u32, +} + + +/// read -- read data from a file +/// +/// ```text +/// size[4] Tread tag[2] fid[4] offset[8] count[4] +/// size[4] Rread tag[2] count[4] data[count] +/// ``` +/// +/// read performs I/O on the file represented by fid. +/// +/// Under 9P2000.L, read cannot be used on directories. See [Treaddir](struct.Treaddir.html) for reading directories. +/// +/// See the Plan 9 manual page for [read(5)](http://9p.io/magic/man2html/5/read). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tread { + pub fid: u32, + pub offset: u64, + pub count: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rread { + pub data: Data, +} + +/// write -- write data to a file +/// +/// ```text +/// size[4] Twrite tag[2] fid[4] offset[8] data[count] +/// size[4] Rwrite tag[2] count[4] +/// ``` +/// +/// write performs I/O on the file represented by fid. +/// +/// See the Plan 9 manual page for [write(5)](http://9p.io/magic/man2html/5/write). +#[derive(Debug, JetStreamWireFormat)] +pub struct Twrite { + pub fid: u32, + pub offset: u64, + pub data: Data, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rwrite { + pub count: u32, +} + +/// clunk -- remove fid +/// +/// ```text +/// size[4] Tclunk tag[2] fid[4] +/// size[4] Rclunk tag[2] +/// ``` +/// +/// clunk removes the fid from the fid table. +/// +/// See the Plan 9 manual page for [clunk(5)](http://9p.io/magic/man2html/5/clunk). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tclunk { + pub fid: u32, +} + +/// remove -- remove a file +/// +/// ```text +/// size[4] Tremove tag[2] fid[4] +/// size[4] Rremove tag[2] +/// ``` +/// +/// remove removes the file represented by fid. +/// +/// See the Plan 9 manual page for [remove(5)](http://9p.io/magic/man2html/5/remove). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tremove { + pub fid: u32, +} + +/// statfs -- get file system information +/// +/// ```text +/// size[4] Tstatfs tag[2] fid[4] +/// size[4] Rstatfs tag[2] type[4] bsize[4] blocks[8] bfree[8] bavail[8] +/// files[8] ffree[8] fsid[8] namelen[4] +/// ``` +/// +/// statfs is used to request file system information of the file system containing fid. +/// The Rstatfs response corresponds to the fields returned by the statfs(2) system call. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tstatfs { + pub fid: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rstatfs { + pub ty: u32, + pub bsize: u32, + pub blocks: u64, + pub bfree: u64, + pub bavail: u64, + pub files: u64, + pub ffree: u64, + pub fsid: u64, + pub namelen: u32, +} + +/// lopen -- open a file +/// +/// ```text +/// size[4] Tlopen tag[2] fid[4] flags[4] +/// size[4] Rlopen tag[2] qid[13] iounit[4] +/// ``` +/// +/// lopen prepares fid for file I/O. The flags field has the standard open(2) values. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tlopen { + pub fid: u32, + pub flags: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rlopen { + pub qid: Qid, + pub iounit: u32, +} + +/// lcreate -- create a file +/// +/// ```text +/// size[4] Tlcreate tag[2] fid[4] name[s] flags[4] mode[4] gid[4] +/// size[4] Rlcreate tag[2] qid[13] iounit[4] +/// ``` +/// +/// lcreate creates a new file name in the directory represented by fid and prepares it for I/O. +/// The flags field has the standard open(2) values. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tlcreate { + pub fid: u32, + pub name: String, + pub flags: u32, + pub mode: u32, + pub gid: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rlcreate { + pub qid: Qid, + pub iounit: u32, +} + +/// symlink -- create symlink +/// +/// ```text +/// size[4] Tsymlink tag[2] fid[4] name[s] symtgt[s] gid[4] +/// size[4] Rsymlink tag[2] qid[13] +/// ``` +/// +/// symlink creates a new symbolic link name in the directory represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tsymlink { + pub fid: u32, + pub name: String, + pub symtgt: String, + pub gid: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rsymlink { + pub qid: Qid, +} + +/// mknod -- create a special file +/// +/// ```text +/// size[4] Tmknod tag[2] dfid[4] name[s] mode[4] major[4] minor[4] gid[4] +/// size[4] Rmknod tag[2] qid[13] +/// ``` +/// +/// mknod creates a new special file name in the directory represented by dfid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tmknod { + pub dfid: u32, + pub name: String, + pub mode: u32, + pub major: u32, + pub minor: u32, + pub gid: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rmknod { + pub qid: Qid, +} + +/// rename -- rename a file +/// +/// ```text +/// size[4] Trename tag[2] fid[4] dfid[4] name[s] +/// size[4] Rrename tag[2] +/// ``` +/// +/// rename renames a file or directory from old name to new name in the +/// directory represented by dfid. fid represents the file to be renamed. +#[derive(Debug, JetStreamWireFormat)] +pub struct Trename { + pub fid: u32, + pub dfid: u32, + pub name: String, +} + +/// readlink -- read symlink value +/// +/// ```text +/// size[4] Treadlink tag[2] fid[4] +/// size[4] Rreadlink tag[2] target[s] +/// ``` +/// +/// readlink reads the target of the symbolic link represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Treadlink { + pub fid: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rreadlink { + pub target: String, +} + +/// getattr -- get file attributes +/// +/// ```text +/// size[4] Tgetattr tag[2] fid[4] request_mask[8] +/// size[4] Rgetattr tag[2] valid[8] qid[13] mode[4] uid[4] gid[4] nlink[8] +/// rdev[8] size[8] blksize[8] blocks[8] atime_sec[8] atime_nsec[8] +/// mtime_sec[8] mtime_nsec[8] ctime_sec[8] ctime_nsec[8] btime_sec[8] +/// btime_nsec[8] gen[8] data_version[8] +/// ``` +/// +/// getattr gets attributes of the file system object represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tgetattr { + pub fid: u32, + pub request_mask: u64, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rgetattr { + pub valid: u64, + pub qid: Qid, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub nlink: u64, + pub rdev: u64, + pub size: u64, + pub blksize: u64, + pub blocks: u64, + pub atime_sec: u64, + pub atime_nsec: u64, + pub mtime_sec: u64, + pub mtime_nsec: u64, + pub ctime_sec: u64, + pub ctime_nsec: u64, + pub btime_sec: u64, + pub btime_nsec: u64, + pub gen: u64, + pub data_version: u64, +} + +/// setattr -- set file attributes +/// +/// ```text +/// size[4] Tsetattr tag[2] fid[4] valid[4] mode[4] uid[4] gid[4] size[8] +/// atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8] +/// size[4] Rsetattr tag[2] +/// ``` +/// +/// setattr sets attributes of the file system object represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tsetattr { + pub fid: u32, + pub valid: u32, + pub mode: u32, + pub uid: u32, + pub gid: u32, + pub size: u64, + pub atime_sec: u64, + pub atime_nsec: u64, + pub mtime_sec: u64, + pub mtime_nsec: u64, +} + +/// xattrwalk -- walk extended attributes +/// +/// ```text +/// size[4] Txattrwalk tag[2] fid[4] newfid[4] name[s] +/// size[4] Rxattrwalk tag[2] size[8] +/// ``` +/// +/// xattrwalk gets a new fid pointing to the extended attribute directory +/// of the file represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Txattrwalk { + pub fid: u32, + pub newfid: u32, + pub name: String, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rxattrwalk { + pub size: u64, +} + +/// xattrcreate -- create an extended attribute +/// +/// ```text +/// size[4] Txattrcreate tag[2] fid[4] name[s] attr_size[8] flags[4] +/// size[4] Rxattrcreate tag[2] +/// ``` +/// +/// xattrcreate creates a new extended attribute named name of the file represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Txattrcreate { + pub fid: u32, + pub name: String, + pub attr_size: u64, + pub flags: u32, +} + +/// readdir -- read directory entries +/// +/// ```text +/// size[4] Treaddir tag[2] fid[4] offset[8] count[4] +/// size[4] Rreaddir tag[2] count[4] data[count] +/// ``` +/// +/// readdir reads directory entries from the directory represented by fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Treaddir { + pub fid: u32, + pub offset: u64, + pub count: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rreaddir { + pub data: Data, +} + +/// fsync -- synchronize file +/// +/// ```text +/// size[4] Tfsync tag[2] fid[4] datasync[4] +/// size[4] Rfsync tag[2] +/// ``` +/// +/// fsync flushes any cached data and metadata for the file represented by fid to stable storage. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tfsync { + pub fid: u32, + pub datasync: u32, +} + +/// lock -- acquire or release a POSIX record lock +/// +/// ```text +/// size[4] Tlock tag[2] fid[4] type[1] flags[4] start[8] length[8] proc_id[4] client_id[s] +/// size[4] Rlock tag[2] status[1] +/// ``` +/// +/// lock acquires or releases a POSIX record lock on the open file fid. +/// +/// See the Plan 9 manual page for [lock(5)](http://9p.io/magic/man2html/5/lock). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tlock { + pub fid: u32, + pub type_: u8, + pub flags: u32, + pub start: u64, + pub length: u64, + pub proc_id: u32, + pub client_id: String, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rlock { + pub status: u8, +} + +/// getlock -- test for the existence of a POSIX record lock +/// +/// ```text +/// size[4] Tgetlock tag[2] fid[4] type[1] start[8] length[8] proc_id[4] client_id[s] +/// size[4] Rgetlock tag[2] type[1] start[8] length[8] proc_id[4] client_id[s] +/// ``` +/// +/// getlock tests for the existence of a POSIX record lock on the open file fid. +/// +/// See the Plan 9 manual page for [getlock(5)](http://9p.io/magic/man2html/5/getlock). +#[derive(Debug, JetStreamWireFormat)] +pub struct Tgetlock { + pub fid: u32, + pub type_: u8, + pub start: u64, + pub length: u64, + pub proc_id: u32, + pub client_id: String, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rgetlock { + pub type_: u8, + pub start: u64, + pub length: u64, + pub proc_id: u32, + pub client_id: String, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rlerror { + pub ecode: u32, +} + +// Rerror +#[derive(Debug, JetStreamWireFormat)] +pub struct Rerror { + pub ename: String, +} +/// link -- create hard link +/// +/// ```text +/// size[4] Tlink tag[2] dfid[4] fid[4] name[s] +/// size[4] Rlink tag[2] +/// ``` +/// +/// link creates a new hard link name in the directory dfid that refers to the same file as fid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tlink { + pub dfid: u32, + pub fid: u32, + pub name: String, +} + +/// mkdir -- create directory +/// +/// ```text +/// size[4] Tmkdir tag[2] dfid[4] name[s] mode[4] gid[4] +/// size[4] Rmkdir tag[2] qid[13] +/// ``` +/// +/// mkdir creates a new directory name in the directory represented by dfid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tmkdir { + pub dfid: u32, + pub name: String, + pub mode: u32, + pub gid: u32, +} + +#[derive(Debug, JetStreamWireFormat)] +pub struct Rmkdir { + pub qid: Qid, +} + +/// renameat -- rename a file or directory +/// +/// ```text +/// size[4] Trenameat tag[2] olddirfid[4] oldname[s] newdirfid[4] newname[s] +/// size[4] Rrenameat tag[2] +/// ``` +/// +/// renameat renames a file or directory from oldname in the directory represented by +/// olddirfid to newname in the directory represented by newdirfid. +#[derive(Debug, JetStreamWireFormat)] +pub struct Trenameat { + pub olddirfid: u32, + pub oldname: String, + pub newdirfid: u32, + pub newname: String, +} + +/// unlinkat -- unlink a file or directory +/// +/// ```text +/// size[4] Tunlinkat tag[2] dirfd[4] name[s] flags[4] +/// size[4] Runlinkat tag[2] +/// ``` +/// +/// unlinkat removes the file name from the directory represented by dirfd. +#[derive(Debug, JetStreamWireFormat)] +pub struct Tunlinkat { + pub dirfd: u32, + pub name: String, + pub flags: u32, +} + +/// Qid #[derive(Debug, Copy, Clone, JetStreamWireFormat)] pub struct Qid { pub ty: u8, @@ -735,6 +1112,7 @@ pub struct Qid { pub path: u64, } +/// Dirent -- directory entry #[derive(Debug, JetStreamWireFormat)] pub struct Dirent { pub qid: Qid, @@ -754,126 +1132,12 @@ pub struct Rwalk { pub wqids: Vec, } -#[derive(Debug, JetStreamWireFormat)] -pub struct Rread { - pub data: Data, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rwrite { - pub count: u32, -} - #[derive(Debug, JetStreamWireFormat)] pub struct Rauth { pub aqid: Qid, } -#[derive(Debug, JetStreamWireFormat)] +#[derive(Debug, JetStreamWireFormat)] pub struct Rattach { pub qid: Qid, } - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rlerror { - pub ecode: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rstatfs { - pub ty: u32, - pub bsize: u32, - pub blocks: u64, - pub bfree: u64, - pub bavail: u64, - pub files: u64, - pub ffree: u64, - pub fsid: u64, - pub namelen: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rlopen { - pub qid: Qid, - pub iounit: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rlcreate { - pub qid: Qid, - pub iounit: u32, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rsymlink { - pub qid: Qid, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rmknod { - pub qid: Qid, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rreadlink { - pub target: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rgetattr { - pub valid: u64, - pub qid: Qid, - pub mode: u32, - pub uid: u32, - pub gid: u32, - pub nlink: u64, - pub rdev: u64, - pub size: u64, - pub blksize: u64, - pub blocks: u64, - pub atime_sec: u64, - pub atime_nsec: u64, - pub mtime_sec: u64, - pub mtime_nsec: u64, - pub ctime_sec: u64, - pub ctime_nsec: u64, - pub btime_sec: u64, - pub btime_nsec: u64, - pub gen: u64, - pub data_version: u64, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rxattrwalk { - pub size: u64, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rreaddir { - pub data: Data, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rlock { - pub status: u8, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rgetlock { - pub type_: u8, - pub start: u64, - pub length: u64, - pub proc_id: u32, - pub client_id: String, -} - -#[derive(Debug, JetStreamWireFormat)] -pub struct Rmkdir { - pub qid: Qid, -} - -// Rerror -#[derive(Debug, JetStreamWireFormat)] -pub struct Rerror { - pub ename: String, -} diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs index d1e3b89..95c00b7 100644 --- a/src/protocol/mod.rs +++ b/src/protocol/mod.rs @@ -4,7 +4,6 @@ pub mod messages; pub mod wire_format; -// mod serde_9p; pub use self::messages::*; pub use self::wire_format::Data; diff --git a/src/protocol/wire_format.rs b/src/protocol/wire_format.rs index 6525fa7..9e83b2b 100644 --- a/src/protocol/wire_format.rs +++ b/src/protocol/wire_format.rs @@ -12,6 +12,9 @@ use std::ops::Deref; use std::ops::DerefMut; use std::string::String; use std::vec::Vec; +use zerocopy::{AsBytes, LittleEndian}; + +use crate::messages::Rlerror; /// A type that can be encoded on the wire using the 9P protocol. pub trait WireFormat: std::marker::Sized + Send { @@ -38,37 +41,42 @@ macro_rules! uint_wire_format_impl { } fn encode(&self, writer: &mut W) -> io::Result<()> { - let mut buf = [0u8; mem::size_of::<$Ty>()]; - - // Encode the bytes into the buffer in little endian order. - for idx in 0..mem::size_of::<$Ty>() { - buf[idx] = (self >> (8 * idx)) as u8; - } - - writer.write_all(&buf) + writer.write_all(&self.as_bytes()) } fn decode(reader: &mut R) -> io::Result { - let mut buf = [0u8; mem::size_of::<$Ty>()]; + let mut buf = [0; mem::size_of::<$Ty>()]; reader.read_exact(&mut buf)?; - - // Read bytes from the buffer in little endian order. - let mut result = 0; - for idx in 0..mem::size_of::<$Ty>() { - result |= (buf[idx] as $Ty) << (8 * idx); + paste::expr! { + let num: zerocopy::[<$Ty:snake:upper>] = zerocopy::byteorder::[<$Ty:snake:upper>]::from_bytes(buf); + Ok(num.get()) } - - Ok(result) } } }; } -uint_wire_format_impl!(u8); + uint_wire_format_impl!(u16); uint_wire_format_impl!(u32); uint_wire_format_impl!(u64); uint_wire_format_impl!(i32); +impl WireFormat for u8 { + fn byte_size(&self) -> u32 { + 1 + } + + fn encode(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&[*self]) + } + + fn decode(reader: &mut R) -> io::Result { + let mut byte = [0u8; 1]; + reader.read_exact(&mut byte)?; + Ok(byte[0]) + } +} + // The 9P protocol requires that strings are UTF-8 encoded. The wire format is a u16 // count |N|, encoded in little endian, followed by |N| bytes of UTF-8 data. impl WireFormat for String { @@ -138,7 +146,6 @@ impl WireFormat for Vec { #[derive(PartialEq, Eq, Clone)] pub struct Data(pub Vec); - // The maximum length of a data buffer that we support. In practice the server's max message // size should prevent us from reading too much data so this check is mainly to ensure a // malicious client cannot trick us into allocating massive amounts of memory. @@ -233,6 +240,84 @@ impl WireFormat for Data { } } +impl WireFormat for () { + fn byte_size(&self) -> u32 { + 0 + } + + fn encode(&self, _writer: &mut W) -> io::Result<()> { + Ok(()) + } + + fn decode(_reader: &mut R) -> io::Result { + Ok(()) + } +} + +fn error_to_rmessage(err: &io::Error) -> Rlerror { + let errno = if let Some(errno) = err.raw_os_error() { + errno + } else { + // Make a best-effort guess based on the kind. + match err.kind() { + io::ErrorKind::NotFound => libc::ENOENT, + io::ErrorKind::PermissionDenied => libc::EPERM, + io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED, + io::ErrorKind::ConnectionReset => libc::ECONNRESET, + io::ErrorKind::ConnectionAborted => libc::ECONNABORTED, + io::ErrorKind::NotConnected => libc::ENOTCONN, + io::ErrorKind::AddrInUse => libc::EADDRINUSE, + io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL, + io::ErrorKind::BrokenPipe => libc::EPIPE, + io::ErrorKind::AlreadyExists => libc::EEXIST, + io::ErrorKind::WouldBlock => libc::EWOULDBLOCK, + io::ErrorKind::InvalidInput => libc::EINVAL, + io::ErrorKind::InvalidData => libc::EINVAL, + io::ErrorKind::TimedOut => libc::ETIMEDOUT, + io::ErrorKind::WriteZero => libc::EIO, + io::ErrorKind::Interrupted => libc::EINTR, + io::ErrorKind::Other => libc::EIO, + io::ErrorKind::UnexpectedEof => libc::EIO, + _ => libc::EIO, + } + }; + + Rlerror { + ecode: errno as u32, + } +} + +impl WireFormat for io::Result { + fn byte_size(&self) -> u32 { + match self { + Ok(value) => value.byte_size(), + Err(err) => { + let error = error_to_rmessage(err); + error.byte_size() + } + } + } + + fn encode(&self, writer: &mut W) -> io::Result<()> { + match self { + Ok(value) => value.encode(writer), + Err(err) => { + let error = error_to_rmessage(err); + error.encode(writer) + } + } + } + + fn decode(reader: &mut R) -> io::Result { + let error = Rlerror::decode(reader)?; + if error.ecode == 0 { + Ok(Ok(T::decode(reader)?)) + } else { + Err(io::Error::from_raw_os_error(error.ecode as i32)) + } + } +} + #[cfg(test)] mod test { use std::io::Cursor; diff --git a/src/server/server_tests.rs b/src/server/server_tests.rs index 985a27c..c83e7a4 100644 --- a/src/server/server_tests.rs +++ b/src/server/server_tests.rs @@ -6,18 +6,44 @@ mod tests { server::{ proxy::{DialQuic, Proxy}, quic_server::QuicServer, - }, - service::ninepecho::{self, EchoService}, + }, service::JetStreamService, messages::Rmessage, }; use crate::protocol::{Rframe, Tframe, Tmessage, Tversion}; + use futures_util::Future; use s2n_quic::{provider::tls, Server}; use slog_scope::debug; use std::{ path::{self, Path}, - sync::Arc, + sync::Arc, pin::Pin, error::Error, }; use tokio::{io::AsyncWriteExt, sync::Barrier, net::UnixListener}; + #[derive(Debug, Clone)] + pub struct EchoService; + + impl JetStreamService for EchoService { + fn call( + &mut self, + _req: Tframe, + ) -> Pin< + Box< + dyn Future< + Output = Result>, + > + Send, + >, + > { + Box::pin(async move { + Ok(Rframe { + tag: 0, + msg: Rmessage::Version(crate::protocol::Rversion { + msize: 0, + version: "9P2000".to_string(), + }), + }) + }) + } + } + #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_quic_server_unix_socket_proxy() { let _guard = slog_scope::set_global_logger(setup_logging()); @@ -58,7 +84,7 @@ mod tests { .start() .unwrap(); let qsrv: QuicServer = - QuicServer::new(ninepecho::EchoService); + QuicServer::new(EchoService); debug!("Server started, waiting for barrier"); c.wait().await; let _ = qsrv.serve(server).await; diff --git a/src/service.rs b/src/service.rs index f4786ad..8053bb7 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,15 +1,11 @@ use std::{ error::Error, pin::Pin, - task::{Context, Poll}, }; -use crate::protocol::{Rframe, Rmessage, Tframe, WireFormat}; +use crate::protocol::{Rframe, Tframe, WireFormat}; use bytes::{BufMut, Bytes, BytesMut}; use futures::prelude::*; -use tower::Service; - -pub use jetstream_wire_format_derive::JetStreamWireFormat; /// Message trait for JetStream messages, which need to implement the `WireFormat` trait. pub trait Message: WireFormat + Send + Sync {} @@ -64,85 +60,6 @@ impl JetStreamService for NinePServiceImpl { } } -/// A static 9p service that always returns a version message. -#[derive(Debug, Clone, Copy)] -pub struct Radar; - -#[derive(Debug, Clone, JetStreamWireFormat)] -struct Ping(u8); - -impl Message for Ping {} - -#[derive(Debug, Clone, JetStreamWireFormat)] -struct Pong(u8); - -impl Message for Pong {} - -impl JetStreamService for Radar { - fn call( - &mut self, - req: Ping, - ) -> Pin< - Box< - dyn Future>> - + Send, - >, - > { - Box::pin(async move { Ok(Pong(req.0)) }) - } -} - -pub mod ninepecho { - use super::*; - - #[derive(Debug, Clone, Copy)] - pub struct EchoService; - - impl JetStreamService for EchoService { - fn call( - &mut self, - _req: Tframe, - ) -> Pin< - Box< - dyn Future< - Output = Result>, - > + Send, - >, - > { - Box::pin(async move { - Ok(Rframe { - tag: 0, - msg: Rmessage::Version(crate::protocol::Rversion { - msize: 0, - version: "9P2000".to_string(), - }), - }) - }) - } - } -} - -struct Echo; - -impl Service for Echo { - type Error = Box; - type Future = Pin< - Box> + Send>, - >; - type Response = bytes::Bytes; - - fn poll_ready( - &mut self, - _cx: &mut Context<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: bytes::Bytes) -> Self::Future { - Box::pin(async move { Ok(req) }) - } -} - /// A trait for converting types to and from a wire format. pub trait ConvertWireFormat: WireFormat { /// Converts the type to a byte representation. diff --git a/third_party/p9_wire_format_derive/Cargo.toml b/third_party/p9_wire_format_derive/Cargo.toml index 5f458e0..4727a7f 100644 --- a/third_party/p9_wire_format_derive/Cargo.toml +++ b/third_party/p9_wire_format_derive/Cargo.toml @@ -1,21 +1,26 @@ [package] name = "jetstream_wire_format_derive" version = "1.0.0" -authors = ["The ChromiumOS Authors"] +authors = ["Sevki ", "The ChromiumOS Authors"] license = "BSD-3-Clause" description = "Supporting proc-macro for the `p9` crate." repository = "https://github.com/sevki/jetstream" readme = "../../README.md" +edition = "2021" [dependencies] # rust analyzer doesn't understand the `quote` macro from `proc-macro2` so we # need to use `syn` to parse the input and then use `quote` to generate the -syn = "2" +syn = { version = "2.0.55", features = ["full", "extra-traits"] } quote = "^1" -proc-macro2 = "^1" +proc-macro2 = "1.0.79" +paste = "1.0.14" +convert_case = "0.6.0" +sha256 = "1.5.0" [lib] proc-macro = true [dev-dependencies] pretty_assertions = "1.4.0" +sha256 = "1.5.0" diff --git a/third_party/p9_wire_format_derive/src/lib.rs b/third_party/p9_wire_format_derive/src/lib.rs index ac79db9..60b8f2c 100644 --- a/third_party/p9_wire_format_derive/src/lib.rs +++ b/third_party/p9_wire_format_derive/src/lib.rs @@ -7,8 +7,6 @@ //! This is only intended to be used from within the `p9` crate. #![recursion_limit = "256"] - -extern crate proc_macro; extern crate proc_macro2; #[macro_use] @@ -17,13 +15,10 @@ extern crate quote; #[macro_use] extern crate syn; -use proc_macro2::Span; -use proc_macro2::TokenStream; -use syn::spanned::Spanned; -use syn::Data; -use syn::DeriveInput; -use syn::Fields; -use syn::Ident; +use proc_macro2::{TokenStream, Span}; +use syn::{DeriveInput, Fields, Ident, Data, spanned::Spanned}; + +mod service; /// The function that derives the actual implementation. #[proc_macro_derive(JetStreamWireFormat)] @@ -34,6 +29,14 @@ pub fn p9_wire_format( p9_wire_format_inner(input).into() } +#[proc_macro_attribute] +pub fn service( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + service::derive_jet_stream_protocol_impl(_attr, item) +} + fn p9_wire_format_inner(input: DeriveInput) -> TokenStream { if !input.generics.params.is_empty() { return quote! { @@ -56,8 +59,7 @@ fn p9_wire_format_inner(input: DeriveInput) -> TokenStream { use self::std::result::Result::Ok; use super::#container; - - use protocol::WireFormat; + use super::WireFormat; impl WireFormat for #container { fn byte_size(&self) -> u32 { @@ -103,10 +105,10 @@ fn byte_size_sum(data: &Data) -> TokenStream { 0 #(+ #fields)* } } else { - unimplemented!(); + unimplemented!("byte_size_sum for {:?}", data.struct_token.span); } } else { - unimplemented!(); + unimplemented!("byte_size_sum for "); } } @@ -166,7 +168,6 @@ fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream { #field: #field, } }); - quote! { #(#values)* @@ -176,7 +177,7 @@ fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream { }) } } else if let Fields::Unnamed(unnamed) = &data.fields { - let values = unnamed.unnamed.iter().enumerate().map(|(i, f)| { + let values = unnamed.unnamed.iter().enumerate().map(|(i, _f)| { let index = syn::Index::from(i); // create a new ident that s __{index} let ident = Ident::new( @@ -184,7 +185,7 @@ fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream { Span::call_site(), ); quote! { - let #ident: #f = WireFormat::decode(_reader)?; + let #ident = WireFormat::decode(_reader)?; } }); @@ -201,8 +202,9 @@ fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream { quote! { #(#values)* + Ok(#container( - #(#members)* + #(#members,)* )) } } else { @@ -315,7 +317,7 @@ mod tests { use super::Niijima_先輩; - use protocol::WireFormat; + use super::WireFormat; impl WireFormat for Niijima_先輩 { fn byte_size(&self) -> u32 { @@ -366,4 +368,70 @@ mod tests { expected.to_string(), ); } + + #[test] + fn end_to_end_unnamed() { + let input: DeriveInput = parse_quote! { + struct Niijima_先輩(u8, u16, u32, u64, String, Vec, Nested); + }; + + let expected = quote! { + mod wire_format_niijima_先輩 { + extern crate std; + use self::std::io; + use self::std::result::Result::Ok; + + use super::Niijima_先輩; + + use super::WireFormat; + + impl WireFormat for Niijima_先輩 { + fn byte_size(&self) -> u32 { + 0 + + WireFormat::byte_size(&self.0) + + WireFormat::byte_size(&self.1) + + WireFormat::byte_size(&self.2) + + WireFormat::byte_size(&self.3) + + WireFormat::byte_size(&self.4) + + WireFormat::byte_size(&self.5) + + WireFormat::byte_size(&self.6) + } + + fn encode(&self, _writer: &mut W) -> io::Result<()> { + WireFormat::encode(&self.0, _writer)?; + WireFormat::encode(&self.1, _writer)?; + WireFormat::encode(&self.2, _writer)?; + WireFormat::encode(&self.3, _writer)?; + WireFormat::encode(&self.4, _writer)?; + WireFormat::encode(&self.5, _writer)?; + WireFormat::encode(&self.6, _writer)?; + Ok(()) + } + fn decode(_reader: &mut R) -> io::Result { + let __0 = WireFormat::decode(_reader)?; + let __1= WireFormat::decode(_reader)?; + let __2 = WireFormat::decode(_reader)?; + let __3 = WireFormat::decode(_reader)?; + let __4 = WireFormat::decode(_reader)?; + let __5 = WireFormat::decode(_reader)?; + let __6 = WireFormat::decode(_reader)?; + Ok(Niijima_先輩( + __0, + __1, + __2, + __3, + __4, + __5, + __6, + )) + } + } + } + }; + + assert_eq!( + p9_wire_format_inner(input).to_string(), + expected.to_string(), + ); + } } diff --git a/third_party/p9_wire_format_derive/src/service.rs b/third_party/p9_wire_format_derive/src/service.rs new file mode 100644 index 0000000..7ed18b1 --- /dev/null +++ b/third_party/p9_wire_format_derive/src/service.rs @@ -0,0 +1,503 @@ +extern crate proc_macro; + +use std::iter; +use sha256::digest; +use self::proc_macro::TokenStream; +use quote::quote; +use syn::{ + Ident, Item, ItemMod, + ItemTrait, TraitItem, parse_str, +}; + +fn generate_msg_id( + index: usize, + method_name: &Ident, +) -> proc_macro2::TokenStream { + let upper_cased_method_name = method_name.to_string().to_uppercase(); + let tmsg_const_name = Ident::new( + &format!("T{}", upper_cased_method_name), + method_name.span(), + ); + let rmsg_const_name = Ident::new( + &format!("R{}", upper_cased_method_name), + method_name.span(), + ); + let offset = 2 * index as u8; + + quote! { + pub const #tmsg_const_name: u8 = MESSAGE_ID_START + #offset; + pub const #rmsg_const_name: u8 = MESSAGE_ID_START + #offset + 1; + } +} + +fn generate_input_struct( + request_struct_ident: &Ident, + method_sig: &syn::Signature, +) -> proc_macro2::TokenStream { + let inputs = method_sig.inputs.iter().map(|arg| match arg { + syn::FnArg::Typed(pat) => { + let name = pat.pat.clone(); + let ty = pat.ty.clone(); + quote! { + pub #name: #ty + } + } + syn::FnArg::Receiver(_) => quote! {}, + }); + + quote! { + #[derive(JetStreamWireFormat)] + pub struct #request_struct_ident { + pub tag: u16, + #(#inputs)* + } + } +} + +fn generate_return_struct( + return_struct_ident: &Ident, + method_sig: &syn::Signature, +) -> proc_macro2::TokenStream { + match &method_sig.output { + syn::ReturnType::Type(_, ty) => quote! { + #[derive(JetStreamWireFormat)] + pub struct #return_struct_ident (pub u16, pub #ty ); + }, + syn::ReturnType::Default => quote! { + #[derive(JetStreamWireFormat)] + pub struct #return_struct_ident(pub u16); + }, + } +} + +struct IdentCased(Ident); + +impl From<&Ident> for IdentCased { + fn from(ident: &Ident) -> Self { + IdentCased(ident.clone()) + } +} + +impl IdentCased { + fn remove_prefix(&self) -> Self { + let s = self.0.to_string(); + IdentCased(Ident::new(&s[1..], self.0.span())) + } + fn to_title_case(&self) -> Self { + let converter = + convert_case::Converter::new().to_case(convert_case::Case::Title); + let converted = converter.convert(self.0.to_string()); + IdentCased(Ident::new(&converted, self.0.span())) + } + fn to_upper_case(&self) -> Self { + let converter = + convert_case::Converter::new().to_case(convert_case::Case::Upper); + let converted = converter.convert(self.0.to_string()); + IdentCased(Ident::new(&converted, self.0.span())) + } +} + +impl From for Ident { + fn from(ident: IdentCased) -> Self { + ident.0 + } +} + +enum Direction{Rx, Tx} + + +fn generate_frame( + direction: Direction, + msgs: Vec<(Ident, proc_macro2::TokenStream)>, +) -> proc_macro2::TokenStream { + let enum_name = match direction { + Direction::Rx => quote! { Rmessage }, + Direction::Tx => quote! { Tmessage }, + }; + let frame_name = match direction { + Direction::Rx => quote! { Rframe }, + Direction::Tx => quote! { Tframe }, + }; + + let rmsg_variants = msgs.iter().map(|(ident, _)| { + let name: IdentCased = ident.into(); + let variant_name: Ident = name.remove_prefix().to_title_case().into(); + quote! { + #variant_name(#ident), + } + }); + let cloned_byte_sizes = msgs.iter().map(|(ident, _)| { + let name: IdentCased = ident.into(); + let variant_name: Ident = name.remove_prefix().to_title_case().into(); + quote! { + #enum_name::#variant_name(msg) => msg.byte_size() + } + }); + + let match_arms = msgs.iter().map(|(ident, _)| { + let name: IdentCased = ident.into(); + let variant_name: Ident = name.remove_prefix().to_title_case().into(); + quote! { + #enum_name::#variant_name(msg) + } + }); + + let encode_bodies = msgs.iter().map(|(ident, _)| { + let name: IdentCased = ident.into(); + let variant_name: Ident = name.to_upper_case().into(); + let new_ident = Ident::new(&format!("{}", variant_name), ident.span()); + quote!{ + #new_ident, + } + }); + + let decode_bodies = msgs.iter().map(|(ident, _)| { + let name: IdentCased = ident.into(); + let variant_name: Ident = name.remove_prefix().to_title_case().into(); + let const_name: Ident = name.to_upper_case().into(); + quote!{ + #const_name => Ok(#enum_name::#variant_name(WireFormat::decode(reader)?)), + } + }); + + let type_match_arms = std::iter::zip(match_arms.clone(), encode_bodies.clone()).map(|(arm, body)| { + quote! { + #arm => #body + } + }); + + let encode_match_arms = match_arms.clone().map(|arm| { + quote! { + #arm => msg.encode(writer)?, + } + }); + + + quote! { + pub enum #enum_name { + #( #rmsg_variants )* + } + + pub struct #frame_name { + pub tag: u16, + pub msg: #enum_name, + } + impl WireFormat for #frame_name { + fn byte_size(&self) -> u32 { + let msg = &self.msg; + let msg_size = match msg { + #( + #cloned_byte_sizes, + )* + }; + // size + type + tag + message size + (mem::size_of::() + mem::size_of::() + mem::size_of::()) + as u32 + + msg_size + } + + fn encode(&self, writer: &mut W) -> io::Result<()> { + self.byte_size().encode(writer)?; + + let ty = match &self.msg { + #( + #type_match_arms + )* + }; + + ty.encode(writer)?; + + self.tag.encode(writer)?; + + match &self.msg { + #( + #encode_match_arms + )* + } + + Ok(()) + } + + fn decode(reader: &mut R) -> io::Result { + let byte_size = u32::decode(reader)?; + + // byte_size includes the size of byte_size so remove that from the + // expected length of the message. Also make sure that byte_size is at least + // that long to begin with. + if byte_size < mem::size_of::() as u32 { + return Err(io::Error::new( + ErrorKind::InvalidData, + format!("byte_size(= {}) is less than 4 bytes", byte_size), + )); + } + + let reader = &mut reader.take(byte_size as u64 - mem::size_of::() as u64); + + let mut ty: u8 = WireFormat::decode(reader)?; + + let tag: u16 = WireFormat::decode(reader)?; + + let msg: #enum_name = Self::decode_message(reader, ty)?; + + Ok(#frame_name { tag, msg }) + } + } + impl #frame_name { + fn decode_message(reader: &mut R, ty: u8) -> io::Result<#enum_name> { + match ty { + #( + #decode_bodies + )* + _ => Err(io::Error::new( + ErrorKind::InvalidData, + format!("unknown message type: {}", ty), + )), + } + } + } + } +} + + +fn generate_tframe( + tmsgs: Vec<(Ident, proc_macro2::TokenStream)>, +) -> proc_macro2::TokenStream { + generate_frame(Direction::Tx, tmsgs) +} + +fn generate_rframe( + rmsgs: Vec<(Ident, proc_macro2::TokenStream)>, +) -> proc_macro2::TokenStream { + generate_frame(Direction::Rx, rmsgs) +} + +fn generate_match_arms( + tmsgs: impl Iterator, +) -> impl Iterator { + tmsgs.map(|(ident, _)| { + let name: IdentCased = (&ident).into(); + let variant_name: Ident = name.remove_prefix().to_title_case().into(); + quote! { + Tmessage::#variant_name(msg) + } + }) +} + +pub fn generate_jetstream_prococol( + item: &mut ItemTrait, + digest: String, +) -> proc_macro2::TokenStream { + let trait_name: &Ident = &item.ident; + let original_trait_name = trait_name.clone(); + let mut trait_ident: IdentCased = trait_name.into(); + trait_ident = trait_ident.to_title_case(); + let trait_name:Ident = trait_ident.into(); + // rename the trait to have a prefix + let trait_name = Ident::new( + &format!("{}Protocol", trait_name), + trait_name.span(), + ); + + let mut tmsgs = vec![]; + let mut rmsgs = vec![]; + let mut calls = vec![]; + let mut msg_ids = vec![]; + + { + let with_calls = item + .items + .iter() + .enumerate() + .map(|(index, item)| match item { + TraitItem::Fn(method) => { + let method_name = &method.sig.ident; + let request_struct_ident = Ident::new( + &format!("T{}", method_name), + method_name.span(), + ); + let return_struct_ident = Ident::new( + &format!("R{}", method_name), + method_name.span(), + ); + + let _output_type = match &method.sig.output { + syn::ReturnType::Type(_, ty) => quote! { #ty }, + syn::ReturnType::Default => quote! { () }, + }; + let msg_id = generate_msg_id(index, method_name); + msg_ids.push(msg_id); + let request_struct = + generate_input_struct(&request_struct_ident.clone(), &method.sig); + let return_struct = + generate_return_struct(&return_struct_ident.clone(), &method.sig); + + tmsgs.push((request_struct_ident.clone(),request_struct.clone())); + rmsgs.push((return_struct_ident.clone(),return_struct.clone())); + let has_req = method.sig.inputs.iter().count() > 1; + let is_async = method.sig.asyncness.is_some(); + let maybe_await = if is_async { quote! { .await } } else { quote! {} }; + if has_req { + quote! { + #[inline] + async fn #method_name(&mut self, tag: u16, req: #request_struct_ident) -> #return_struct_ident { + #return_struct_ident(tag, #original_trait_name::#method_name(self, req.req)#maybe_await) + } + } + } else { + quote! { + #[inline] + async fn #method_name(&mut self,tag: u16, req: #request_struct_ident)-> #return_struct_ident { + #return_struct_ident(tag, #original_trait_name::#method_name(self)#maybe_await) + } + } + } + } + _ => quote! { #item }, + }); + calls.extend(with_calls); + + let match_arms = generate_match_arms(tmsgs.clone().into_iter()); + let match_arm_bodies: Vec = item.items.clone().iter().map(|item| match item { + TraitItem::Fn(method) => { + let method_name = &method.sig.ident; + let name: IdentCased = method_name.into(); + let variant_name: Ident = name.to_title_case().into(); + + quote! { + { + let msg = #trait_name::#method_name(self,req.tag, msg).await; + Rframe{ + tag: req.tag, + msg: Rmessage::#variant_name(msg) + } + } + } + } + _ => quote! {}, + }).collect(); + let matches = std::iter::zip(match_arms, match_arm_bodies.iter()).map(|(arm, body)| { + quote! { + #arm => #body + } + }); + calls.extend(iter::once(quote! { + #[inline] + async fn rpc(&mut self, req:Tframe) -> Rframe { + match req.msg { + #( + #matches + )* + } + } + #[inline] + async fn handle(&mut self, reader: &mut R, writer: &mut W) -> io::Result<()> { + let req = Tframe::decode_async(reader).await?; + let res = self.rpc(req).await; + res.encode_async(writer).await?; + Ok(()) + } + })); + } + let tmsg_definitions = tmsgs.iter().map(|(_ident, def)| { + quote! { + #def + } + }); + + let rmsg_definitions = rmsgs.iter().map(|(_ident, def)| { + quote! { + #def + } + }); + + let tframe = generate_tframe(tmsgs.clone()); + let rframe = generate_rframe(rmsgs.clone()); + + #[allow(clippy::to_string_in_format_args)] + let protocol_version = format!("pub const PROTOCOL_VERSION: &str = \"dev.branch.jetstream.proto/{}/{}.{}.{}-{}\";", + original_trait_name.to_string().to_lowercase(), + env!("CARGO_PKG_VERSION_MAJOR"), + env!("CARGO_PKG_VERSION_MINOR"), + env!("CARGO_PKG_VERSION_PATCH"), + digest[0..8].to_string() + ); + + // make a const with the digest + let digest = format!("pub const DIGEST: &str = \"{}\";", digest); + let digest = parse_str::(digest.as_str()).unwrap(); + let protocol_version = parse_str::(protocol_version.as_str()).unwrap(); + quote!( + const MESSAGE_ID_START: u8 = 101; + + #digest + + #protocol_version + + #( + #msg_ids + )* + + #( + #tmsg_definitions + )* + + #( + #rmsg_definitions + )* + + #tframe + + #rframe + + #[async_trait::async_trait] + pub trait #trait_name: #original_trait_name + Send + Sync { + #( + #calls + )* + } + + ) +} + +pub fn derive_jet_stream_protocol_impl( + _attr: TokenStream, + item: TokenStream, +) -> TokenStream { + let mut item_mod: ItemMod = parse_macro_input!(item as ItemMod); + let module_name = item_mod.ident.clone(); + + // get all the original items in the module + let original_mod = item_mod.clone(); + let original_content = original_mod.content.as_ref().expect("module content"); + let original_items = &original_content.1; + let p =quote! { #original_mod }.to_string(); + let dig: String = digest(p); + + let transformed_items = item_mod + .content + .as_mut() + .expect("module content") + .1 + .iter_mut() + .map(| trait_item| match trait_item { + Item::Trait(trait_item) => generate_jetstream_prococol(trait_item, dig.clone()), + _ => quote! { }, + }); + // Construct the final output TokenStream + TokenStream::from(quote! { + mod #module_name { + pub use async_trait::async_trait; + use std::io; + pub use jetstream::{Message, WireFormat, JetStreamWireFormat, async_wire_format::AsyncWireFormatExt}; + pub use std::mem; + pub use std::io::{Read, Write, ErrorKind}; + pub use std::future::Future; + pub use std::pin::Pin; + pub use super::*; + + #(#transformed_items)* + + #(#original_items)* + } + }) +}