From 253ba655a7c9ecefdcedc3388249af08c617b85b Mon Sep 17 00:00:00 2001 From: Erik Chi Date: Fri, 29 Sep 2023 00:11:07 -0400 Subject: [PATCH] working simple echo WASM template with both listener + dialer --- .cargo/config | 6 + .github/workflows/rust.yml | 22 ++ .gitignore | 4 + Cargo.lock | 580 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 29 ++ src/common.rs | 163 +++++++++++ src/config.rs | 82 ++++++ src/dial.rs | 51 ++++ src/lib.rs | 55 ++++ src/listener.rs | 94 ++++++ src/net/mod.rs | 178 ++++++++++++ src/net/tls.rs | 371 ++++++++++++++++++++++++ 12 files changed, 1635 insertions(+) create mode 100644 .cargo/config create mode 100644 .github/workflows/rust.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/common.rs create mode 100644 src/config.rs create mode 100644 src/dial.rs create mode 100644 src/lib.rs create mode 100644 src/listener.rs create mode 100644 src/net/mod.rs create mode 100644 src/net/tls.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..9c37fa7 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,6 @@ +[build] +target = "wasm32-wasi" + +[target.wasm32-wasi] +runner = ["wasmtime", "run", "--tcplisten", "127.0.0.1:12345"] +rustflags = [ "--cfg", "tokio_unstable"] diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..92f4043 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: WASM + +on: + push: + branches: [ "wasm-template-v1.0" ] + pull_request: + branches: [ "wasm-template-v1.0" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ "ubuntu-latest", "windows-latest", "macos-latest" ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Build + run: cargo build --target wasm32-wasi --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe1f43e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*/target +/target +.DS_Store +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d8d8563 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,580 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.148" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "socket2" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "water-wasm-template" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "lazy_static", + "libc", + "serde", + "serde_json", + "tokio", + "tokio-util", + "toml", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b2be853 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "water-wasm-template" +version = "0.1.0" +authors = ["ecec "] +description = "A WASM template that can be integrated with W.A.T.E.R. library" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "water_wasm_template" +path = "src/lib.rs" +crate-type = ["cdylib", "lib"] + +[dependencies] +tokio = { version = "1.24.2", default-features = false, features = ["net", "rt", "macros", "io-util", "io-std", "time", "sync"] } +tokio-util = { version = "0.7.1", features = ["codec"] } + +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.107" +bincode = "1.3" + +anyhow = "1.0.7" +tracing = "0.1" +tracing-subscriber = "0.3.17" +toml = "0.5.9" +lazy_static = "1.4" +url = { version = "2.2.2", features = ["serde"] } +libc = "0.2.147" \ No newline at end of file diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..93fdcd7 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,163 @@ +use super::*; + +// =============== _required_ ================== +// #[export_name = "_version"] +#[no_mangle] +fn _version() -> i32 { + info!("[WASM] running in _version, returning: {}", API_VERSION); + API_VERSION +} + +// #[export_name = "_init"] +#[no_mangle] +fn _init() { + // FIXME: hardcoded debug for now + let debug = true; + if debug { + tracing_subscriber::fmt() + .with_max_level(Level::INFO) + .init(); + } + + info!("[WASM] running in _init"); +} + +#[no_mangle] +fn _process_config(fd: i32) { + info!("[WASM] running in _process_config"); + + let mut config_file = unsafe { std::fs::File::from_raw_fd(fd) }; + let mut config = String::new(); + match config_file.read_to_string(&mut config) { + Ok(_) => { + let config: Config = match serde_json::from_str(&config) { + Ok(config) => config, + Err(e) => { + eprintln!("[WASM] > _process_config ERROR: {}", e); + return; + } + }; + + let mut global_conn = CONN.lock().unwrap(); + global_conn.config = config; + + info!("[WASM] _process_config CONFIG: {:?}", global_conn); + }, + Err(e) => { + eprintln!("[WASM] > WASM _process_config falied reading path ERROR: {}", e); + return; + } + }; +} + +/// this _read function is triggered by the Host to read from the remote connection +#[no_mangle] +fn _read_from_net() -> i64 { + info!("[WASM] running in _read_from_net"); + + let mut global_conn = match CONN.lock() { + Ok(conf) => conf, + Err(e) => { + eprintln!("[WASM] > ERROR in read_from_net: {}", e); + return -1; + } + }; + let fd = global_conn.remote_reader; + + let mut stream = unsafe { std::net::TcpStream::from_raw_fd(fd) }; + // stream.set_nonblocking(true).unwrap(); + // let mut tokio_stream = tokio::net::TcpStream::from_std(stream).unwrap(); + + let mut buf = vec![0u8; 4096]; + + // let bytes_read = match tokio_stream.read(&mut buf).await { + let bytes_read = match stream.read(&mut buf) { + Ok(n) => n, + Err(e) => { + eprintln!("[WASM] > ERROR in read_from_net: {}", e); + return -1; + } + }; + + let mut unix_stream = unsafe { std::fs::File::from_raw_fd(global_conn.water_writer) }; + if let Err(e) = unix_stream.write_all(&buf[..bytes_read]) { + eprintln!("[WASM] > ERROR in read_from_net: {}", e); + return -1; + } + + global_conn.remote_reader = stream.into_raw_fd(); + global_conn.water_writer = unix_stream.into_raw_fd(); + + bytes_read as i64 +} + +#[no_mangle] +fn _write_2_net(bytes_write: i64) -> i64 { + info!("[WASM] running in _write_2_net"); + + let mut global_conn = match CONN.lock() { + Ok(conf) => conf, + Err(e) => { + eprintln!("[WASM] > ERROR in write_2_net: {}", e); + return -1; + } + }; + let fd = global_conn.remote_writer; + + let mut unix_stream = unsafe { std::fs::File::from_raw_fd(global_conn.water_reader) }; + let mut buf = vec![0u8; bytes_write as usize]; + + let mut bytes_read = 0; + loop { + let readed = match unix_stream.read(&mut buf) { + Ok(n) => n, + Err(e) => { + eprintln!("[WASM] > ERROR in write_2_net: {}", e); + return -1; + } + }; + + bytes_read += readed; + if bytes_read == bytes_write as usize { + break; + } + } + + let mut stream = unsafe { std::net::TcpStream::from_raw_fd(fd) }; + // stream.set_nonblocking(true).unwrap(); + // let mut tokio_stream = tokio::net::TcpStream::from_std(stream).unwrap(); + + // let bytes_written = match tokio_stream.write_all(&buf[..bytes_read]).await { + let bytes_written = match stream.write_all(&buf[..bytes_read]) { + Ok(_) => bytes_read, + Err(e) => { + eprintln!("[WASM] > ERROR in write_2_net: {}", e); + return -1; + } + }; + + global_conn.remote_writer = stream.into_raw_fd(); + global_conn.water_reader = unix_stream.into_raw_fd(); + + bytes_written as i64 +} + +#[no_mangle] +fn _close(fd: i32) { + +} + +#[no_mangle] +fn _water_bridging(reader_fd: i32, writer_fd: i32) { + let mut global_conn = match CONN.lock() { + Ok(conf) => conf, + Err(e) => { + eprintln!("[WASM] > ERROR: {}", e); + return; + } + }; + + global_conn.water_reader = reader_fd; + global_conn.water_writer = writer_fd; +} +// ================== end ====================== \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..27e8cb1 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,82 @@ +use super::*; + +// create a struct to hold the Conn +#[derive(Debug, Deserialize)] +// #[derive(Debug)] +pub struct Connections { + pub water_reader: i32, + pub water_writer: i32, + + pub remote_reader: i32, + pub remote_writer: i32, + + pub config: Config, + +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub local_address: String, + pub local_port: u32, + pub remote_address: String, + pub remote_port: u32, + + // key: String, + // cert: String, +} + +// implement a constructor for the config +impl Config { + pub fn new() -> Self { + Config { + local_address: String::from("127.0.0.1"), + local_port: 8080, + remote_address: String::from("example.com"), + remote_port: 8082, + + // key: String::from(""), + // cert: String::from(""), + } + } +} + +impl Connections { + pub fn new() -> Self { + Connections { + water_reader: 0, + water_writer: 0, + + remote_reader: 0, + remote_writer: 0, + + config: Config::new(), + } + } +} + +#[derive(Serialize, Deserialize)] +#[repr(C)] +pub struct StreamConfig { + pub addr: String, + pub port: u32, + pub name: String, +} + +impl StreamConfig { + pub fn init(addr: String, port: u32, name: String) -> Self { + StreamConfig { + addr: addr, + port: port, + name: name, + } + } + + pub fn to_bytes(&self) -> Vec { + let size = mem::size_of::(); + let ptr = self as *const Self; + + let bytes_slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) }; + let bytes = bytes_slice.to_vec(); + bytes + } +} \ No newline at end of file diff --git a/src/dial.rs b/src/dial.rs new file mode 100644 index 0000000..99913e6 --- /dev/null +++ b/src/dial.rs @@ -0,0 +1,51 @@ +use super::*; + +#[no_mangle] +fn dial() { + info!("[WASM] running in _entry func..."); + + let mut global_conn = match CONN.lock() { + Ok(conf) => conf, + Err(e) => { + eprintln!("[WASM] > ERROR: {}", e); + return; + // return Err(anyhow::anyhow!("failed to lock config")); + } + }; + + let mut fd: i32 = -1; + + // FIXME: hardcoded the filename for now, make it a config later + fd = _tcp_connect(global_conn.config.remote_address.clone(), global_conn.config.remote_port, "CONNECT_REMOTE".to_string()).unwrap(); + + if fd < 0 { + eprintln!("failed to create connection to remote"); + return; + // return Err(anyhow::anyhow!("failed to create connection to remote")); + } + + global_conn.remote_reader = fd; + global_conn.remote_writer = fd; + + // Ok(()) +} + +fn _tcp_connect(addr: String, port: u32, name: String) -> Result { + let stream = StreamConfig::init(addr.clone(), port, name.clone()); + + let encoded: Vec = bincode::serialize(&stream).expect("Failed to serialize"); + + let address = encoded.as_ptr() as u32; + let size = encoded.len() as u32; + + let mut fd = -1; + unsafe { + fd = connect_tcp(address, size); + }; + + if fd < 0 { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "failed to create listener")); + } + + Ok(fd) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..bb6f763 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,55 @@ +// lib.rs + +pub mod config; +pub mod common; +pub mod dial; +pub mod listener; +// pub mod net; + +pub use config::*; +pub use common::*; +pub use dial::*; +pub use listener::*; +// pub use net::*; + +// =================== Imports & Modules ===================== +use std::{ + io::{self, Read, Write}, + os::fd::IntoRawFd, + os::wasi::prelude::FromRawFd, + sync::Mutex, + vec, +}; + +use tokio::{ + io::{AsyncRead, AsyncWrite, AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream}, + time::timeout, +}; +use lazy_static::lazy_static; +use serde_json; +use tracing::{info, Level}; +use tracing_subscriber; +use bincode::{self}; + +use std::fs::File; +use std::mem; +use anyhow::{Context, Result}; +use serde::{Serialize, Deserialize, de::DeserializeOwned}; +use tokio_util::codec::{AnyDelimiterCodec, Framed, FramedParts}; +use std::time::Duration; + + +extern "C" { + // #[link_name = "create-listen"] + fn create_listen(ptr: u32, size: u32) -> i32; + fn connect_tcp(ptr: u32, size: u32) -> i32; +} + +// =================== Constants & Globals =================== +const API_VERSION: i32 = 0x001aaaaa; // 0.01.aaaa -> v0.1-alpha + +// create a mutable global variable stores a pointer to the config +lazy_static! { + static ref CONN: Mutex = Mutex::new(Connections::new()); +} diff --git a/src/listener.rs b/src/listener.rs new file mode 100644 index 0000000..49bf525 --- /dev/null +++ b/src/listener.rs @@ -0,0 +1,94 @@ +use super::*; + +// ----------------------- Listener methods ----------------------- +#[no_mangle] +fn listen() { + wrapper().unwrap(); +} + +fn _listener_creation() -> Result { + let global_conn = match CONN.lock() { + Ok(conf) => conf, + Err(e) => { + eprintln!("[WASM] > ERROR: {}", e); + return Err(std::io::Error::new(std::io::ErrorKind::Other, "failed to lock config")); + } + }; + + // FIXME: hardcoded the filename for now, make it a config later + let stream = StreamConfig::init(global_conn.config.local_address.clone(), global_conn.config.local_port, "LISTEN".to_string()); + + let encoded: Vec = bincode::serialize(&stream).expect("Failed to serialize"); + + let address = encoded.as_ptr() as u32; + let size = encoded.len() as u32; + + let mut fd = -1; + unsafe { + fd = create_listen(address, size); + }; + + if fd < 0 { + return Err(std::io::Error::new(std::io::ErrorKind::Other, "failed to create listener")); + } + + Ok(fd) +} + + +#[tokio::main(flavor = "current_thread")] +async fn wrapper() -> std::io::Result<()> { + let fd = _listener_creation().unwrap(); + + // Set up pre-established listening socket. + let standard = unsafe { std::net::TcpListener::from_raw_fd(fd) }; + // standard.set_nonblocking(true).unwrap(); + let listener = TcpListener::from_std(standard)?; + let mut test = true; + + loop { + // Accept new sockets in a loop. + let socket = match listener.accept().await { + Ok(s) => s.0, + Err(e) => { + eprintln!("[WASM] > ERROR: {}", e); + continue; + } + }; + + // Spawn a background task for each new connection. + tokio::spawn(async move { + eprintln!("[WASM] > CONNECTED"); + match handle(socket, test).await { + Ok(()) => eprintln!("[WASM] > DISCONNECTED"), + Err(e) => eprintln!("[WASM] > ERROR: {}", e), + } + }); + + test = false; + } +} + +async fn handle(mut socket: TcpStream, i: bool) -> std::io::Result<()> { + loop { + let mut buf = [0u8; 4096]; + + + // Read some bytes from the socket. + let read = socket.read(&mut buf).await?; + + if i { + // thread::sleep(std::time::Duration::from_secs(3)); + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + } + + // Handle a clean disconnection. + if read == 0 { + return Ok(()); + } + + // Write bytes both locally and remotely. + std::io::stdout().write_all(&buf[..read])?; + socket.write_all(&buf[..read]).await?; + } +} \ No newline at end of file diff --git a/src/net/mod.rs b/src/net/mod.rs new file mode 100644 index 0000000..ec59a90 --- /dev/null +++ b/src/net/mod.rs @@ -0,0 +1,178 @@ +use serde::{de::Error as _, Deserialize, Deserializer, Serialize}; +use std::{collections::HashMap, ops::Deref}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +/// Name assigned to a file descriptor +/// +/// This is used to export the `FD_NAMES` environment variable, +/// which is a concatenation of all file descriptors names seperated by `:`. +/// +/// See the [crate] documentation for examples. +pub struct FileName(String); + +impl TryFrom for FileName { + type Error = &'static str; + + fn try_from(name: String) -> Result { + if name.find(':').is_some() { + Err("file name must not contain ':'") + } else { + Ok(Self(name)) + } + } +} + +impl TryFrom<&str> for FileName { + type Error = >::Error; + + fn try_from(name: &str) -> Result { + String::from(name).try_into() + } +} + +impl Deref for FileName { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'de> Deserialize<'de> for FileName { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let name = String::deserialize(deserializer)?; + name.try_into().map_err(D::Error::custom) + } +} + +const fn default_tcp_port() -> u16 { + 80 +} + +const fn default_tls_port() -> u16 { + 443 +} + +fn default_addr() -> String { + "::".into() +} + + + +// /// Parameters for a pre-opened file descriptor +// #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +// #[serde(tag = "kind", deny_unknown_fields)] +// pub enum File { +// // /// File descriptor of `/dev/null` +// // #[serde(rename = "null")] +// // Null(NullFile), + +// // /// File descriptor of stdin +// // #[serde(rename = "stdin")] +// // Stdin(StdioFile), + +// // /// File descriptor of stdout +// // #[serde(rename = "stdout")] +// // Stdout(StdioFile), + +// // /// File descriptor of stderr +// // #[serde(rename = "stderr")] +// // Stderr(StdioFile), + +// /// File descriptor of a listen socket +// #[serde(rename = "listen")] +// Listen(ListenFile), + +// /// File descriptor of a stream socket +// #[serde(rename = "connect")] +// Connect(ConnectFile), +// } + +// impl File { +// /// Get the name for a file descriptor +// pub fn name(&self) -> &str { +// match self { +// // Self::Null(NullFile { name }) => name.as_deref().unwrap_or("null"), +// // Self::Stdin(StdioFile { name }) => name.as_deref().unwrap_or("stdin"), +// // Self::Stdout(StdioFile { name }) => name.as_deref().unwrap_or("stdout"), +// // Self::Stderr(StdioFile { name }) => name.as_deref().unwrap_or("stderr"), +// Self::Listen(ListenFile::Tls { name, .. }) => name, +// Self::Listen(ListenFile::Tcp { name, .. }) => name, +// Self::Connect(ConnectFile::Tls { name, host, .. }) => name.as_deref().unwrap_or(host), +// Self::Connect(ConnectFile::Tcp { name, host, .. }) => name.as_deref().unwrap_or(host), +// } +// } +// } + + + +/// File descriptor of a listen socket +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "prot", deny_unknown_fields)] +pub enum ListenFile { + /// TLS listen socket + #[serde(rename = "tls")] + Tls { + /// Name assigned to the file descriptor + name: FileName, + + /// Address to listen on + #[serde(default = "default_addr")] + addr: String, + + /// Port to listen on + #[serde(default = "default_tls_port")] + port: u16, + }, + + /// TCP listen socket + #[serde(rename = "tcp")] + Tcp { + /// Name assigned to the file descriptor + name: FileName, + + /// Address to listen on + #[serde(default = "default_addr")] + addr: String, + + /// Port to listen on + #[serde(default = "default_tcp_port")] + port: u16, + }, +} + +/// File descriptor of a stream socket +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "prot", deny_unknown_fields)] +pub enum ConnectFile { + /// TLS stream socket + #[serde(rename = "tls")] + Tls { + /// Name assigned to the file descriptor + name: Option, + + /// Host address to connect to + host: String, + + /// Port to connect to + #[serde(default = "default_tls_port")] + port: u16, + }, + + /// TCP stream socket + #[serde(rename = "tcp")] + Tcp { + /// Name assigned to the file descriptor + name: Option, + + /// Host address to connect to + host: String, + + /// Port to connect to + #[serde(default = "default_tcp_port")] + port: u16, + }, +} \ No newline at end of file diff --git a/src/net/tls.rs b/src/net/tls.rs new file mode 100644 index 0000000..1fe731e --- /dev/null +++ b/src/net/tls.rs @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! A WasiFile for transparent TLS + +use std::any::Any; +use std::io; +use std::io::{IoSlice, IoSliceMut, Read, Write}; +use std::sync::Arc; + +use anyhow::Context; +use cap_std::net::{Shutdown, TcpListener as CapListener, TcpStream as CapStream}; +#[cfg(windows)] +use io_extras::os::windows::AsRawHandleOrSocket; +#[cfg(unix)] +use io_lifetimes::AsFd; + +use rustls::{ClientConfig, ClientConnection, Connection, ServerConfig, ServerConnection}; +use wasi_common::file::{FdFlags, FileType, RiFlags, RoFlags, SdFlags, SiFlags}; +use wasi_common::{Error, ErrorExt, WasiFile}; +#[cfg(unix)] +use wasmtime_wasi::net::get_fd_flags; +use wasmtime_wasi::net::is_read_write; + +trait IOAsync { + fn complete_io_async(&mut self, io: &mut T) -> io::Result<(usize, usize)> + where + Self: Sized, + T: io::Read + io::Write; +} + +impl IOAsync for Connection { + /// This function uses `io` to complete any outstanding IO for this connection. + /// + /// Based upon [`complete_io`], but with added `flush()` and `WouldBlock` error handling for async connections. + /// + /// [`complete_io`]: https://github.com/rustls/rustls/blob/c42c53e13dfc54495cbb62577f6bb58eddf5ff8a/rustls/src/conn.rs#L462-L507 + fn complete_io_async(&mut self, io: &mut T) -> io::Result<(usize, usize)> + where + Self: Sized, + T: io::Read + io::Write, + { + let until_handshaked = self.is_handshaking(); + let mut eof = false; + let mut wrlen = 0; + let mut rdlen = 0; + + loop { + while self.wants_write() { + let res = self.write_tls(io); + if matches!(&res, Err(e) if e.kind() == io::ErrorKind::WouldBlock) { + break; + } + wrlen += res?; + } + + if !until_handshaked && wrlen > 0 { + let _ignored = io.flush(); + return Ok((rdlen, wrlen)); + } + + if !eof && self.wants_read() { + match self.read_tls(io) { + Ok(0) => eof = true, + Ok(n) => rdlen += n, + Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok((rdlen, wrlen)), + Err(e) => return Err(e), + } + } + + match self.process_new_packets() { + Ok(_) => {} + Err(e) => { + // In case we have an alert to send describing this error, + // try a last-gasp write -- but don't predate the primary + // error. + let _ignored = self.write_tls(io); + let _ignored = io.flush(); + + return Err(io::Error::new(io::ErrorKind::InvalidData, e)); + } + }; + + match (eof, until_handshaked, self.is_handshaking()) { + (_, true, false) => return Ok((rdlen, wrlen)), + (_, false, _) => return Ok((rdlen, wrlen)), + (true, true, true) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof)), + (..) => {} + } + } + } +} + +pub struct Stream { + tcp: CapStream, + tls: Connection, + nonblocking: bool, +} + +impl From for Box { + fn from(value: Stream) -> Self { + Box::new(value) + } +} + +impl Stream { + pub fn connect( + tcp: CapStream, + name: impl AsRef, + cfg: Arc, + ) -> Result { + let name = name + .as_ref() + .try_into() + .map_err(|_| Error::invalid_argument().context("failed to construct server name"))?; + + let tls = ClientConnection::new(cfg, name) + .context("failed to create a new TLS client connection") + .map(Connection::Client) + .map_err(|_| { + Error::invalid_argument().context("failed to create a new TLS client connection") + })?; + + let mut stream = Self { + tcp, + tls, + nonblocking: false, // this is only valid under assumption that this executable has opened the socket + }; + stream + .complete_io() + .map_err(|_| Error::invalid_argument().context("failed to complete connection I/O"))?; + Ok(stream) + } + + fn complete_io(&mut self) -> Result<(), Error> { + if self.nonblocking { + self.tls + .complete_io_async(&mut self.tcp) + .map_err(>::into)?; + } else { + self.tls + .complete_io(&mut self.tcp) + .map_err(>::into)?; + } + Ok(()) + } +} + +#[wiggle::async_trait] +impl WasiFile for Stream { + fn as_any(&self) -> &dyn Any { + self + } + + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.tcp.as_fd()) + } + + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.tcp.as_raw_handle_or_socket()) + } + + async fn get_filetype(&mut self) -> Result { + Ok(FileType::SocketStream) + } + + #[cfg(unix)] + async fn get_fdflags(&mut self) -> Result { + let fdflags = get_fd_flags(&self.tcp)?; + Ok(fdflags) + } + + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + if fdflags == FdFlags::NONBLOCK { + self.tcp + .set_nonblocking(true) + .map_err(|_| Error::invalid_argument().context("failed to enable NONBLOCK"))?; + self.nonblocking = true; + Ok(()) + } else if fdflags.is_empty() { + self.tcp + .set_nonblocking(false) + .map_err(|_| Error::invalid_argument().context("failed to disable NONBLOCK"))?; + self.nonblocking = false; + Ok(()) + } else { + Err(Error::invalid_argument().context("cannot set anything else than NONBLOCK")) + } + } + + async fn read_vectored<'a>(&mut self, bufs: &mut [IoSliceMut<'a>]) -> Result { + loop { + self.complete_io()?; + match self.tls.reader().read_vectored(bufs) { + Ok(n) => { + return n + .try_into() + .map_err(>::into) + } + Err(e) if !self.nonblocking && e.kind() == io::ErrorKind::WouldBlock => {} + Err(e) => return Err(e.into()), + } + } + } + + async fn write_vectored<'a>(&mut self, bufs: &[IoSlice<'a>]) -> Result { + match self.tls.writer().write_vectored(bufs) { + Ok(n) => { + self.complete_io()?; + n.try_into() + .map_err(>::into) + } + Err(e) => Err(e.into()), + } + } + + async fn peek(&mut self, _buf: &mut [u8]) -> Result { + // TODO: implement + // https://github.com/enarx/enarx/issues/2241 + Err(Error::badf()) + } + + async fn num_ready_bytes(&self) -> Result { + // TODO: implement + // https://github.com/enarx/enarx/issues/2242 + Ok(0) + } + + async fn readable(&self) -> Result<(), Error> { + let (readable, _writeable) = is_read_write(&self.tcp)?; + if readable { + Ok(()) + } else { + Err(Error::io()) + } + } + async fn writable(&self) -> Result<(), Error> { + let (_readable, writeable) = is_read_write(&self.tcp)?; + if writeable { + Ok(()) + } else { + Err(Error::io()) + } + } + + async fn sock_recv<'a>( + &mut self, + ri_data: &mut [IoSliceMut<'a>], + ri_flags: RiFlags, + ) -> Result<(u64, RoFlags), Error> { + if ri_flags != RiFlags::empty() { + return Err(Error::not_supported()); + } + // TODO: Add support for peek and waitall + // https://github.com/enarx/enarx/issues/2243 + let n = self.read_vectored(ri_data).await?; + Ok((n, RoFlags::empty())) + } + + async fn sock_send<'a>( + &mut self, + si_data: &[IoSlice<'a>], + si_flags: SiFlags, + ) -> Result { + if si_flags != SiFlags::empty() { + return Err(Error::not_supported()); + } + + let n = self.write_vectored(si_data).await?; + Ok(n) + } + + async fn sock_shutdown(&mut self, how: SdFlags) -> Result<(), Error> { + let how = if how == SdFlags::RD | SdFlags::WR { + Shutdown::Both + } else if how == SdFlags::RD { + Shutdown::Read + } else if how == SdFlags::WR { + Shutdown::Write + } else { + return Err(Error::invalid_argument()); + }; + self.tcp.shutdown(how)?; + Ok(()) + } +} + +pub struct Listener { + listener: CapListener, + cfg: Arc, +} + +impl Listener { + pub fn new(listener: CapListener, cfg: Arc) -> Self { + Self { listener, cfg } + } +} + +impl From for Box { + fn from(value: Listener) -> Self { + Box::new(value) + } +} + +#[wiggle::async_trait] +impl WasiFile for Listener { + fn as_any(&self) -> &dyn Any { + self + } + + #[cfg(unix)] + fn pollable(&self) -> Option> { + Some(self.listener.as_fd()) + } + + #[cfg(windows)] + fn pollable(&self) -> Option { + Some(self.listener.as_raw_handle_or_socket()) + } + + async fn sock_accept(&mut self, fdflags: FdFlags) -> Result, Error> { + let (tcp, ..) = self.listener.accept()?; + + let tls = ServerConnection::new(self.cfg.clone()) + .map_err(|e| Error::invalid_argument().context(e.to_string())) + .map(Connection::Server) + .map_err(|e| Error::invalid_argument().context(e.to_string()))?; + + let mut stream = Stream { + tcp, + tls, + nonblocking: false, + }; + stream.set_fdflags(FdFlags::empty()).await.map_err(|_| { + Error::invalid_argument().context("failed to unset client stream FD flags") + })?; + stream + .complete_io() + .map_err(|_| Error::invalid_argument().context("failed to complete connection I/O"))?; + stream.set_fdflags(fdflags).await.map_err(|_| { + Error::invalid_argument().context("failed to set requested client stream FD flags") + })?; + Ok(Box::new(stream)) + } + + async fn get_filetype(&mut self) -> Result { + Ok(FileType::SocketStream) + } + + #[cfg(unix)] + async fn get_fdflags(&mut self) -> Result { + let fdflags = get_fd_flags(&self.listener)?; + Ok(fdflags) + } + + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + if fdflags == FdFlags::NONBLOCK { + self.listener.set_nonblocking(true)?; + } else if fdflags.is_empty() { + self.listener.set_nonblocking(false)?; + } else { + return Err(Error::invalid_argument().context("cannot set anything else than NONBLOCK")); + } + Ok(()) + } + + async fn num_ready_bytes(&self) -> Result { + Ok(1) + } +}