diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..2ac3e067 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,105 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: Rust + +on: + + pull_request: + branches: + - master + + push: + branches: + - master + +jobs: + + stable: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Update Rust + run: | + rustup update + rustup target add wasm32-unknown-unknown + + - name: Build + env: + RUSTFLAGS: -C link-args=-S -D warnings + run: cargo build --target=wasm32-unknown-unknown --release --all-targets + + - name: Format (clippy) + env: + RUSTFLAGS: -C link-args=-S -D warnings + run: cargo clippy --target=wasm32-unknown-unknown --release --all-targets + + - name: Format (rustfmt) + run: cargo fmt -- --check + + - name: Format (manifest) + run: cargo verify-project + + - name: Format (addlicense) + run: | + go get -u github.com/google/addlicense + export PATH=$PATH:$(go env GOPATH)/bin + addlicense -check . + + - name: Package (docs) + run: cargo doc --no-deps --target=wasm32-unknown-unknown + + - name: Package (publish) + run: cargo publish --dry-run --target=wasm32-unknown-unknown + + nightly: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Update Rust + run: | + rustup toolchain install nightly + rustup target add --toolchain nightly wasm32-unknown-unknown + + - name: Build + env: + RUSTFLAGS: -C link-args=-S -D warnings + run: cargo +nightly build --target=wasm32-unknown-unknown --release --all-targets + + - name: Format (clippy) + env: + RUSTFLAGS: -C link-args=-S -D warnings + run: cargo +nightly clippy --target=wasm32-unknown-unknown --release --all-targets + + - name: Format (rustfmt) + run: cargo +nightly fmt -- --check + + - name: Format (manifest) + run: cargo +nightly verify-project + + - name: Format (addlicense) + run: | + go get -u github.com/google/addlicense + export PATH=$PATH:$(go env GOPATH)/bin + addlicense -check . + + - name: Package (docs) + run: cargo +nightly doc --no-deps --target=wasm32-unknown-unknown + + - name: Package (publish) + run: cargo +nightly publish --dry-run --target=wasm32-unknown-unknown diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..96ef6c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..90a7c213 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## [0.1.0] - 2020-02-29 + +- Initial release. + + +[0.1.0]: https://github.com/proxy-wasm/proxy-wasm-rust-sdk/releases/tag/v0.1.0 diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..f1f3bca0 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @PiotrSikora diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..654a0716 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ +# How to Contribute + +We'd love to accept your patches and contributions to this project. There are +just a few small guidelines you need to follow. + +## Contributor License Agreement + +Contributions to this project must be accompanied by a Contributor License +Agreement. You (or your employer) retain the copyright to your contribution; +this simply gives us permission to use and redistribute your contributions as +part of the project. Head over to to see +your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one +(even if it was for a different project), you probably don't need to do it +again. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult +[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more +information on using pull requests. + +## Community Guidelines + +This project follows [Google's Open Source Community +Guidelines](https://opensource.google/conduct/). diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..73c7afaa --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "proxy-wasm" +version = "0.1.0" +authors = ["Piotr Sikora "] +description = "WebAssembly for Proxies" +readme = "README.md" +license = "Apache-2.0" +repository = "https://github.com/proxy-wasm/proxy-wasm-rust-sdk" +edition = "2018" + +[dependencies] +hashbrown = { version = "0.7", default-features = false, features = ["ahash", "inline-more"] } +log = "0.4" +wee_alloc = "0.4" + +[dev-dependencies] +chrono = "0.4" + +[profile.release] +lto = true +opt-level = 3 +panic = "abort" + +[[example]] +name = "hello_world" +path = "examples/hello_world.rs" +crate-type = ["cdylib"] + +[[example]] +name = "http_auth_random" +path = "examples/http_auth_random.rs" +crate-type = ["cdylib"] + +[[example]] +name = "http_headers" +path = "examples/http_headers.rs" +crate-type = ["cdylib"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 00000000..8ee40bce --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# WebAssembly for Proxies (Rust SDK) + +[![Build Status][build-badge]][build-link] +[![Crate][crate-badge]][crate-link] +[![Documentation][docs-badge]][docs-link] +[![Apache 2.0 License][license-badge]][license-link] + + +[build-badge]: https://github.com/proxy-wasm/proxy-wasm-rust-sdk/workflows/Rust/badge.svg?branch=master +[build-link]: https://github.com/proxy-wasm/proxy-wasm-rust-sdk/actions?query=workflow%3ARust+branch%3Amaster +[crate-badge]: https://img.shields.io/crates/v/proxy-wasm.svg +[crate-link]: https://crates.io/crates/proxy-wasm +[docs-badge]: https://docs.rs/proxy-wasm/badge.svg +[docs-link]: https://docs.rs/proxy-wasm +[license-badge]: https://img.shields.io/github/license/proxy-wasm/proxy-wasm-rust-sdk +[license-link]: https://github.com/proxy-wasm/proxy-wasm-rust-sdk/blob/master/LICENSE diff --git a/examples/hello_world.rs b/examples/hello_world.rs new file mode 100644 index 00000000..8f439540 --- /dev/null +++ b/examples/hello_world.rs @@ -0,0 +1,42 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use chrono::{DateTime, Utc}; +use log::info; +use proxy_wasm::traits::*; +use proxy_wasm::types::*; +use std::time::Duration; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_log_level(LogLevel::Trace); + proxy_wasm::set_root_context(|_| -> Box { Box::new(HelloWorld) }); +} + +struct HelloWorld; + +impl Context for HelloWorld {} + +impl RootContext for HelloWorld { + fn on_vm_start(&mut self, _: usize) -> bool { + info!("Hello, World!"); + self.set_tick_period(Duration::from_secs(5)); + true + } + + fn on_tick(&mut self) { + let datetime: DateTime = self.get_current_time().into(); + info!("It's {}", datetime); + } +} diff --git a/examples/http_auth_random.rs b/examples/http_auth_random.rs new file mode 100644 index 00000000..b9e747c6 --- /dev/null +++ b/examples/http_auth_random.rs @@ -0,0 +1,67 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::trace; +use proxy_wasm::traits::*; +use proxy_wasm::types::*; +use std::time::Duration; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_log_level(LogLevel::Trace); + proxy_wasm::set_http_context(|_, _| -> Box { Box::new(HttpAuthRandom) }); +} + +struct HttpAuthRandom; + +impl HttpContext for HttpAuthRandom { + fn on_http_request_headers(&mut self, _: usize) -> Action { + self.dispatch_http_call( + "httpbin", + vec![ + (":method", "GET"), + (":path", "/bytes/1"), + (":authority", "httpbin.org"), + ], + None, + vec![], + Duration::from_secs(5), + ) + .unwrap(); + Action::Pause + } + + fn on_http_response_headers(&mut self, _: usize) -> Action { + self.set_http_response_header("Powered-By", Some("proxy-wasm")); + Action::Continue + } +} + +impl Context for HttpAuthRandom { + fn on_http_call_response(&mut self, _: u32, _: usize, body_size: usize, _: usize) { + if let Some(body) = self.get_http_call_response_body(0, body_size) { + if !body.is_empty() && body[0] % 2 == 0 { + trace!("Access granted."); + self.resume_http_request(); + return; + } + } + trace!("Access forbidden."); + self.send_http_response( + 403, + vec![("Powered-By", "proxy-wasm")], + Some(b"Access forbidden.\n"), + ); + } +} diff --git a/examples/http_headers.rs b/examples/http_headers.rs new file mode 100644 index 00000000..14b32861 --- /dev/null +++ b/examples/http_headers.rs @@ -0,0 +1,62 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use log::trace; +use proxy_wasm::traits::*; +use proxy_wasm::types::*; + +#[no_mangle] +pub fn _start() { + proxy_wasm::set_log_level(LogLevel::Trace); + proxy_wasm::set_http_context(|context_id, _| -> Box { + Box::new(HttpHeaders { context_id }) + }); +} + +struct HttpHeaders { + context_id: u32, +} + +impl Context for HttpHeaders {} + +impl HttpContext for HttpHeaders { + fn on_http_request_headers(&mut self, _: usize) -> Action { + for (name, value) in &self.get_http_request_headers() { + trace!("#{} -> {}: {}", self.context_id, name, value); + } + + match self.get_http_request_header(":path") { + Some(path) if path == "/hello" => { + self.send_http_response( + 200, + vec![("Hello", "World"), ("Powered-By", "proxy-wasm")], + Some(b"Hello, World!\n"), + ); + Action::Pause + } + _ => Action::Continue, + } + } + + fn on_http_response_headers(&mut self, _: usize) -> Action { + for (name, value) in &self.get_http_response_headers() { + trace!("#{} <- {}: {}", self.context_id, name, value); + } + Action::Continue + } + + fn on_log(&mut self) { + trace!("#{} completed.", self.context_id); + } +} diff --git a/src/allocator.rs b/src/allocator.rs new file mode 100644 index 00000000..5e66543b --- /dev/null +++ b/src/allocator.rs @@ -0,0 +1,26 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +#[no_mangle] +pub extern "C" fn malloc(size: usize) -> *mut u8 { + let mut vec: Vec = Vec::with_capacity(size); + unsafe { + vec.set_len(size); + } + let slice = vec.into_boxed_slice(); + Box::into_raw(slice) as *mut u8 +} diff --git a/src/dispatcher.rs b/src/dispatcher.rs new file mode 100644 index 00000000..897f95d8 --- /dev/null +++ b/src/dispatcher.rs @@ -0,0 +1,493 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::hostcalls; +use crate::traits::*; +use crate::types::*; +use hashbrown::HashMap; +use std::cell::{Cell, RefCell}; + +thread_local! { +static DISPATCHER: Dispatcher = Dispatcher::new(); +} + +pub(crate) fn set_root_context(callback: NewRootContext) { + DISPATCHER.with(|dispatcher| dispatcher.set_root_context(callback)); +} + +pub(crate) fn set_stream_context(callback: NewStreamContext) { + DISPATCHER.with(|dispatcher| dispatcher.set_stream_context(callback)); +} + +pub(crate) fn set_http_context(callback: NewHttpContext) { + DISPATCHER.with(|dispatcher| dispatcher.set_http_context(callback)); +} + +pub(crate) fn register_callout(token_id: u32) { + DISPATCHER.with(|dispatcher| dispatcher.register_callout(token_id)); +} + +struct NoopRoot; + +impl Context for NoopRoot {} +impl RootContext for NoopRoot {} + +struct Dispatcher { + new_root: Cell>, + roots: RefCell>>, + new_stream: Cell>, + streams: RefCell>>, + new_http_stream: Cell>, + http_streams: RefCell>>, + active_id: Cell, + callouts: RefCell>, +} + +impl Dispatcher { + fn new() -> Dispatcher { + Dispatcher { + new_root: Cell::new(None), + roots: RefCell::new(HashMap::new()), + new_stream: Cell::new(None), + streams: RefCell::new(HashMap::new()), + new_http_stream: Cell::new(None), + http_streams: RefCell::new(HashMap::new()), + active_id: Cell::new(0), + callouts: RefCell::new(HashMap::new()), + } + } + + fn set_root_context(&self, callback: NewRootContext) { + self.new_root.set(Some(callback)); + } + + fn set_stream_context(&self, callback: NewStreamContext) { + self.new_stream.set(Some(callback)); + } + + fn set_http_context(&self, callback: NewHttpContext) { + self.new_http_stream.set(Some(callback)); + } + + fn create_root_context(&self, context_id: u32) { + let new_context = match self.new_root.get() { + Some(f) => f(context_id), + None => Box::new(NoopRoot), + }; + if self + .roots + .borrow_mut() + .insert(context_id, new_context) + .is_some() + { + panic!("duplicate context_id") + } + } + + fn create_stream_context(&self, context_id: u32, root_context_id: u32) { + if !self.roots.borrow().contains_key(&root_context_id) { + panic!("invalid root_context_id") + } + let new_context = match self.new_stream.get() { + Some(f) => f(context_id, root_context_id), + None => panic!("missing constructor"), + }; + if self + .streams + .borrow_mut() + .insert(context_id, new_context) + .is_some() + { + panic!("duplicate context_id") + } + } + + fn create_http_context(&self, context_id: u32, root_context_id: u32) { + if !self.roots.borrow().contains_key(&root_context_id) { + panic!("invalid root_context_id") + } + let new_context = match self.new_http_stream.get() { + Some(f) => f(context_id, root_context_id), + None => panic!("missing constructor"), + }; + if self + .http_streams + .borrow_mut() + .insert(context_id, new_context) + .is_some() + { + panic!("duplicate context_id") + } + } + + fn register_callout(&self, token_id: u32) { + if self + .callouts + .borrow_mut() + .insert(token_id, self.active_id.get()) + .is_some() + { + panic!("duplicate token_id") + } + } + + fn on_create_context(&self, context_id: u32, root_context_id: u32) { + if root_context_id == 0 { + self.create_root_context(context_id) + } else if self.new_http_stream.get().is_some() { + self.create_http_context(context_id, root_context_id); + } else if self.new_stream.get().is_some() { + self.create_stream_context(context_id, root_context_id); + } else { + panic!("missing constructors") + } + } + + fn on_done(&self, context_id: u32) -> bool { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_done() + } else if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_done() + } else if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + root.on_done() + } else { + panic!("invalid context_id") + } + } + + fn on_log(&self, context_id: u32) { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_log() + } else if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_log() + } else if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + root.on_log() + } else { + panic!("invalid context_id") + } + } + + fn on_delete(&self, context_id: u32) { + if !(self.http_streams.borrow_mut().remove(&context_id).is_some() + || self.streams.borrow_mut().remove(&context_id).is_some() + || self.roots.borrow_mut().remove(&context_id).is_some()) + { + panic!("invalid context_id") + } + } + + fn on_vm_start(&self, context_id: u32, vm_configuration_size: usize) -> bool { + if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + root.on_vm_start(vm_configuration_size) + } else { + panic!("invalid context_id") + } + } + + fn on_configure(&self, context_id: u32, plugin_configuration_size: usize) -> bool { + if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + root.on_configure(plugin_configuration_size) + } else { + panic!("invalid context_id") + } + } + + fn on_tick(&self, context_id: u32) { + if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + root.on_tick() + } else { + panic!("invalid context_id") + } + } + + fn on_queue_ready(&self, context_id: u32, queue_id: u32) { + if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + root.on_queue_ready(queue_id) + } else { + panic!("invalid context_id") + } + } + + fn on_new_connection(&self, context_id: u32) -> Action { + if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_new_connection() + } else { + panic!("invalid context_id") + } + } + + fn on_downstream_data(&self, context_id: u32, data_size: usize, end_of_stream: bool) -> Action { + if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_downstream_data(data_size, end_of_stream) + } else { + panic!("invalid context_id") + } + } + + fn on_downstream_close(&self, context_id: u32, peer_type: PeerType) { + if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_downstream_close(peer_type) + } else { + panic!("invalid context_id") + } + } + + fn on_upstream_data(&self, context_id: u32, data_size: usize, end_of_stream: bool) -> Action { + if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_upstream_data(data_size, end_of_stream) + } else { + panic!("invalid context_id") + } + } + + fn on_upstream_close(&self, context_id: u32, peer_type: PeerType) { + if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + stream.on_upstream_close(peer_type) + } else { + panic!("invalid context_id") + } + } + + fn on_http_request_headers(&self, context_id: u32, num_headers: usize) -> Action { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_http_request_headers(num_headers) + } else { + panic!("invalid context_id") + } + } + + fn on_http_request_body( + &self, + context_id: u32, + body_size: usize, + end_of_stream: bool, + ) -> Action { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_http_request_body(body_size, end_of_stream) + } else { + panic!("invalid context_id") + } + } + + fn on_http_request_trailers(&self, context_id: u32, num_trailers: usize) -> Action { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_http_request_trailers(num_trailers) + } else { + panic!("invalid context_id") + } + } + + fn on_http_response_headers(&self, context_id: u32, num_headers: usize) -> Action { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_http_response_headers(num_headers) + } else { + panic!("invalid context_id") + } + } + + fn on_http_response_body( + &self, + context_id: u32, + body_size: usize, + end_of_stream: bool, + ) -> Action { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_http_response_body(body_size, end_of_stream) + } else { + panic!("invalid context_id") + } + } + + fn on_http_response_trailers(&self, context_id: u32, num_trailers: usize) -> Action { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + http_stream.on_http_response_trailers(num_trailers) + } else { + panic!("invalid context_id") + } + } + + fn on_http_call_response( + &self, + token_id: u32, + num_headers: usize, + body_size: usize, + num_trailers: usize, + ) { + if let Some(context_id) = self.callouts.borrow_mut().remove(&token_id) { + if let Some(http_stream) = self.http_streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + hostcalls::set_effective_context(context_id).unwrap(); + http_stream.on_http_call_response(token_id, num_headers, body_size, num_trailers) + } else if let Some(stream) = self.streams.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + hostcalls::set_effective_context(context_id).unwrap(); + stream.on_http_call_response(token_id, num_headers, body_size, num_trailers) + } else if let Some(root) = self.roots.borrow_mut().get_mut(&context_id) { + self.active_id.set(context_id); + hostcalls::set_effective_context(context_id).unwrap(); + root.on_http_call_response(token_id, num_headers, body_size, num_trailers) + } + } else { + panic!("invalid token_id") + } + } +} + +#[no_mangle] +pub extern "C" fn proxy_on_context_create(context_id: u32, root_context_id: u32) { + DISPATCHER.with(|dispatcher| dispatcher.on_create_context(context_id, root_context_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_done(context_id: u32) -> bool { + DISPATCHER.with(|dispatcher| dispatcher.on_done(context_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_log(context_id: u32) { + DISPATCHER.with(|dispatcher| dispatcher.on_log(context_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_delete(context_id: u32) { + DISPATCHER.with(|dispatcher| dispatcher.on_delete(context_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_vm_start(context_id: u32, vm_configuration_size: usize) -> bool { + DISPATCHER.with(|dispatcher| dispatcher.on_vm_start(context_id, vm_configuration_size)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_configure(context_id: u32, plugin_configuration_size: usize) -> bool { + DISPATCHER.with(|dispatcher| dispatcher.on_configure(context_id, plugin_configuration_size)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_tick(context_id: u32) { + DISPATCHER.with(|dispatcher| dispatcher.on_tick(context_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_queue_ready(context_id: u32, queue_id: u32) { + DISPATCHER.with(|dispatcher| dispatcher.on_queue_ready(context_id, queue_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_new_connection(context_id: u32) -> Action { + DISPATCHER.with(|dispatcher| dispatcher.on_new_connection(context_id)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_downstream_data( + context_id: u32, + data_size: usize, + end_of_stream: bool, +) -> Action { + DISPATCHER + .with(|dispatcher| dispatcher.on_downstream_data(context_id, data_size, end_of_stream)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_downstream_connection_close(context_id: u32, peer_type: PeerType) { + DISPATCHER.with(|dispatcher| dispatcher.on_downstream_close(context_id, peer_type)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_upstream_data( + context_id: u32, + data_size: usize, + end_of_stream: bool, +) -> Action { + DISPATCHER.with(|dispatcher| dispatcher.on_upstream_data(context_id, data_size, end_of_stream)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_upstream_connection_close(context_id: u32, peer_type: PeerType) { + DISPATCHER.with(|dispatcher| dispatcher.on_upstream_close(context_id, peer_type)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_request_headers(context_id: u32, num_headers: usize) -> Action { + DISPATCHER.with(|dispatcher| dispatcher.on_http_request_headers(context_id, num_headers)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_request_body( + context_id: u32, + body_size: usize, + end_of_stream: bool, +) -> Action { + DISPATCHER + .with(|dispatcher| dispatcher.on_http_request_body(context_id, body_size, end_of_stream)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_request_trailers(context_id: u32, num_trailers: usize) -> Action { + DISPATCHER.with(|dispatcher| dispatcher.on_http_request_trailers(context_id, num_trailers)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_response_headers(context_id: u32, num_headers: usize) -> Action { + DISPATCHER.with(|dispatcher| dispatcher.on_http_response_headers(context_id, num_headers)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_response_body( + context_id: u32, + body_size: usize, + end_of_stream: bool, +) -> Action { + DISPATCHER + .with(|dispatcher| dispatcher.on_http_response_body(context_id, body_size, end_of_stream)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_response_trailers(context_id: u32, num_trailers: usize) -> Action { + DISPATCHER.with(|dispatcher| dispatcher.on_http_response_trailers(context_id, num_trailers)) +} + +#[no_mangle] +pub extern "C" fn proxy_on_http_call_response( + _context_id: u32, + token_id: u32, + num_headers: usize, + body_size: usize, + num_trailers: usize, +) { + DISPATCHER.with(|dispatcher| { + dispatcher.on_http_call_response(token_id, num_headers, body_size, num_trailers) + }) +} diff --git a/src/hostcalls.rs b/src/hostcalls.rs new file mode 100644 index 00000000..0b770a0f --- /dev/null +++ b/src/hostcalls.rs @@ -0,0 +1,721 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::dispatcher; +use crate::types::*; +use std::ptr::{null, null_mut}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +extern "C" { + fn proxy_log(level: LogLevel, message_data: *const u8, message_size: usize) -> Status; +} + +pub fn log(level: LogLevel, message: &str) -> Result<(), Status> { + unsafe { + match proxy_log(level, message.as_ptr(), message.len()) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_current_time_nanoseconds(return_time: *mut u64) -> Status; +} + +pub fn get_current_time() -> Result { + let mut return_time: u64 = 0; + unsafe { + match proxy_get_current_time_nanoseconds(&mut return_time) { + Status::Ok => Ok(UNIX_EPOCH + Duration::from_nanos(return_time)), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_set_tick_period_milliseconds(period: u32) -> Status; +} + +pub fn set_tick_period(period: Duration) -> Result<(), Status> { + unsafe { + match proxy_set_tick_period_milliseconds(period.as_millis() as u32) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_configuration( + return_buffer_data: *mut *mut u8, + return_buffer_size: *mut usize, + ) -> Status; +} + +pub fn get_configuration() -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + match proxy_get_configuration(&mut return_data, &mut return_size) { + Status::Ok => { + if !return_data.is_null() { + Ok(Some(Vec::from_raw_parts( + return_data, + return_size, + return_size, + ))) + } else { + Ok(None) + } + } + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_buffer_bytes( + buffer_type: BufferType, + start: usize, + max_size: usize, + return_buffer_data: *mut *mut u8, + return_buffer_size: *mut usize, + ) -> Status; +} + +pub fn get_buffer( + buffer_type: BufferType, + start: usize, + max_size: usize, +) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + match proxy_get_buffer_bytes( + buffer_type, + start, + max_size, + &mut return_data, + &mut return_size, + ) { + Status::Ok => { + if !return_data.is_null() { + Ok(Some(Vec::from_raw_parts( + return_data, + return_size, + return_size, + ))) + } else { + Ok(None) + } + } + Status::NotFound => Ok(None), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_header_map_pairs( + map_type: MapType, + return_map_data: *mut *mut u8, + return_map_size: *mut usize, + ) -> Status; +} + +pub fn get_map(map_type: MapType) -> Result, Status> { + unsafe { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + match proxy_get_header_map_pairs(map_type, &mut return_data, &mut return_size) { + Status::Ok => { + if !return_data.is_null() { + let serialized_map = Vec::from_raw_parts(return_data, return_size, return_size); + Ok(utils::deserialize_map(&serialized_map)) + } else { + Ok(Vec::new()) + } + } + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_set_header_map_pairs( + map_type: MapType, + map_data: *const u8, + map_size: usize, + ) -> Status; +} + +pub fn set_map(map_type: MapType, map: Vec<(&str, &str)>) -> Result<(), Status> { + let serialized_map = utils::serialize_map(map); + unsafe { + match proxy_set_header_map_pairs(map_type, serialized_map.as_ptr(), serialized_map.len()) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_header_map_value( + map_type: MapType, + key_data: *const u8, + key_size: usize, + return_value_data: *mut *mut u8, + return_value_size: *mut usize, + ) -> Status; +} + +pub fn get_map_value(map_type: MapType, key: &str) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + match proxy_get_header_map_value( + map_type, + key.as_ptr(), + key.len(), + &mut return_data, + &mut return_size, + ) { + Status::Ok => { + if !return_data.is_null() { + Ok(Some( + String::from_utf8(Vec::from_raw_parts( + return_data, + return_size, + return_size, + )) + .unwrap(), + )) + } else { + Ok(None) + } + } + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_replace_header_map_value( + map_type: MapType, + key_data: *const u8, + key_size: usize, + value_data: *const u8, + value_size: usize, + ) -> Status; +} + +extern "C" { + fn proxy_remove_header_map_value( + map_type: MapType, + key_data: *const u8, + key_size: usize, + ) -> Status; +} + +pub fn set_map_value(map_type: MapType, key: &str, value: Option<&str>) -> Result<(), Status> { + unsafe { + if let Some(value) = value { + match proxy_replace_header_map_value( + map_type, + key.as_ptr(), + key.len(), + value.as_ptr(), + value.len(), + ) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } else { + match proxy_remove_header_map_value(map_type, key.as_ptr(), key.len()) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } + } +} + +extern "C" { + fn proxy_add_header_map_value( + map_type: MapType, + key_data: *const u8, + key_size: usize, + value_data: *const u8, + value_size: usize, + ) -> Status; +} + +pub fn add_map_value(map_type: MapType, key: &str, value: &str) -> Result<(), Status> { + unsafe { + match proxy_add_header_map_value( + map_type, + key.as_ptr(), + key.len(), + value.as_ptr(), + value.len(), + ) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_property( + path_data: *const u8, + path_size: usize, + return_value_data: *mut *mut u8, + return_value_size: *mut usize, + ) -> Status; +} + +pub fn get_property(path: Vec<&str>) -> Result, Status> { + let serialized_path = utils::serialize_property_path(path); + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + match proxy_get_property( + serialized_path.as_ptr(), + serialized_path.len(), + &mut return_data, + &mut return_size, + ) { + Status::Ok => { + if !return_data.is_null() { + Ok(Some(Vec::from_raw_parts( + return_data, + return_size, + return_size, + ))) + } else { + Ok(None) + } + } + Status::NotFound => Ok(None), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_set_property( + path_data: *const u8, + path_size: usize, + value_data: *const u8, + value_size: usize, + ) -> Status; +} + +pub fn set_property(path: Vec<&str>, value: Option<&[u8]>) -> Result<(), Status> { + let serialized_path = utils::serialize_property_path(path); + unsafe { + match proxy_set_property( + serialized_path.as_ptr(), + serialized_path.len(), + value.map_or(null(), |value| value.as_ptr()), + value.map_or(0, |value| value.len()), + ) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_get_shared_data( + key_data: *const u8, + key_size: usize, + return_value_data: *mut *mut u8, + return_value_size: *mut usize, + return_cas: *mut u32, + ) -> Status; +} + +pub fn get_shared_data(key: &str) -> Result<(Option, Option), Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + let mut return_cas: u32 = 0; + unsafe { + match proxy_get_shared_data( + key.as_ptr(), + key.len(), + &mut return_data, + &mut return_size, + &mut return_cas, + ) { + Status::Ok => { + let cas = match return_cas { + 0 => None, + cas => Some(cas), + }; + if !return_data.is_null() { + Ok(( + Some(Vec::from_raw_parts(return_data, return_size, return_size)), + cas, + )) + } else { + Ok((None, cas)) + } + } + Status::NotFound => Ok((None, None)), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_set_shared_data( + key_data: *const u8, + key_size: usize, + value_data: *const u8, + value_size: usize, + cas: u32, + ) -> Status; +} + +pub fn set_shared_data(key: &str, value: Option<&[u8]>, cas: Option) -> Result<(), Status> { + unsafe { + match proxy_set_shared_data( + key.as_ptr(), + key.len(), + value.map_or(null(), |value| value.as_ptr()), + value.map_or(0, |value| value.len()), + cas.unwrap_or(0), + ) { + Status::Ok => Ok(()), + Status::CasMismatch => Err(Status::CasMismatch), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_register_shared_queue( + name_data: *const u8, + name_size: usize, + return_id: *mut u32, + ) -> Status; +} + +pub fn register_shared_queue(name: &str) -> Result { + unsafe { + let mut return_id: u32 = 0; + match proxy_register_shared_queue(name.as_ptr(), name.len(), &mut return_id) { + Status::Ok => Ok(return_id), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_resolve_shared_queue( + vm_id_data: *const u8, + vm_id_size: usize, + name_data: *const u8, + name_size: usize, + return_id: *mut u32, + ) -> Status; +} + +pub fn resolve_shared_queue(vm_id: &str, name: &str) -> Result, Status> { + let mut return_id: u32 = 0; + unsafe { + match proxy_resolve_shared_queue( + vm_id.as_ptr(), + vm_id.len(), + name.as_ptr(), + name.len(), + &mut return_id, + ) { + Status::Ok => Ok(Some(return_id)), + Status::NotFound => Ok(None), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_dequeue_shared_queue( + queue_id: u32, + return_value_data: *mut *mut u8, + return_value_size: *mut usize, + ) -> Status; +} + +pub fn dequeue_shared_queue(queue_id: u32) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + match proxy_dequeue_shared_queue(queue_id, &mut return_data, &mut return_size) { + Status::Ok => { + if !return_data.is_null() { + Ok(Some(Vec::from_raw_parts( + return_data, + return_size, + return_size, + ))) + } else { + Ok(None) + } + } + Status::Empty => Ok(None), + Status::NotFound => Err(Status::NotFound), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_enqueue_shared_queue( + queue_id: u32, + value_data: *const u8, + value_size: usize, + ) -> Status; +} + +pub fn enqueue_shared_queue(queue_id: u32, value: Option<&[u8]>) -> Result<(), Status> { + unsafe { + match proxy_enqueue_shared_queue( + queue_id, + value.map_or(null(), |value| value.as_ptr()), + value.map_or(0, |value| value.len()), + ) { + Status::Ok => Ok(()), + Status::NotFound => Err(Status::NotFound), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_continue_request() -> Status; +} + +pub fn resume_http_request() -> Result<(), Status> { + unsafe { + match proxy_continue_request() { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_continue_response() -> Status; +} + +pub fn resume_http_response() -> Result<(), Status> { + unsafe { + match proxy_continue_response() { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_send_local_response( + status_code: u32, + status_code_details_data: *const u8, + status_code_details_size: usize, + body_data: *const u8, + body_size: usize, + headers_data: *const u8, + headers_size: usize, + grpc_status: i32, + ) -> Status; +} + +pub fn send_http_response( + status_code: u32, + headers: Vec<(&str, &str)>, + body: Option<&[u8]>, +) -> Result<(), Status> { + let serialized_headers = utils::serialize_map(headers); + unsafe { + match proxy_send_local_response( + status_code, + null(), + 0, + body.map_or(null(), |body| body.as_ptr()), + body.map_or(0, |body| body.len()), + serialized_headers.as_ptr(), + serialized_headers.len(), + -1, + ) { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_clear_route_cache() -> Status; +} + +pub fn clear_http_route_cache() -> Result<(), Status> { + unsafe { + match proxy_clear_route_cache() { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_http_call( + upstream_data: *const u8, + upstream_size: usize, + headers_data: *const u8, + headers_size: usize, + body_data: *const u8, + body_size: usize, + trailers_data: *const u8, + trailers_size: usize, + timeout: u32, + return_token: *mut u32, + ) -> Status; +} + +pub fn dispatch_http_call( + upstream: &str, + headers: Vec<(&str, &str)>, + body: Option<&[u8]>, + trailers: Vec<(&str, &str)>, + timeout: Duration, +) -> Result { + let serialized_headers = utils::serialize_map(headers); + let serialized_trailers = utils::serialize_map(trailers); + let mut return_token: u32 = 0; + unsafe { + match proxy_http_call( + upstream.as_ptr(), + upstream.len(), + serialized_headers.as_ptr(), + serialized_headers.len(), + body.map_or(null(), |body| body.as_ptr()), + body.map_or(0, |body| body.len()), + serialized_trailers.as_ptr(), + serialized_trailers.len(), + timeout.as_millis() as u32, + &mut return_token, + ) { + Status::Ok => { + dispatcher::register_callout(return_token); + Ok(return_token) + } + Status::BadArgument => Err(Status::BadArgument), + Status::InternalFailure => Err(Status::InternalFailure), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_set_effective_context(context_id: u32) -> Status; +} + +pub fn set_effective_context(context_id: u32) -> Result<(), Status> { + unsafe { + match proxy_set_effective_context(context_id) { + Status::Ok => Ok(()), + Status::BadArgument => Err(Status::BadArgument), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +extern "C" { + fn proxy_done() -> Status; +} + +pub fn done() -> Result<(), Status> { + unsafe { + match proxy_done() { + Status::Ok => Ok(()), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +mod utils { + use crate::types::Bytes; + use std::convert::TryFrom; + + pub(super) fn serialize_property_path(path: Vec<&str>) -> Bytes { + if path.is_empty() { + return Vec::new(); + } + let mut size: usize = 0; + for part in &path { + size += part.len() + 1; + } + let mut bytes: Bytes = Vec::with_capacity(size); + for part in &path { + bytes.extend_from_slice(&part.as_bytes()); + bytes.push(0); + } + bytes.pop(); + bytes + } + + pub(super) fn serialize_map(map: Vec<(&str, &str)>) -> Bytes { + let mut size: usize = 4; + for (name, value) in &map { + size += name.len() + value.len() + 10; + } + let mut bytes: Bytes = Vec::with_capacity(size); + bytes.extend_from_slice(&map.len().to_le_bytes()); + for (name, value) in &map { + bytes.extend_from_slice(&name.len().to_le_bytes()); + bytes.extend_from_slice(&value.len().to_le_bytes()); + } + for (name, value) in &map { + bytes.extend_from_slice(&name.as_bytes()); + bytes.push(0); + bytes.extend_from_slice(&value.as_bytes()); + bytes.push(0); + } + bytes + } + + pub(super) fn deserialize_map(bytes: &[u8]) -> Vec<(String, String)> { + let mut map = Vec::new(); + if bytes.is_empty() { + return map; + } + let size = u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[0..4]).unwrap()) as usize; + let mut p = 4 + size * 8; + for n in 0..size { + let s = 4 + n * 8; + let size = u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[s..s + 4]).unwrap()) as usize; + let key = bytes[p..p + size].to_vec(); + p += size + 1; + let size = + u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[s + 4..s + 8]).unwrap()) as usize; + let value = bytes[p..p + size].to_vec(); + p += size + 1; + map.push(( + String::from_utf8(key).unwrap(), + String::from_utf8(value).unwrap(), + )); + } + map + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..cee98b72 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,40 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub mod hostcalls; +pub mod traits; +pub mod types; + +mod allocator; +mod dispatcher; +mod logger; + +pub fn set_log_level(level: types::LogLevel) { + logger::set_log_level(level); +} + +pub fn set_root_context(callback: types::NewRootContext) { + dispatcher::set_root_context(callback); +} + +pub fn set_stream_context(callback: types::NewStreamContext) { + dispatcher::set_stream_context(callback); +} + +pub fn set_http_context(callback: types::NewHttpContext) { + dispatcher::set_http_context(callback); +} + +#[no_mangle] +pub extern "C" fn proxy_abi_version_0_1_0() {} diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 00000000..050a356d --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,71 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::hostcalls; +use crate::types::LogLevel; +use std::panic; +use std::sync::atomic::{AtomicBool, Ordering}; + +struct Logger; + +static LOGGER: Logger = Logger; +static INITIALIZED: AtomicBool = AtomicBool::new(false); + +pub(crate) fn set_log_level(level: LogLevel) { + if !INITIALIZED.load(Ordering::Relaxed) { + log::set_logger(&LOGGER).unwrap(); + panic::set_hook(Box::new(|panic_info| { + hostcalls::log(LogLevel::Critical, &panic_info.to_string()).unwrap(); + })); + INITIALIZED.store(true, Ordering::Relaxed); + } + LOGGER.set_log_level(level); +} + +impl Logger { + pub fn set_log_level(&self, level: LogLevel) { + let filter = match level { + LogLevel::Trace => log::LevelFilter::Trace, + LogLevel::Debug => log::LevelFilter::Debug, + LogLevel::Info => log::LevelFilter::Info, + LogLevel::Warn => log::LevelFilter::Warn, + LogLevel::Error => log::LevelFilter::Error, + LogLevel::Critical => log::LevelFilter::Off, + }; + log::set_max_level(filter); + } +} + +impl log::Log for Logger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= log::max_level() + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + let level = match record.level() { + log::Level::Trace => LogLevel::Trace, + log::Level::Debug => LogLevel::Debug, + log::Level::Info => LogLevel::Info, + log::Level::Warn => LogLevel::Warn, + log::Level::Error => LogLevel::Error, + }; + let message = record.args().to_string(); + hostcalls::log(level, &message).unwrap(); + } + + fn flush(&self) {} +} diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 00000000..ffad21a4 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,289 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::hostcalls; +use crate::types::*; +use std::time::{Duration, SystemTime}; + +pub trait Context { + fn get_current_time(&self) -> SystemTime { + hostcalls::get_current_time().unwrap() + } + + fn get_property(&self, path: Vec<&str>) -> Option { + hostcalls::get_property(path).unwrap() + } + + fn set_property(&self, path: Vec<&str>, value: Option<&[u8]>) { + hostcalls::set_property(path, value).unwrap() + } + + fn get_shared_data(&self, key: &str) -> (Option, Option) { + hostcalls::get_shared_data(key).unwrap() + } + + fn set_shared_data( + &self, + key: &str, + value: Option<&[u8]>, + cas: Option, + ) -> Result<(), Status> { + hostcalls::set_shared_data(key, value, cas) + } + + fn register_shared_queue(&self, name: &str) -> u32 { + hostcalls::register_shared_queue(name).unwrap() + } + + fn resolve_shared_queue(&self, vm_id: &str, name: &str) -> Option { + hostcalls::resolve_shared_queue(vm_id, name).unwrap() + } + + fn dequeue_shared_queue(&self, queue_id: u32) -> Result, Status> { + hostcalls::dequeue_shared_queue(queue_id) + } + + fn enqueue_shared_queue(&self, queue_id: u32, value: Option<&[u8]>) -> Result<(), Status> { + hostcalls::enqueue_shared_queue(queue_id, value) + } + + fn dispatch_http_call( + &self, + upstream: &str, + headers: Vec<(&str, &str)>, + body: Option<&[u8]>, + trailers: Vec<(&str, &str)>, + timeout: Duration, + ) -> Result { + hostcalls::dispatch_http_call(upstream, headers, body, trailers, timeout) + } + + fn on_http_call_response( + &mut self, + _token_id: u32, + _num_headers: usize, + _body_size: usize, + _num_trailers: usize, + ) { + } + + fn get_http_call_response_headers(&self) -> Vec<(String, String)> { + hostcalls::get_map(MapType::HttpCallResponseHeaders).unwrap() + } + + fn get_http_call_response_body(&self, start: usize, max_size: usize) -> Option { + hostcalls::get_buffer(BufferType::HttpCallResponseBody, start, max_size).unwrap() + } + + fn get_http_call_response_trailers(&self) -> Vec<(String, String)> { + hostcalls::get_map(MapType::HttpCallResponseTrailers).unwrap() + } + + fn on_done(&mut self) -> bool { + true + } + + fn done(&self) { + hostcalls::done().unwrap() + } +} + +pub trait RootContext: Context { + fn on_vm_start(&mut self, _vm_configuration_size: usize) -> bool { + true + } + + fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool { + true + } + + fn get_configuration(&self) -> Option { + hostcalls::get_configuration().unwrap() + } + + fn set_tick_period(&self, period: Duration) { + hostcalls::set_tick_period(period).unwrap() + } + + fn on_tick(&mut self) {} + + fn on_queue_ready(&mut self, _queue_id: u32) {} + + fn on_log(&mut self) {} +} + +pub trait StreamContext: Context { + fn on_new_connection(&mut self) -> Action { + Action::Continue + } + + fn on_downstream_data(&mut self, _data_size: usize, _end_of_stream: bool) -> Action { + Action::Continue + } + + fn get_downstream_data(&self, start: usize, max_size: usize) -> Option { + hostcalls::get_buffer(BufferType::DownstreamData, start, max_size).unwrap() + } + + fn on_downstream_close(&mut self, _peer_type: PeerType) {} + + fn on_upstream_data(&mut self, _data_size: usize, _end_of_stream: bool) -> Action { + Action::Continue + } + + fn get_upstream_data(&self, start: usize, max_size: usize) -> Option { + hostcalls::get_buffer(BufferType::UpstreamData, start, max_size).unwrap() + } + + fn on_upstream_close(&mut self, _peer_type: PeerType) {} + + fn on_log(&mut self) {} +} + +pub trait HttpContext: Context { + fn on_http_request_headers(&mut self, _num_headers: usize) -> Action { + Action::Continue + } + + fn get_http_request_headers(&self) -> Vec<(String, String)> { + hostcalls::get_map(MapType::HttpRequestHeaders).unwrap() + } + + fn set_http_request_headers(&self, headers: Vec<(&str, &str)>) { + hostcalls::set_map(MapType::HttpRequestHeaders, headers).unwrap() + } + + fn get_http_request_header(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpRequestHeaders, &name).unwrap() + } + + fn set_http_request_header(&self, name: &str, value: Option<&str>) { + hostcalls::set_map_value(MapType::HttpRequestHeaders, &name, value).unwrap() + } + + fn add_http_request_header(&self, name: &str, value: &str) { + hostcalls::add_map_value(MapType::HttpRequestHeaders, &name, value).unwrap() + } + + fn on_http_request_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action { + Action::Continue + } + + fn get_http_request_body(&self, start: usize, max_size: usize) -> Option { + hostcalls::get_buffer(BufferType::HttpRequestBody, start, max_size).unwrap() + } + + fn on_http_request_trailers(&mut self, _num_trailers: usize) -> Action { + Action::Continue + } + + fn get_http_request_trailers(&self) -> Vec<(String, String)> { + hostcalls::get_map(MapType::HttpRequestTrailers).unwrap() + } + + fn set_http_request_trailers(&self, trailers: Vec<(&str, &str)>) { + hostcalls::set_map(MapType::HttpRequestTrailers, trailers).unwrap() + } + + fn get_http_request_trailer(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpRequestTrailers, &name).unwrap() + } + + fn set_http_request_trailer(&self, name: &str, value: Option<&str>) { + hostcalls::set_map_value(MapType::HttpRequestTrailers, &name, value).unwrap() + } + + fn add_http_request_trailer(&self, name: &str, value: &str) { + hostcalls::add_map_value(MapType::HttpRequestTrailers, &name, value).unwrap() + } + + fn resume_http_request(&self) { + hostcalls::resume_http_request().unwrap() + } + + fn on_http_response_headers(&mut self, _num_headers: usize) -> Action { + Action::Continue + } + + fn get_http_response_headers(&self) -> Vec<(String, String)> { + hostcalls::get_map(MapType::HttpResponseHeaders).unwrap() + } + + fn set_http_response_headers(&self, headers: Vec<(&str, &str)>) { + hostcalls::set_map(MapType::HttpResponseHeaders, headers).unwrap() + } + + fn get_http_response_header(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpResponseHeaders, &name).unwrap() + } + + fn set_http_response_header(&self, name: &str, value: Option<&str>) { + hostcalls::set_map_value(MapType::HttpResponseHeaders, &name, value).unwrap() + } + + fn add_http_response_header(&self, name: &str, value: &str) { + hostcalls::add_map_value(MapType::HttpResponseHeaders, &name, value).unwrap() + } + + fn on_http_response_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action { + Action::Continue + } + + fn get_http_response_body(&self, start: usize, max_size: usize) -> Option { + hostcalls::get_buffer(BufferType::HttpResponseBody, start, max_size).unwrap() + } + + fn on_http_response_trailers(&mut self, _num_trailers: usize) -> Action { + Action::Continue + } + + fn get_http_response_trailers(&self) -> Vec<(String, String)> { + hostcalls::get_map(MapType::HttpResponseTrailers).unwrap() + } + + fn set_http_response_trailers(&self, headers: Vec<(&str, &str)>) { + hostcalls::set_map(MapType::HttpResponseTrailers, headers).unwrap() + } + + fn get_http_response_trailer(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpResponseTrailers, &name).unwrap() + } + + fn set_http_response_trailer(&self, name: &str, value: Option<&str>) { + hostcalls::set_map_value(MapType::HttpResponseTrailers, &name, value).unwrap() + } + + fn add_http_response_trailer(&self, name: &str, value: &str) { + hostcalls::add_map_value(MapType::HttpResponseTrailers, &name, value).unwrap() + } + + fn resume_http_response(&self) { + hostcalls::resume_http_response().unwrap() + } + + fn send_http_response( + &self, + status_code: u32, + headers: Vec<(&str, &str)>, + body: Option<&[u8]>, + ) { + hostcalls::send_http_response(status_code, headers, body).unwrap() + } + + fn clear_http_route_cache(&self) { + hostcalls::clear_http_route_cache().unwrap() + } + + fn on_log(&mut self) {} +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 00000000..362e3c1b --- /dev/null +++ b/src/types.rs @@ -0,0 +1,79 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::traits::*; + +pub type NewRootContext = fn(context_id: u32) -> Box; +pub type NewStreamContext = fn(context_id: u32, root_context_id: u32) -> Box; +pub type NewHttpContext = fn(context_id: u32, root_context_id: u32) -> Box; + +#[repr(u32)] +#[derive(Debug)] +pub enum LogLevel { + Trace = 0, + Debug = 1, + Info = 2, + Warn = 3, + Error = 4, + Critical = 5, +} + +#[repr(u32)] +#[derive(Debug)] +pub enum Action { + Continue = 0, + Pause = 1, +} + +#[repr(u32)] +#[derive(Debug)] +pub enum Status { + Ok = 0, + NotFound = 1, + BadArgument = 2, + Empty = 7, + CasMismatch = 8, + InternalFailure = 10, +} + +#[repr(u32)] +#[derive(Debug)] +pub enum BufferType { + HttpRequestBody = 0, + HttpResponseBody = 1, + DownstreamData = 2, + UpstreamData = 3, + HttpCallResponseBody = 4, +} + +#[repr(u32)] +#[derive(Debug)] +pub enum MapType { + HttpRequestHeaders = 0, + HttpRequestTrailers = 1, + HttpResponseHeaders = 2, + HttpResponseTrailers = 3, + HttpCallResponseHeaders = 7, + HttpCallResponseTrailers = 8, +} + +#[repr(u32)] +#[derive(Debug)] +pub enum PeerType { + Unknown = 0, + Local = 1, + Remote = 2, +} + +pub type Bytes = Vec;