| // Copyright 2020 Envoy Project Authors |
| // 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. |
| // |
| #include "include/proxy-wasm/limits.h" |
| #include "include/proxy-wasm/pairs_util.h" |
| #include "include/proxy-wasm/wasm.h" |
| |
| #include <openssl/rand.h> |
| |
| #include <utility> |
| |
| namespace proxy_wasm { |
| |
| thread_local ContextBase *current_context_; |
| thread_local uint32_t effective_context_id_ = 0; |
| |
| // Any currently executing Wasm call context. |
| ContextBase *contextOrEffectiveContext() { |
| if (effective_context_id_ == 0) { |
| return current_context_; |
| } |
| auto *effective_context = current_context_->wasm()->getContext(effective_context_id_); |
| if (effective_context != nullptr) { |
| return effective_context; |
| } |
| // The effective_context_id_ no longer exists, revert to the true context. |
| return current_context_; |
| }; |
| |
| std::unordered_map<std::string, WasmForeignFunction> &foreignFunctions() { |
| static auto *ptr = new std::unordered_map<std::string, WasmForeignFunction>; |
| return *ptr; |
| } |
| |
| WasmForeignFunction getForeignFunction(std::string_view function_name) { |
| auto foreign_functions = foreignFunctions(); |
| auto it = foreign_functions.find(std::string(function_name)); |
| if (it != foreign_functions.end()) { |
| return it->second; |
| } |
| return nullptr; |
| } |
| |
| RegisterForeignFunction::RegisterForeignFunction(const std::string &name, WasmForeignFunction f) { |
| foreignFunctions()[name] = std::move(f); |
| } |
| |
| namespace exports { |
| |
| // General ABI. |
| |
| Word set_property(Word key_ptr, Word key_size, Word value_ptr, Word value_size) { |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| auto value = context->wasmVm()->getMemory(value_ptr, value_size); |
| if (!key || !value) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->setProperty(key.value(), value.value()); |
| } |
| |
| // Generic selector |
| Word get_property(Word path_ptr, Word path_size, Word value_ptr_ptr, Word value_size_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| auto path = context->wasmVm()->getMemory(path_ptr, path_size); |
| if (!path.has_value()) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| std::string value; |
| auto result = context->getProperty(path.value(), &value); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr, value_size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word get_configuration(Word value_ptr_ptr, Word value_size_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| auto value = context->getConfiguration(); |
| if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr, value_size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word get_status(Word code_ptr, Word value_ptr_ptr, Word value_size_ptr) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| auto status = context->getStatus(); |
| if (!context->wasm()->setDatatype(code_ptr, status.first)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!context->wasm()->copyToPointerSize(status.second, value_ptr_ptr, value_size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| // HTTP |
| |
| // Continue/Reply/Route |
| Word continue_request() { |
| auto *context = contextOrEffectiveContext(); |
| return context->continueStream(WasmStreamType::Request); |
| } |
| |
| Word continue_response() { |
| auto *context = contextOrEffectiveContext(); |
| return context->continueStream(WasmStreamType::Response); |
| } |
| |
| Word continue_stream(Word type) { |
| auto *context = contextOrEffectiveContext(); |
| if (type > static_cast<uint64_t>(WasmStreamType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| return context->continueStream(static_cast<WasmStreamType>(type.u64_)); |
| } |
| |
| Word close_stream(Word type) { |
| auto *context = contextOrEffectiveContext(); |
| if (type > static_cast<uint64_t>(WasmStreamType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| return context->closeStream(static_cast<WasmStreamType>(type.u64_)); |
| } |
| |
| Word send_local_response(Word response_code, Word response_code_details_ptr, |
| Word response_code_details_size, Word body_ptr, Word body_size, |
| Word additional_response_header_pairs_ptr, |
| Word additional_response_header_pairs_size, Word grpc_status) { |
| auto *context = contextOrEffectiveContext(); |
| auto details = |
| context->wasmVm()->getMemory(response_code_details_ptr, response_code_details_size); |
| auto body = context->wasmVm()->getMemory(body_ptr, body_size); |
| auto additional_response_header_pairs = context->wasmVm()->getMemory( |
| additional_response_header_pairs_ptr, additional_response_header_pairs_size); |
| if (!details || !body || !additional_response_header_pairs) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| auto additional_headers = PairsUtil::toPairs(additional_response_header_pairs.value()); |
| context->sendLocalResponse(response_code, body.value(), std::move(additional_headers), |
| grpc_status, details.value()); |
| context->wasm()->stopNextIteration(true); |
| return WasmResult::Ok; |
| } |
| |
| Word clear_route_cache() { |
| auto *context = contextOrEffectiveContext(); |
| context->clearRouteCache(); |
| return WasmResult::Ok; |
| } |
| |
| Word set_effective_context(Word context_id) { |
| auto *context = contextOrEffectiveContext(); |
| auto cid = static_cast<uint32_t>(context_id); |
| auto *c = context->wasm()->getContext(cid); |
| if (c == nullptr) { |
| return WasmResult::BadArgument; |
| } |
| effective_context_id_ = cid; |
| return WasmResult::Ok; |
| } |
| |
| Word done() { |
| auto *context = contextOrEffectiveContext(); |
| return context->wasm()->done(context); |
| } |
| |
| Word call_foreign_function(Word function_name, Word function_name_size, Word arguments, |
| Word arguments_size, Word results, Word results_size) { |
| auto *context = contextOrEffectiveContext(); |
| auto function = context->wasmVm()->getMemory(function_name, function_name_size); |
| if (!function) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| auto args_opt = context->wasmVm()->getMemory(arguments, arguments_size); |
| if (!args_opt) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| auto f = getForeignFunction(function.value()); |
| if (!f) { |
| return WasmResult::NotFound; |
| } |
| auto &wasm = *context->wasm(); |
| auto &args = args_opt.value(); |
| uint64_t address = 0; |
| void *result = nullptr; |
| size_t result_size = 0; |
| auto res = f(wasm, args, [&wasm, &address, &result, &result_size, results](size_t s) -> void * { |
| if (results != 0U) { |
| result = wasm.allocMemory(s, &address); |
| } else { |
| // If the caller does not want the results, allocate a temporary buffer for them. |
| result = ::malloc(s); |
| } |
| result_size = s; |
| return result; |
| }); |
| if (results != 0U && !context->wasmVm()->setWord(results, Word(address))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (results_size != 0U && !context->wasmVm()->setWord(results_size, Word(result_size))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (results == 0U) { |
| ::free(result); |
| } |
| return res; |
| } |
| |
| // SharedData |
| Word get_shared_data(Word key_ptr, Word key_size, Word value_ptr_ptr, Word value_size_ptr, |
| Word cas_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| if (!key) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| std::pair<std::string, uint32_t> data; |
| WasmResult result = context->getSharedData(key.value(), &data); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->copyToPointerSize(data.first, value_ptr_ptr, value_size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!context->wasmVm()->setMemory(cas_ptr, sizeof(uint32_t), &data.second)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word set_shared_data(Word key_ptr, Word key_size, Word value_ptr, Word value_size, Word cas) { |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| auto value = context->wasmVm()->getMemory(value_ptr, value_size); |
| if (!key || !value) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->setSharedData(key.value(), value.value(), cas); |
| } |
| |
| Word register_shared_queue(Word queue_name_ptr, Word queue_name_size, Word token_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| auto queue_name = context->wasmVm()->getMemory(queue_name_ptr, queue_name_size); |
| if (!queue_name) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| uint32_t token; |
| auto result = context->registerSharedQueue(queue_name.value(), &token); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->setDatatype(token_ptr, token)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word dequeue_shared_queue(Word token, Word data_ptr_ptr, Word data_size_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| std::string data; |
| WasmResult result = context->dequeueSharedQueue(token.u32(), &data); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->copyToPointerSize(data, data_ptr_ptr, data_size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word resolve_shared_queue(Word vm_id_ptr, Word vm_id_size, Word queue_name_ptr, |
| Word queue_name_size, Word token_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| auto vm_id = context->wasmVm()->getMemory(vm_id_ptr, vm_id_size); |
| auto queue_name = context->wasmVm()->getMemory(queue_name_ptr, queue_name_size); |
| if (!vm_id || !queue_name) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| uint32_t token = 0; |
| auto result = context->lookupSharedQueue(vm_id.value(), queue_name.value(), &token); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->setDatatype(token_ptr, token)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word enqueue_shared_queue(Word token, Word data_ptr, Word data_size) { |
| auto *context = contextOrEffectiveContext(); |
| auto data = context->wasmVm()->getMemory(data_ptr, data_size); |
| if (!data) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->enqueueSharedQueue(token.u32(), data.value()); |
| } |
| |
| // Header/Trailer/Metadata Maps |
| Word add_header_map_value(Word type, Word key_ptr, Word key_size, Word value_ptr, Word value_size) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| auto value = context->wasmVm()->getMemory(value_ptr, value_size); |
| if (!key || !value) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->addHeaderMapValue(static_cast<WasmHeaderMapType>(type.u64_), key.value(), |
| value.value()); |
| } |
| |
| Word get_header_map_value(Word type, Word key_ptr, Word key_size, Word value_ptr_ptr, |
| Word value_size_ptr) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| if (!key) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| std::string_view value; |
| auto result = |
| context->getHeaderMapValue(static_cast<WasmHeaderMapType>(type.u64_), key.value(), &value); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr, value_size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word replace_header_map_value(Word type, Word key_ptr, Word key_size, Word value_ptr, |
| Word value_size) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| auto value = context->wasmVm()->getMemory(value_ptr, value_size); |
| if (!key || !value) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->replaceHeaderMapValue(static_cast<WasmHeaderMapType>(type.u64_), key.value(), |
| value.value()); |
| } |
| |
| Word remove_header_map_value(Word type, Word key_ptr, Word key_size) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto key = context->wasmVm()->getMemory(key_ptr, key_size); |
| if (!key) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->removeHeaderMapValue(static_cast<WasmHeaderMapType>(type.u64_), key.value()); |
| } |
| |
| Word get_header_map_pairs(Word type, Word ptr_ptr, Word size_ptr) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| Pairs pairs; |
| auto result = context->getHeaderMapPairs(static_cast<WasmHeaderMapType>(type.u64_), &pairs); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (pairs.empty()) { |
| if (!context->wasm()->copyToPointerSize("", ptr_ptr, size_ptr)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| uint64_t size = PairsUtil::pairsSize(pairs); |
| uint64_t ptr = 0; |
| char *buffer = static_cast<char *>(context->wasm()->allocMemory(size, &ptr)); |
| if (buffer == nullptr) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!PairsUtil::marshalPairs(pairs, buffer, size)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!context->wasmVm()->setWord(ptr_ptr, Word(ptr))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!context->wasmVm()->setWord(size_ptr, Word(size))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word set_header_map_pairs(Word type, Word ptr, Word size) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto data = context->wasmVm()->getMemory(ptr, size); |
| if (!data) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->setHeaderMapPairs(static_cast<WasmHeaderMapType>(type.u64_), |
| PairsUtil::toPairs(data.value())); |
| } |
| |
| Word get_header_map_size(Word type, Word result_ptr) { |
| if (type > static_cast<uint64_t>(WasmHeaderMapType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| uint32_t size; |
| auto result = context->getHeaderMapSize(static_cast<WasmHeaderMapType>(type.u64_), &size); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasmVm()->setWord(result_ptr, Word(size))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| // Buffer |
| Word get_buffer_bytes(Word type, Word start, Word length, Word ptr_ptr, Word size_ptr) { |
| if (type > static_cast<uint64_t>(WasmBufferType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto *buffer = context->getBuffer(static_cast<WasmBufferType>(type.u64_)); |
| if (buffer == nullptr) { |
| return WasmResult::NotFound; |
| } |
| // Check for overflow. |
| if (start > start + length) { |
| return WasmResult::BadArgument; |
| } |
| // Don't overread. |
| if (start > buffer->size()) { |
| length = 0; |
| } else if (start + length > buffer->size()) { |
| length = buffer->size() - start; |
| } |
| if (length == 0) { |
| if (!context->wasmVm()->setWord(ptr_ptr, Word(0))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!context->wasmVm()->setWord(size_ptr, Word(0))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| return buffer->copyTo(context->wasm(), start, length, ptr_ptr, size_ptr); |
| } |
| |
| Word get_buffer_status(Word type, Word length_ptr, Word flags_ptr) { |
| if (type > static_cast<uint64_t>(WasmBufferType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto *buffer = context->getBuffer(static_cast<WasmBufferType>(type.u64_)); |
| if (buffer == nullptr) { |
| return WasmResult::NotFound; |
| } |
| auto length = buffer->size(); |
| uint32_t flags = 0; |
| if (!context->wasmVm()->setWord(length_ptr, Word(length))) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| if (!context->wasm()->setDatatype(flags_ptr, flags)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word set_buffer_bytes(Word type, Word start, Word length, Word data_ptr, Word data_size) { |
| if (type > static_cast<uint64_t>(WasmBufferType::MAX)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto *buffer = context->getBuffer(static_cast<WasmBufferType>(type.u64_)); |
| if (buffer == nullptr) { |
| return WasmResult::NotFound; |
| } |
| auto data = context->wasmVm()->getMemory(data_ptr, data_size); |
| if (!data) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return buffer->copyFrom(start, length, data.value()); |
| } |
| |
| Word http_call(Word uri_ptr, Word uri_size, Word header_pairs_ptr, Word header_pairs_size, |
| Word body_ptr, Word body_size, Word trailer_pairs_ptr, Word trailer_pairs_size, |
| Word timeout_milliseconds, Word token_ptr) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| auto uri = context->wasmVm()->getMemory(uri_ptr, uri_size); |
| auto body = context->wasmVm()->getMemory(body_ptr, body_size); |
| auto header_pairs = context->wasmVm()->getMemory(header_pairs_ptr, header_pairs_size); |
| auto trailer_pairs = context->wasmVm()->getMemory(trailer_pairs_ptr, trailer_pairs_size); |
| if (!uri || !body || !header_pairs || !trailer_pairs) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| auto headers = PairsUtil::toPairs(header_pairs.value()); |
| auto trailers = PairsUtil::toPairs(trailer_pairs.value()); |
| uint32_t token = 0; |
| // NB: try to write the token to verify the memory before starting the async |
| // operation. |
| if (!context->wasm()->setDatatype(token_ptr, token)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| auto result = |
| context->httpCall(uri.value(), headers, body.value(), trailers, timeout_milliseconds, &token); |
| context->wasm()->setDatatype(token_ptr, token); |
| return result; |
| } |
| |
| Word define_metric(Word metric_type, Word name_ptr, Word name_size, Word metric_id_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| auto name = context->wasmVm()->getMemory(name_ptr, name_size); |
| if (!name) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| uint32_t metric_id = 0; |
| auto result = |
| context->defineMetric(static_cast<uint32_t>(metric_type.u64_), name.value(), &metric_id); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->setDatatype(metric_id_ptr, metric_id)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word increment_metric(Word metric_id, int64_t offset) { |
| auto *context = contextOrEffectiveContext(); |
| return context->incrementMetric(metric_id, offset); |
| } |
| |
| Word record_metric(Word metric_id, uint64_t value) { |
| auto *context = contextOrEffectiveContext(); |
| return context->recordMetric(metric_id, value); |
| } |
| |
| Word get_metric(Word metric_id, Word result_uint64_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| uint64_t value = 0; |
| auto result = context->getMetric(metric_id, &value); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->setDatatype(result_uint64_ptr, value)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word grpc_call(Word service_ptr, Word service_size, Word service_name_ptr, Word service_name_size, |
| Word method_name_ptr, Word method_name_size, Word initial_metadata_ptr, |
| Word initial_metadata_size, Word request_ptr, Word request_size, |
| Word timeout_milliseconds, Word token_ptr) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| auto service = context->wasmVm()->getMemory(service_ptr, service_size); |
| auto service_name = context->wasmVm()->getMemory(service_name_ptr, service_name_size); |
| auto method_name = context->wasmVm()->getMemory(method_name_ptr, method_name_size); |
| auto initial_metadata_pairs = |
| context->wasmVm()->getMemory(initial_metadata_ptr, initial_metadata_size); |
| auto request = context->wasmVm()->getMemory(request_ptr, request_size); |
| if (!service || !service_name || !method_name || !initial_metadata_pairs || !request) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| uint32_t token = 0; |
| auto initial_metadata = PairsUtil::toPairs(initial_metadata_pairs.value()); |
| auto result = context->grpcCall(service.value(), service_name.value(), method_name.value(), |
| initial_metadata, request.value(), |
| std::chrono::milliseconds(timeout_milliseconds), &token); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->setDatatype(token_ptr, token)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word grpc_stream(Word service_ptr, Word service_size, Word service_name_ptr, Word service_name_size, |
| Word method_name_ptr, Word method_name_size, Word initial_metadata_ptr, |
| Word initial_metadata_size, Word token_ptr) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| auto service = context->wasmVm()->getMemory(service_ptr, service_size); |
| auto service_name = context->wasmVm()->getMemory(service_name_ptr, service_name_size); |
| auto method_name = context->wasmVm()->getMemory(method_name_ptr, method_name_size); |
| auto initial_metadata_pairs = |
| context->wasmVm()->getMemory(initial_metadata_ptr, initial_metadata_size); |
| if (!service || !service_name || !method_name || !initial_metadata_pairs) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| uint32_t token = 0; |
| auto initial_metadata = PairsUtil::toPairs(initial_metadata_pairs.value()); |
| auto result = context->grpcStream(service.value(), service_name.value(), method_name.value(), |
| initial_metadata, &token); |
| if (result != WasmResult::Ok) { |
| return result; |
| } |
| if (!context->wasm()->setDatatype(token_ptr, token)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word grpc_cancel(Word token) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| return context->grpcCancel(token); |
| } |
| |
| Word grpc_close(Word token) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| return context->grpcClose(token); |
| } |
| |
| Word grpc_send(Word token, Word message_ptr, Word message_size, Word end_stream) { |
| auto *context = contextOrEffectiveContext()->root_context(); |
| auto message = context->wasmVm()->getMemory(message_ptr, message_size); |
| if (!message) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->grpcSend(token, message.value(), end_stream != 0U); |
| } |
| |
| // WASIp1 typings in comments sourced from |
| // https://github.com/WebAssembly/wasi-libc/blob/446cb3f1aa21f9b1a1eab372f82d65d19003e924/libc-bottom-half/headers/public/wasi/api.h |
| |
| // __wasi_errno_t path_open(__wasi_fd_t fd, __wasi_lookupflags_t dirflags, const char *path, |
| // size_t path_len, __wasi_oflags_t oflags, __wasi_rights_t fs_rights_base, __wasi_rights_t |
| // fs_rights_inheriting, __wasi_fdflags_t fdflags, __wasi_fd_t *retptr0) |
| Word wasi_unstable_path_open(Word /*fd*/, Word /*dir_flags*/, Word /*path*/, Word /*path_len*/, |
| Word /*oflags*/, int64_t /*fs_rights_base*/, |
| int64_t /*fg_rights_inheriting*/, Word /*fd_flags*/, |
| Word /*nwritten_ptr*/) { |
| return 44; // __WASI_ERRNO_NOENT |
| } |
| |
| // __wasi_errno_t __wasi_fd_prestat_get(__wasi_fd_t fd, __wasi_prestat_t *retptr0) |
| Word wasi_unstable_fd_prestat_get(Word /*fd*/, Word /*buf_ptr*/) { |
| return 8; // __WASI_ERRNO_BADF |
| } |
| |
| // __wasi_errno_t __wasi_fd_prestat_dir_name(__wasi_fd_t fd, uint8_t * path, __wasi_size_t path_len) |
| Word wasi_unstable_fd_prestat_dir_name(Word /*fd*/, Word /*path_ptr*/, Word /*path_len*/) { |
| return 52; // __WASI_ERRNO_ENOSYS |
| } |
| |
| // Implementation of writev-like() syscall that redirects stdout/stderr to Envoy |
| // logs. |
| Word writevImpl(Word fd, Word iovs, Word iovs_len, Word *nwritten_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| |
| // Read syscall args. |
| uint64_t log_level; |
| switch (fd) { |
| case 1 /* stdout */: |
| log_level = 2; // LogLevel::info |
| break; |
| case 2 /* stderr */: |
| log_level = 4; // LogLevel::error |
| break; |
| default: |
| return 8; // __WASI_EBADF |
| } |
| |
| std::string s; |
| for (size_t i = 0; i < iovs_len; i++) { |
| auto memslice = |
| context->wasmVm()->getMemory(iovs + i * 2 * sizeof(uint32_t), 2 * sizeof(uint32_t)); |
| if (!memslice) { |
| return 21; // __WASI_EFAULT |
| } |
| const auto *iovec = reinterpret_cast<const uint32_t *>(memslice.value().data()); |
| if (iovec[1] != 0U /* buf_len */) { |
| const auto buf = wasmtoh(iovec[0], context->wasmVm()->usesWasmByteOrder()); |
| const auto buf_len = wasmtoh(iovec[1], context->wasmVm()->usesWasmByteOrder()); |
| memslice = context->wasmVm()->getMemory(buf, buf_len); |
| if (!memslice) { |
| return 21; // __WASI_EFAULT |
| } |
| s.append(memslice.value().data(), memslice.value().size()); |
| } |
| } |
| |
| size_t written = s.size(); |
| if (written != 0U) { |
| // Remove trailing newline from the logs, if any. |
| if (s[written - 1] == '\n') { |
| s.erase(written - 1); |
| } |
| if (context->log(log_level, s) != WasmResult::Ok) { |
| return 8; // __WASI_EBADF |
| } |
| } |
| *nwritten_ptr = Word(written); |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_fd_write(_wasi_fd_t fd, const _wasi_ciovec_t *iov, |
| // size_t iovs_len, size_t* nwritten); |
| Word wasi_unstable_fd_write(Word fd, Word iovs, Word iovs_len, Word nwritten_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| |
| Word nwritten(0); |
| auto result = writevImpl(fd, iovs, iovs_len, &nwritten); |
| if (result != 0) { // __WASI_ESUCCESS |
| return result; |
| } |
| if (!context->wasmVm()->setWord(nwritten_ptr, Word(nwritten))) { |
| return 21; // __WASI_EFAULT |
| } |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_fd_read(_wasi_fd_t fd, const __wasi_iovec_t *iovs, |
| // size_t iovs_len, __wasi_size_t *nread); |
| Word wasi_unstable_fd_read(Word /*fd*/, Word /*iovs_ptr*/, Word /*iovs_len*/, Word /*nread_ptr*/) { |
| // Don't support reading of any files. |
| return 52; // __WASI_ERRNO_ENOSYS |
| } |
| |
| // __wasi_errno_t __wasi_fd_seek(__wasi_fd_t fd, __wasi_filedelta_t offset, |
| // __wasi_whence_t whence,__wasi_filesize_t *newoffset); |
| Word wasi_unstable_fd_seek(Word /*fd*/, int64_t /*offset*/, Word /*whence*/, |
| Word /*newoffset_ptr*/) { |
| auto *context = contextOrEffectiveContext(); |
| context->error("wasi_unstable fd_seek"); |
| return 0; |
| } |
| |
| // __wasi_errno_t __wasi_fd_close(__wasi_fd_t fd); |
| Word wasi_unstable_fd_close(Word /*fd*/) { |
| auto *context = contextOrEffectiveContext(); |
| context->error("wasi_unstable fd_close"); |
| return 0; |
| } |
| |
| // __wasi_errno_t __wasi_fd_fdstat_get(__wasi_fd_t fd, __wasi_fdstat_t *stat) |
| Word wasi_unstable_fd_fdstat_get(Word fd, Word statOut) { |
| // We will only support this interface on stdout and stderr |
| if (fd != 1 && fd != 2) { |
| return 8; // __WASI_EBADF; |
| } |
| |
| // The last word points to a 24-byte structure, which we |
| // are mostly going to zero out. |
| uint64_t wasi_fdstat[3]; |
| wasi_fdstat[0] = 0; |
| wasi_fdstat[1] = 64; // This sets "fs_rights_base" to __WASI_RIGHTS_FD_WRITE |
| wasi_fdstat[2] = 0; |
| |
| auto *context = contextOrEffectiveContext(); |
| context->wasmVm()->setMemory(statOut, 3 * sizeof(uint64_t), &wasi_fdstat); |
| |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_fd_fdstat_set_flags(__wasi_fd_t fd, __wasi_fdflags_t flags) |
| Word wasi_unstable_fd_fdstat_set_flags(Word /*fd*/, Word /*flags*/) { |
| // Flags that can be specified: append, dsync, nonblock, rsync, and sync. Proxy-wasm only supports |
| // STDOUT and STDERR, but none of these flags have any effect in Proxy-Wasm. |
| return 52; // __WASI_ERRNO_ENOSYS |
| } |
| |
| // __wasi_errno_t __wasi_environ_get(char **environ, char *environ_buf); |
| Word wasi_unstable_environ_get(Word environ_array_ptr, Word environ_buf) { |
| auto *context = contextOrEffectiveContext(); |
| auto word_size = context->wasmVm()->getWordSize(); |
| const auto &envs = context->wasm()->envs(); |
| for (const auto &e : envs) { |
| if (!context->wasmVm()->setWord(environ_array_ptr, environ_buf)) { |
| return 21; // __WASI_EFAULT |
| } |
| |
| std::string data; |
| data.reserve(e.first.size() + e.second.size() + 2); |
| data.append(e.first); |
| data.append("="); |
| data.append(e.second); |
| data.append({0x0}); |
| if (!context->wasmVm()->setMemory(environ_buf, data.size(), data.c_str())) { |
| return 21; // __WASI_EFAULT |
| } |
| environ_buf = environ_buf.u64_ + data.size(); |
| environ_array_ptr = environ_array_ptr.u64_ + word_size; |
| } |
| |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_environ_sizes_get(size_t *environ_count, size_t |
| // *environ_buf_size); |
| Word wasi_unstable_environ_sizes_get(Word count_ptr, Word buf_size_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| const auto &envs = context->wasm()->envs(); |
| if (!context->wasmVm()->setWord(count_ptr, Word(envs.size()))) { |
| return 21; // __WASI_EFAULT |
| } |
| |
| size_t size = 0; |
| for (const auto &e : envs) { |
| // len(key) + len(value) + 1('=') + 1(null terminator) |
| size += e.first.size() + e.second.size() + 2; |
| } |
| if (!context->wasmVm()->setWord(buf_size_ptr, Word(size))) { |
| return 21; // __WASI_EFAULT |
| } |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_args_get(uint8_t **argv, uint8_t *argv_buf); |
| Word wasi_unstable_args_get(Word /*argv_array_ptr*/, Word /*argv_buf_ptr*/) { |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_args_sizes_get(size_t *argc, size_t *argv_buf_size); |
| Word wasi_unstable_args_sizes_get(Word argc_ptr, Word argv_buf_size_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| if (!context->wasmVm()->setWord(argc_ptr, Word(0))) { |
| return 21; // __WASI_EFAULT |
| } |
| if (!context->wasmVm()->setWord(argv_buf_size_ptr, Word(0))) { |
| return 21; // __WASI_EFAULT |
| } |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_clock_time_get(uint32_t id, uint64_t precision, uint64_t* time); |
| Word wasi_unstable_clock_time_get(Word clock_id, uint64_t /*precision*/, |
| Word result_time_uint64_ptr) { |
| |
| uint64_t result = 0; |
| auto *context = contextOrEffectiveContext(); |
| switch (clock_id) { |
| case 0 /* realtime */: |
| result = context->getCurrentTimeNanoseconds(); |
| break; |
| case 1 /* monotonic */: |
| result = context->getMonotonicTimeNanoseconds(); |
| break; |
| default: |
| // process_cputime_id and thread_cputime_id are not supported yet. |
| return 58; // __WASI_ENOTSUP |
| } |
| if (!context->wasm()->setDatatype(result_time_uint64_ptr, result)) { |
| return 21; // __WASI_EFAULT |
| } |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_random_get(uint8_t *buf, size_t buf_len); |
| Word wasi_unstable_random_get(Word result_buf_ptr, Word buf_len) { |
| if (buf_len > PROXY_WASM_HOST_WASI_RANDOM_GET_MAX_SIZE_BYTES) { |
| return 28; // __WASI_EINVAL |
| } |
| if (buf_len == 0) { |
| return 0; // __WASI_ESUCCESS |
| } |
| auto *context = contextOrEffectiveContext(); |
| std::vector<uint8_t> random(buf_len); |
| RAND_bytes(random.data(), random.size()); |
| if (!context->wasmVm()->setMemory(result_buf_ptr, random.size(), random.data())) { |
| return 21; // __WASI_EFAULT |
| } |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_sched_yield() |
| Word wasi_unstable_sched_yield() { |
| // Per POSIX man pages, it is valid to return success if the calling thread is the only thread in |
| // the highest priority list. This is vacuously true for wasm without threads. There are no valid |
| // error cases defined. |
| return 0; // __WASI_ESUCCESS |
| } |
| |
| // __wasi_errno_t __wasi_poll_oneoff(const __wasi_subscription_t *in, __wasi_event_t *out, |
| // __wasi_size_t nsubscriptions, __wasi_size_t *nevents) |
| Word wasi_unstable_poll_oneoff(Word /*in*/, Word /*out*/, Word /*nsubscriptions*/, |
| Word /*nevents_ptr*/) { |
| return 52; // __WASI_ERRNO_ENOSYS |
| } |
| |
| // void __wasi_proc_exit(__wasi_exitcode_t rval); |
| void wasi_unstable_proc_exit(Word /*exit_code*/) { |
| auto *context = contextOrEffectiveContext(); |
| context->error("wasi_unstable proc_exit"); |
| } |
| |
| Word pthread_equal(Word left, Word right) { return static_cast<uint64_t>(left == right); } |
| |
| void emscripten_notify_memory_growth(Word /*memory_index*/) {} |
| |
| Word set_tick_period_milliseconds(Word period_milliseconds) { |
| TimerToken token = 0; |
| return contextOrEffectiveContext()->setTimerPeriod(std::chrono::milliseconds(period_milliseconds), |
| &token); |
| } |
| |
| Word get_current_time_nanoseconds(Word result_uint64_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| uint64_t result = context->getCurrentTimeNanoseconds(); |
| if (!context->wasm()->setDatatype(result_uint64_ptr, result)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| Word log(Word level, Word address, Word size) { |
| if (level > static_cast<uint64_t>(LogLevel::Max)) { |
| return WasmResult::BadArgument; |
| } |
| auto *context = contextOrEffectiveContext(); |
| auto message = context->wasmVm()->getMemory(address, size); |
| if (!message) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return context->log(level, message.value()); |
| } |
| |
| Word get_log_level(Word result_level_uint32_ptr) { |
| auto *context = contextOrEffectiveContext(); |
| uint32_t level = context->getLogLevel(); |
| if (!context->wasm()->setDatatype(result_level_uint32_ptr, level)) { |
| return WasmResult::InvalidMemoryAccess; |
| } |
| return WasmResult::Ok; |
| } |
| |
| } // namespace exports |
| } // namespace proxy_wasm |