This commit is contained in:
Maik Riechert 2021-09-15 12:25:16 +01:00 коммит произвёл GitHub
Родитель 169345ebfa
Коммит e93d2f0beb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 814 добавлений и 668 удалений

Просмотреть файл

@ -348,12 +348,80 @@ set(CCF_NETWORK_TEST_ARGS -l ${TEST_HOST_LOGGING_LEVEL} --worker-threads
${WORKER_THREADS}
)
if("sgx" IN_LIST COMPILE_TARGETS)
add_enclave_library(js_openenclave.enclave ${CCF_DIR}/src/js/openenclave.cpp)
use_oe_mbedtls(js_openenclave.enclave)
target_link_libraries(js_openenclave.enclave PUBLIC ccf.enclave)
add_lvi_mitigations(js_openenclave.enclave)
install(
TARGETS js_openenclave.enclave
EXPORT ccf
DESTINATION lib
)
endif()
if("virtual" IN_LIST COMPILE_TARGETS)
add_library(js_openenclave.virtual STATIC ${CCF_DIR}/src/js/openenclave.cpp)
add_san(js_openenclave.virtual)
target_link_libraries(js_openenclave.virtual PUBLIC ccf.virtual)
target_compile_options(js_openenclave.virtual PRIVATE ${COMPILE_LIBCXX})
target_compile_definitions(
js_openenclave.virtual PUBLIC INSIDE_ENCLAVE VIRTUAL_ENCLAVE
_LIBCPP_HAS_THREAD_API_PTHREAD
)
set_property(
TARGET js_openenclave.virtual PROPERTY POSITION_INDEPENDENT_CODE ON
)
use_client_mbedtls(js_openenclave.virtual)
install(
TARGETS js_openenclave.virtual
EXPORT ccf
DESTINATION lib
)
endif()
if("sgx" IN_LIST COMPILE_TARGETS)
add_enclave_library(
js_generic_base.enclave ${CCF_DIR}/src/apps/js_generic/js_generic_base.cpp
)
use_oe_mbedtls(js_generic_base.enclave)
target_link_libraries(js_generic_base.enclave PUBLIC ccf.enclave)
add_lvi_mitigations(js_generic_base.enclave)
install(
TARGETS js_generic_base.enclave
EXPORT ccf
DESTINATION lib
)
endif()
if("virtual" IN_LIST COMPILE_TARGETS)
add_library(
js_generic_base.virtual STATIC
${CCF_DIR}/src/apps/js_generic/js_generic_base.cpp
)
add_san(js_generic_base.virtual)
add_warning_checks(js_generic_base.virtual)
target_link_libraries(js_generic_base.virtual PUBLIC ccf.virtual)
target_compile_options(js_generic_base.virtual PRIVATE ${COMPILE_LIBCXX})
target_compile_definitions(
js_generic_base.virtual PUBLIC INSIDE_ENCLAVE VIRTUAL_ENCLAVE
_LIBCPP_HAS_THREAD_API_PTHREAD
)
set_property(
TARGET js_generic_base.virtual PROPERTY POSITION_INDEPENDENT_CODE ON
)
use_client_mbedtls(js_generic_base.virtual)
install(
TARGETS js_generic_base.virtual
EXPORT ccf
DESTINATION lib
)
endif()
# SNIPPET_START: JS generic application
add_ccf_app(
js_generic
SRCS ${CCF_DIR}/src/apps/js_generic/js_generic.cpp
LINK_LIBS_ENCLAVE quickjs.enclave -lgcc
LINK_LIBS_VIRTUAL quickjs.host INSTALL_LIBS ON
LINK_LIBS_ENCLAVE js_generic_base.enclave js_openenclave.enclave
LINK_LIBS_VIRTUAL js_generic_base.virtual js_openenclave.virtual INSTALL_LIBS
ON
)
sign_app_library(
js_generic.enclave ${CCF_DIR}/src/apps/js_generic/oe_sign.conf

Просмотреть файл

@ -5,6 +5,7 @@ A CCF application is composed of the following:
- The :ref:`Application Entry Point <build_apps/api:Application Entry Point>` which registers the application in CCF.
- A collection of :cpp:class:`endpoints <ccf::endpoints::Endpoint>` handling HTTP requests and grouped in a single :cpp:class:`registry <ccf::endpoints::EndpointRegistry>`. An :cpp:class:`endpoint <ccf::endpoints::Endpoint>` reads and writes to the key-value store via the :ref:`Key-Value Store API <build_apps/kv/api:Key-Value Store API>`.
- An optional set of :ref:`JavaScript FFI Plugins <build_apps/api:JavaScript FFI Plugins>` that can be registered to extend the built-in JavaScript API surface.
Application Entry Point
-----------------------
@ -96,3 +97,9 @@ Historical Queries
.. doxygenstruct:: ccf::historical::State
:project: CCF
:members:
JavaScript FFI Plugins
----------------------
.. doxygenfunction:: ccfapp::get_js_plugins
:project: CCF

Просмотреть файл

@ -5,16 +5,16 @@ A C++ application exposes itself to CCF by implementing:
.. literalinclude:: ../../include/ccf/app_interface.h
:language: cpp
:start-after: SNIPPET_START: rpc_handler
:end-before: SNIPPET_END: rpc_handler
:start-after: SNIPPET_START: app_interface
:end-before: SNIPPET_END: app_interface
:dedent:
The Logging application simply has:
.. literalinclude:: ../../samples/apps/logging/logging.cpp
:language: cpp
:start-after: SNIPPET_START: rpc_handler
:end-before: SNIPPET_END: rpc_handler
:start-after: SNIPPET_START: app_interface
:end-before: SNIPPET_END: app_interface
:dedent:
.. note::

Просмотреть файл

@ -2,7 +2,10 @@
// Licensed under the Apache 2.0 License.
#pragma once
#include "js_plugin.h"
#include <memory>
#include <vector>
namespace ccf
{
@ -17,7 +20,7 @@ namespace ccfapp
// Forward declaration
struct AbstractNodeContext;
// SNIPPET_START: rpc_handler
// SNIPPET_START: app_interface
/** To be implemented by the application to be registered by CCF.
*
* @param network Access to the network's replicated tables
@ -27,5 +30,11 @@ namespace ccfapp
*/
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& network, AbstractNodeContext& context);
// SNIPPET_END: rpc_handler
/** To be implemented by the application to be registered by CCF.
*
* @return Vector of JavaScript FFI plugins
*/
std::vector<ccf::js::FFIPlugin> get_js_plugins();
// SNIPPET_END: app_interface
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/js_plugin.h"
namespace ccf::js
{
extern FFIPlugin openenclave_plugin;
}

Просмотреть файл

@ -6,7 +6,7 @@
#include <quickjs/quickjs.h>
#include <string>
namespace js
namespace ccf::js
{
struct FFIPlugin
{

Просмотреть файл

@ -1198,11 +1198,11 @@ namespace loggingapp
namespace ccfapp
{
// SNIPPET_START: rpc_handler
// SNIPPET_START: app_interface
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& nwt, ccfapp::AbstractNodeContext& context)
{
return make_shared<loggingapp::Logger>(nwt, context);
}
// SNIPPET_END: rpc_handler
// SNIPPET_END: app_interface
}

Просмотреть файл

@ -1,653 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "apps/utils/metrics_tracker.h"
#include "ccf/app_interface.h"
#include "ccf/historical_queries_adapter.h"
#include "ccf/user_frontend.h"
#include "ccf/version.h"
#include "crypto/entropy.h"
#include "crypto/key_wrap.h"
#include "crypto/rsa_key_pair.h"
#include "js/wrap.h"
#include "kv/untyped_map.h"
#include "named_auth_policies.h"
#include <memory>
#include <quickjs/quickjs-exports.h>
#include <quickjs/quickjs.h>
#include <stdexcept>
#include <vector>
#include "ccf/js_openenclave_plugin.h"
#include "js_generic_base.h"
namespace ccfapp
{
using namespace std;
using namespace kv;
using namespace ccf;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
class JSHandlers : public UserEndpointRegistry
{
private:
NetworkTables& network;
ccfapp::AbstractNodeContext& context;
metrics::Tracker metrics_tracker;
static JSValue create_json_obj(const nlohmann::json& j, JSContext* ctx)
{
const auto buf = j.dump();
return JS_ParseJSON(ctx, buf.data(), buf.size(), "<json>");
}
JSValue create_caller_obj(
ccf::endpoints::EndpointContext& endpoint_ctx, JSContext* ctx)
{
if (endpoint_ctx.caller == nullptr)
{
return JS_NULL;
}
auto caller = JS_NewObject(ctx);
if (auto jwt_ident = endpoint_ctx.try_get_caller<ccf::JwtAuthnIdentity>())
{
JS_SetPropertyStr(
ctx,
caller,
"policy",
JS_NewString(ctx, get_policy_name_from_ident(jwt_ident)));
auto jwt = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
jwt,
"keyIssuer",
JS_NewStringLen(
ctx, jwt_ident->key_issuer.data(), jwt_ident->key_issuer.size()));
JS_SetPropertyStr(
ctx, jwt, "header", create_json_obj(jwt_ident->header, ctx));
JS_SetPropertyStr(
ctx, jwt, "payload", create_json_obj(jwt_ident->payload, ctx));
JS_SetPropertyStr(ctx, caller, "jwt", jwt);
return caller;
}
else if (
auto empty_ident =
endpoint_ctx.try_get_caller<ccf::EmptyAuthnIdentity>())
{
JS_SetPropertyStr(
ctx,
caller,
"policy",
JS_NewString(ctx, get_policy_name_from_ident(empty_ident)));
return caller;
}
char const* policy_name = nullptr;
std::string id;
bool is_member = false;
if (
auto user_cert_ident =
endpoint_ctx.try_get_caller<ccf::UserCertAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(user_cert_ident);
id = user_cert_ident->user_id;
is_member = false;
}
else if (
auto member_cert_ident =
endpoint_ctx.try_get_caller<ccf::MemberCertAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(member_cert_ident);
id = member_cert_ident->member_id;
is_member = true;
}
else if (
auto user_sig_ident =
endpoint_ctx.try_get_caller<ccf::UserSignatureAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(user_sig_ident);
id = user_sig_ident->user_id;
is_member = false;
}
else if (
auto member_sig_ident =
endpoint_ctx.try_get_caller<ccf::MemberSignatureAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(member_sig_ident);
id = member_sig_ident->member_id;
is_member = true;
}
if (policy_name == nullptr)
{
throw std::logic_error("Unable to convert caller info to JS object");
}
// Retrieve user/member data from authenticated caller id
nlohmann::json data = nullptr;
ccf::ApiResult result = ccf::ApiResult::OK;
if (is_member)
{
result = get_member_data_v1(endpoint_ctx.tx, id, data);
}
else
{
result = get_user_data_v1(endpoint_ctx.tx, id, data);
}
if (result == ccf::ApiResult::InternalError)
{
throw std::logic_error(
fmt::format("Failed to get data for caller {}", id));
}
crypto::Pem cert;
if (is_member)
{
result = get_member_cert_v1(endpoint_ctx.tx, id, cert);
}
else
{
result = get_user_cert_v1(endpoint_ctx.tx, id, cert);
}
if (result == ccf::ApiResult::InternalError)
{
throw std::logic_error(
fmt::format("Failed to get certificate for caller {}", id));
}
JS_SetPropertyStr(ctx, caller, "policy", JS_NewString(ctx, policy_name));
JS_SetPropertyStr(
ctx, caller, "id", JS_NewStringLen(ctx, id.data(), id.size()));
JS_SetPropertyStr(ctx, caller, "data", create_json_obj(data, ctx));
JS_SetPropertyStr(
ctx,
caller,
"cert",
JS_NewStringLen(ctx, cert.str().data(), cert.size()));
return caller;
}
JSValue create_request_obj(
ccf::endpoints::EndpointContext& endpoint_ctx, JSContext* ctx)
{
auto request = JS_NewObject(ctx);
auto headers = JS_NewObject(ctx);
for (auto& [header_name, header_value] :
endpoint_ctx.rpc_ctx->get_request_headers())
{
JS_SetPropertyStr(
ctx,
headers,
header_name.c_str(),
JS_NewStringLen(ctx, header_value.c_str(), header_value.size()));
}
JS_SetPropertyStr(ctx, request, "headers", headers);
const auto& request_query = endpoint_ctx.rpc_ctx->get_request_query();
auto query_str =
JS_NewStringLen(ctx, request_query.c_str(), request_query.size());
JS_SetPropertyStr(ctx, request, "query", query_str);
auto params = JS_NewObject(ctx);
for (auto& [param_name, param_value] :
endpoint_ctx.rpc_ctx->get_request_path_params())
{
JS_SetPropertyStr(
ctx,
params,
param_name.c_str(),
JS_NewStringLen(ctx, param_value.c_str(), param_value.size()));
}
JS_SetPropertyStr(ctx, request, "params", params);
const auto& request_body = endpoint_ctx.rpc_ctx->get_request_body();
auto body_ = JS_NewObjectClass(ctx, js::body_class_id);
JS_SetOpaque(body_, (void*)&request_body);
JS_SetPropertyStr(ctx, request, "body", body_);
JS_SetPropertyStr(
ctx, request, "caller", create_caller_obj(endpoint_ctx, ctx));
return request;
}
void execute_request(
const ccf::endpoints::EndpointProperties& props,
ccf::endpoints::EndpointContext& endpoint_ctx)
{
if (props.mode == ccf::endpoints::Mode::Historical)
{
auto is_tx_committed =
[this](ccf::View view, ccf::SeqNo seqno, std::string& error_reason) {
return ccf::historical::is_tx_committed_v2(
consensus, view, seqno, error_reason);
};
ccf::historical::adapter_v2(
[this, &props](
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr state) {
auto tx = state->store->create_tx();
auto tx_id = state->transaction_id;
auto receipt = state->receipt;
assert(receipt);
do_execute_request(props, endpoint_ctx, tx, tx_id, receipt);
},
context.get_historical_state(),
is_tx_committed)(endpoint_ctx);
}
else
{
do_execute_request(
props, endpoint_ctx, endpoint_ctx.tx, std::nullopt, nullptr);
}
}
void do_execute_request(
const ccf::endpoints::EndpointProperties& props,
ccf::endpoints::EndpointContext& endpoint_ctx,
kv::Tx& target_tx,
const std::optional<ccf::TxID>& transaction_id,
ccf::historical::TxReceiptPtr receipt)
{
js::Runtime rt;
rt.add_ccf_classdefs();
JS_SetModuleLoaderFunc(
rt, nullptr, js::js_app_module_loader, &endpoint_ctx.tx);
js::Context ctx(rt);
js::TxContext txctx{&target_tx, js::TxAccess::APP};
js::register_request_body_class(ctx);
js::populate_global(
&txctx,
endpoint_ctx.rpc_ctx.get(),
transaction_id,
receipt,
nullptr,
&context.get_node_state(),
nullptr,
ctx);
JSValue export_func;
try
{
auto module_val =
js::load_app_module(ctx, props.js_module.c_str(), &endpoint_ctx.tx);
export_func =
ctx.function(module_val, props.js_function, props.js_module);
}
catch (const std::exception& exc)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
exc.what());
return;
}
// Call exported function
auto request = create_request_obj(endpoint_ctx, ctx);
int argc = 1;
JSValueConst* argv = (JSValueConst*)&request;
auto val = ctx(JS_Call(ctx, export_func, JS_UNDEFINED, argc, argv));
JS_FreeValue(ctx, request);
JS_FreeValue(ctx, export_func);
if (JS_IsException(val))
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Exception thrown while executing.");
return;
}
// Handle return value: {body, headers, statusCode}
if (!JS_IsObject(val))
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (not an object).");
return;
}
// Response body (also sets a default response content-type header)
{
auto response_body_js = ctx(JS_GetPropertyStr(ctx, val, "body"));
if (!JS_IsUndefined(response_body_js))
{
std::vector<uint8_t> response_body;
size_t buf_size;
size_t buf_offset;
JSValue typed_array_buffer = JS_GetTypedArrayBuffer(
ctx, response_body_js, &buf_offset, &buf_size, nullptr);
uint8_t* array_buffer;
if (!JS_IsException(typed_array_buffer))
{
size_t buf_size_total;
array_buffer =
JS_GetArrayBuffer(ctx, &buf_size_total, typed_array_buffer);
array_buffer += buf_offset;
JS_FreeValue(ctx, typed_array_buffer);
}
else
{
array_buffer = JS_GetArrayBuffer(ctx, &buf_size, response_body_js);
}
if (array_buffer)
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::OCTET_STREAM);
response_body =
std::vector<uint8_t>(array_buffer, array_buffer + buf_size);
}
else
{
const char* cstr = nullptr;
if (JS_IsString(response_body_js))
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::TEXT);
cstr = JS_ToCString(ctx, response_body_js);
}
else
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::JSON);
JSValue rval =
JS_JSONStringify(ctx, response_body_js, JS_NULL, JS_NULL);
if (JS_IsException(rval))
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (error during JSON "
"conversion of body).");
return;
}
cstr = JS_ToCString(ctx, rval);
JS_FreeValue(ctx, rval);
}
if (!cstr)
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (error during string "
"conversion of body).");
return;
}
std::string str(cstr);
JS_FreeCString(ctx, cstr);
response_body = std::vector<uint8_t>(str.begin(), str.end());
}
endpoint_ctx.rpc_ctx->set_response_body(std::move(response_body));
}
}
// Response headers
{
auto response_headers_js = ctx(JS_GetPropertyStr(ctx, val, "headers"));
if (JS_IsObject(response_headers_js))
{
uint32_t prop_count = 0;
JSPropertyEnum* props = nullptr;
JS_GetOwnPropertyNames(
ctx,
&props,
&prop_count,
response_headers_js,
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY);
for (size_t i = 0; i < prop_count; i++)
{
auto prop_name = props[i].atom;
auto prop_name_cstr = ctx(JS_AtomToCString(ctx, prop_name));
auto prop_val =
ctx(JS_GetProperty(ctx, response_headers_js, prop_name));
auto prop_val_cstr = JS_ToCString(ctx, prop_val);
if (!prop_val_cstr)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (header value type).");
return;
}
endpoint_ctx.rpc_ctx->set_response_header(
prop_name_cstr, prop_val_cstr);
JS_FreeCString(ctx, prop_val_cstr);
}
js_free(ctx, props);
}
}
// Response status code
{
int response_status_code = HTTP_STATUS_OK;
auto status_code_js = ctx(JS_GetPropertyStr(ctx, val, "statusCode"));
if (!JS_IsUndefined(status_code_js) && !JS_IsNull(status_code_js))
{
if (JS_VALUE_GET_TAG(status_code_js.val) != JS_TAG_INT)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (status code type).");
return;
}
response_status_code = JS_VALUE_GET_INT(status_code_js.val);
}
endpoint_ctx.rpc_ctx->set_response_status(response_status_code);
}
return;
}
struct JSDynamicEndpoint : public ccf::endpoints::EndpointDefinition
{};
public:
JSHandlers(NetworkTables& network, AbstractNodeContext& context) :
UserEndpointRegistry(context),
network(network),
context(context)
{
metrics_tracker.install_endpoint(*this);
}
void instantiate_authn_policies(JSDynamicEndpoint& endpoint)
{
for (const auto& policy_name : endpoint.properties.authn_policies)
{
auto policy = get_policy_by_name(policy_name);
if (policy == nullptr)
{
throw std::logic_error(
fmt::format("Unknown auth policy: {}", policy_name));
}
endpoint.authn_policies.push_back(std::move(policy));
}
}
ccf::endpoints::EndpointDefinitionPtr find_endpoint(
kv::Tx& tx, enclave::RpcContext& rpc_ctx) override
{
const auto method = rpc_ctx.get_method();
const auto verb = rpc_ctx.get_request_verb();
auto endpoints =
tx.ro<ccf::endpoints::EndpointsMap>(ccf::Tables::ENDPOINTS);
const auto key = ccf::endpoints::EndpointKey{method, verb};
// Look for a direct match of the given path
const auto it = endpoints->get(key);
if (it.has_value())
{
auto endpoint_def = std::make_shared<JSDynamicEndpoint>();
endpoint_def->dispatch = key;
endpoint_def->properties = it.value();
instantiate_authn_policies(*endpoint_def);
return endpoint_def;
}
// If that doesn't exist, look through _all_ the endpoints to find
// templated matches. If there is one, that's a match. More is an error,
// none means delegate to the base class.
{
std::vector<ccf::endpoints::EndpointDefinitionPtr> matches;
endpoints->foreach_key(
[this, &endpoints, &matches, &key, &rpc_ctx](const auto& other_key) {
if (key.verb == other_key.verb)
{
const auto opt_spec =
ccf::endpoints::parse_path_template(other_key.uri_path);
if (opt_spec.has_value())
{
const auto& template_spec = opt_spec.value();
// This endpoint has templates in its path, and the correct verb
// - now check if template matches the current request's path
std::smatch match;
if (std::regex_match(
key.uri_path, match, template_spec.template_regex))
{
if (matches.empty())
{
// Populate the request_path_params while we have the match,
// though this will be discarded on error if we later find
// multiple matches
auto& path_params = rpc_ctx.get_request_path_params();
for (size_t i = 0;
i < template_spec.template_component_names.size();
++i)
{
const auto& template_name =
template_spec.template_component_names[i];
const auto& template_value = match[i + 1].str();
path_params[template_name] = template_value;
}
}
auto endpoint = std::make_shared<JSDynamicEndpoint>();
endpoint->dispatch = other_key;
endpoint->properties = endpoints->get(other_key).value();
instantiate_authn_policies(*endpoint);
matches.push_back(endpoint);
}
}
}
return true;
});
if (matches.size() > 1)
{
report_ambiguous_templated_path(key.uri_path, matches);
}
else if (matches.size() == 1)
{
return matches[0];
}
}
return ccf::endpoints::EndpointRegistry::find_endpoint(tx, rpc_ctx);
}
void execute_endpoint(
ccf::endpoints::EndpointDefinitionPtr e,
ccf::endpoints::EndpointContext& endpoint_ctx) override
{
auto endpoint = dynamic_cast<const JSDynamicEndpoint*>(e.get());
if (endpoint != nullptr)
{
execute_request(endpoint->properties, endpoint_ctx);
return;
}
ccf::endpoints::EndpointRegistry::execute_endpoint(e, endpoint_ctx);
}
// Since we do our own dispatch within the default handler, report the
// supported methods here
void build_api(nlohmann::json& document, kv::ReadOnlyTx& tx) override
{
UserEndpointRegistry::build_api(document, tx);
auto endpoints =
tx.ro<ccf::endpoints::EndpointsMap>(ccf::Tables::ENDPOINTS);
endpoints->foreach([&document](const auto& key, const auto& properties) {
const auto http_verb = key.verb.get_http_method();
if (!http_verb.has_value())
{
return true;
}
if (!properties.openapi_hidden)
{
auto& path_op = ds::openapi::path_operation(
ds::openapi::path(document, key.uri_path),
http_verb.value(),
false);
if (!properties.openapi.empty())
{
for (const auto& [k, v] : properties.openapi.items())
{
LOG_INFO_FMT("Inserting field {}", k);
}
path_op.insert(
properties.openapi.cbegin(), properties.openapi.cend());
}
}
return true;
});
}
void tick(std::chrono::milliseconds elapsed, size_t tx_count) override
{
metrics_tracker.tick(elapsed, tx_count);
ccf::UserEndpointRegistry::tick(elapsed, tx_count);
}
};
#pragma clang diagnostic pop
class JS : public ccf::RpcFrontend
{
private:
JSHandlers js_handlers;
public:
JS(NetworkTables& network, ccfapp::AbstractNodeContext& context) :
ccf::RpcFrontend(*network.tables, js_handlers),
js_handlers(network, context)
{}
};
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
NetworkTables& network, ccfapp::AbstractNodeContext& context)
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context)
{
return make_shared<JS>(network, context);
return get_rpc_handler_impl(network, context);
}
std::vector<ccf::js::FFIPlugin> get_js_plugins()
{
return {ccf::js::openenclave_plugin};
}
} // namespace ccfapp

Просмотреть файл

@ -0,0 +1,653 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "apps/utils/metrics_tracker.h"
#include "ccf/app_interface.h"
#include "ccf/historical_queries_adapter.h"
#include "ccf/user_frontend.h"
#include "ccf/version.h"
#include "crypto/entropy.h"
#include "crypto/key_wrap.h"
#include "crypto/rsa_key_pair.h"
#include "js/wrap.h"
#include "kv/untyped_map.h"
#include "named_auth_policies.h"
#include <memory>
#include <quickjs/quickjs-exports.h>
#include <quickjs/quickjs.h>
#include <stdexcept>
#include <vector>
namespace ccfapp
{
using namespace std;
using namespace kv;
using namespace ccf;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
class JSHandlers : public UserEndpointRegistry
{
private:
NetworkTables& network;
ccfapp::AbstractNodeContext& context;
metrics::Tracker metrics_tracker;
static JSValue create_json_obj(const nlohmann::json& j, JSContext* ctx)
{
const auto buf = j.dump();
return JS_ParseJSON(ctx, buf.data(), buf.size(), "<json>");
}
JSValue create_caller_obj(
ccf::endpoints::EndpointContext& endpoint_ctx, JSContext* ctx)
{
if (endpoint_ctx.caller == nullptr)
{
return JS_NULL;
}
auto caller = JS_NewObject(ctx);
if (auto jwt_ident = endpoint_ctx.try_get_caller<ccf::JwtAuthnIdentity>())
{
JS_SetPropertyStr(
ctx,
caller,
"policy",
JS_NewString(ctx, get_policy_name_from_ident(jwt_ident)));
auto jwt = JS_NewObject(ctx);
JS_SetPropertyStr(
ctx,
jwt,
"keyIssuer",
JS_NewStringLen(
ctx, jwt_ident->key_issuer.data(), jwt_ident->key_issuer.size()));
JS_SetPropertyStr(
ctx, jwt, "header", create_json_obj(jwt_ident->header, ctx));
JS_SetPropertyStr(
ctx, jwt, "payload", create_json_obj(jwt_ident->payload, ctx));
JS_SetPropertyStr(ctx, caller, "jwt", jwt);
return caller;
}
else if (
auto empty_ident =
endpoint_ctx.try_get_caller<ccf::EmptyAuthnIdentity>())
{
JS_SetPropertyStr(
ctx,
caller,
"policy",
JS_NewString(ctx, get_policy_name_from_ident(empty_ident)));
return caller;
}
char const* policy_name = nullptr;
std::string id;
bool is_member = false;
if (
auto user_cert_ident =
endpoint_ctx.try_get_caller<ccf::UserCertAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(user_cert_ident);
id = user_cert_ident->user_id;
is_member = false;
}
else if (
auto member_cert_ident =
endpoint_ctx.try_get_caller<ccf::MemberCertAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(member_cert_ident);
id = member_cert_ident->member_id;
is_member = true;
}
else if (
auto user_sig_ident =
endpoint_ctx.try_get_caller<ccf::UserSignatureAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(user_sig_ident);
id = user_sig_ident->user_id;
is_member = false;
}
else if (
auto member_sig_ident =
endpoint_ctx.try_get_caller<ccf::MemberSignatureAuthnIdentity>())
{
policy_name = get_policy_name_from_ident(member_sig_ident);
id = member_sig_ident->member_id;
is_member = true;
}
if (policy_name == nullptr)
{
throw std::logic_error("Unable to convert caller info to JS object");
}
// Retrieve user/member data from authenticated caller id
nlohmann::json data = nullptr;
ccf::ApiResult result = ccf::ApiResult::OK;
if (is_member)
{
result = get_member_data_v1(endpoint_ctx.tx, id, data);
}
else
{
result = get_user_data_v1(endpoint_ctx.tx, id, data);
}
if (result == ccf::ApiResult::InternalError)
{
throw std::logic_error(
fmt::format("Failed to get data for caller {}", id));
}
crypto::Pem cert;
if (is_member)
{
result = get_member_cert_v1(endpoint_ctx.tx, id, cert);
}
else
{
result = get_user_cert_v1(endpoint_ctx.tx, id, cert);
}
if (result == ccf::ApiResult::InternalError)
{
throw std::logic_error(
fmt::format("Failed to get certificate for caller {}", id));
}
JS_SetPropertyStr(ctx, caller, "policy", JS_NewString(ctx, policy_name));
JS_SetPropertyStr(
ctx, caller, "id", JS_NewStringLen(ctx, id.data(), id.size()));
JS_SetPropertyStr(ctx, caller, "data", create_json_obj(data, ctx));
JS_SetPropertyStr(
ctx,
caller,
"cert",
JS_NewStringLen(ctx, cert.str().data(), cert.size()));
return caller;
}
JSValue create_request_obj(
ccf::endpoints::EndpointContext& endpoint_ctx, JSContext* ctx)
{
auto request = JS_NewObject(ctx);
auto headers = JS_NewObject(ctx);
for (auto& [header_name, header_value] :
endpoint_ctx.rpc_ctx->get_request_headers())
{
JS_SetPropertyStr(
ctx,
headers,
header_name.c_str(),
JS_NewStringLen(ctx, header_value.c_str(), header_value.size()));
}
JS_SetPropertyStr(ctx, request, "headers", headers);
const auto& request_query = endpoint_ctx.rpc_ctx->get_request_query();
auto query_str =
JS_NewStringLen(ctx, request_query.c_str(), request_query.size());
JS_SetPropertyStr(ctx, request, "query", query_str);
auto params = JS_NewObject(ctx);
for (auto& [param_name, param_value] :
endpoint_ctx.rpc_ctx->get_request_path_params())
{
JS_SetPropertyStr(
ctx,
params,
param_name.c_str(),
JS_NewStringLen(ctx, param_value.c_str(), param_value.size()));
}
JS_SetPropertyStr(ctx, request, "params", params);
const auto& request_body = endpoint_ctx.rpc_ctx->get_request_body();
auto body_ = JS_NewObjectClass(ctx, js::body_class_id);
JS_SetOpaque(body_, (void*)&request_body);
JS_SetPropertyStr(ctx, request, "body", body_);
JS_SetPropertyStr(
ctx, request, "caller", create_caller_obj(endpoint_ctx, ctx));
return request;
}
void execute_request(
const ccf::endpoints::EndpointProperties& props,
ccf::endpoints::EndpointContext& endpoint_ctx)
{
if (props.mode == ccf::endpoints::Mode::Historical)
{
auto is_tx_committed =
[this](ccf::View view, ccf::SeqNo seqno, std::string& error_reason) {
return ccf::historical::is_tx_committed_v2(
consensus, view, seqno, error_reason);
};
ccf::historical::adapter_v2(
[this, &props](
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr state) {
auto tx = state->store->create_tx();
auto tx_id = state->transaction_id;
auto receipt = state->receipt;
assert(receipt);
do_execute_request(props, endpoint_ctx, tx, tx_id, receipt);
},
context.get_historical_state(),
is_tx_committed)(endpoint_ctx);
}
else
{
do_execute_request(
props, endpoint_ctx, endpoint_ctx.tx, std::nullopt, nullptr);
}
}
void do_execute_request(
const ccf::endpoints::EndpointProperties& props,
ccf::endpoints::EndpointContext& endpoint_ctx,
kv::Tx& target_tx,
const std::optional<ccf::TxID>& transaction_id,
ccf::historical::TxReceiptPtr receipt)
{
js::Runtime rt;
rt.add_ccf_classdefs();
JS_SetModuleLoaderFunc(
rt, nullptr, js::js_app_module_loader, &endpoint_ctx.tx);
js::Context ctx(rt);
js::TxContext txctx{&target_tx, js::TxAccess::APP};
js::register_request_body_class(ctx);
js::populate_global(
&txctx,
endpoint_ctx.rpc_ctx.get(),
transaction_id,
receipt,
nullptr,
&context.get_node_state(),
nullptr,
ctx);
JSValue export_func;
try
{
auto module_val =
js::load_app_module(ctx, props.js_module.c_str(), &endpoint_ctx.tx);
export_func =
ctx.function(module_val, props.js_function, props.js_module);
}
catch (const std::exception& exc)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
exc.what());
return;
}
// Call exported function
auto request = create_request_obj(endpoint_ctx, ctx);
int argc = 1;
JSValueConst* argv = (JSValueConst*)&request;
auto val = ctx(JS_Call(ctx, export_func, JS_UNDEFINED, argc, argv));
JS_FreeValue(ctx, request);
JS_FreeValue(ctx, export_func);
if (JS_IsException(val))
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Exception thrown while executing.");
return;
}
// Handle return value: {body, headers, statusCode}
if (!JS_IsObject(val))
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (not an object).");
return;
}
// Response body (also sets a default response content-type header)
{
auto response_body_js = ctx(JS_GetPropertyStr(ctx, val, "body"));
if (!JS_IsUndefined(response_body_js))
{
std::vector<uint8_t> response_body;
size_t buf_size;
size_t buf_offset;
JSValue typed_array_buffer = JS_GetTypedArrayBuffer(
ctx, response_body_js, &buf_offset, &buf_size, nullptr);
uint8_t* array_buffer;
if (!JS_IsException(typed_array_buffer))
{
size_t buf_size_total;
array_buffer =
JS_GetArrayBuffer(ctx, &buf_size_total, typed_array_buffer);
array_buffer += buf_offset;
JS_FreeValue(ctx, typed_array_buffer);
}
else
{
array_buffer = JS_GetArrayBuffer(ctx, &buf_size, response_body_js);
}
if (array_buffer)
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::OCTET_STREAM);
response_body =
std::vector<uint8_t>(array_buffer, array_buffer + buf_size);
}
else
{
const char* cstr = nullptr;
if (JS_IsString(response_body_js))
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::TEXT);
cstr = JS_ToCString(ctx, response_body_js);
}
else
{
endpoint_ctx.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE,
http::headervalues::contenttype::JSON);
JSValue rval =
JS_JSONStringify(ctx, response_body_js, JS_NULL, JS_NULL);
if (JS_IsException(rval))
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (error during JSON "
"conversion of body).");
return;
}
cstr = JS_ToCString(ctx, rval);
JS_FreeValue(ctx, rval);
}
if (!cstr)
{
js::js_dump_error(ctx);
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (error during string "
"conversion of body).");
return;
}
std::string str(cstr);
JS_FreeCString(ctx, cstr);
response_body = std::vector<uint8_t>(str.begin(), str.end());
}
endpoint_ctx.rpc_ctx->set_response_body(std::move(response_body));
}
}
// Response headers
{
auto response_headers_js = ctx(JS_GetPropertyStr(ctx, val, "headers"));
if (JS_IsObject(response_headers_js))
{
uint32_t prop_count = 0;
JSPropertyEnum* props = nullptr;
JS_GetOwnPropertyNames(
ctx,
&props,
&prop_count,
response_headers_js,
JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY);
for (size_t i = 0; i < prop_count; i++)
{
auto prop_name = props[i].atom;
auto prop_name_cstr = ctx(JS_AtomToCString(ctx, prop_name));
auto prop_val =
ctx(JS_GetProperty(ctx, response_headers_js, prop_name));
auto prop_val_cstr = JS_ToCString(ctx, prop_val);
if (!prop_val_cstr)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (header value type).");
return;
}
endpoint_ctx.rpc_ctx->set_response_header(
prop_name_cstr, prop_val_cstr);
JS_FreeCString(ctx, prop_val_cstr);
}
js_free(ctx, props);
}
}
// Response status code
{
int response_status_code = HTTP_STATUS_OK;
auto status_code_js = ctx(JS_GetPropertyStr(ctx, val, "statusCode"));
if (!JS_IsUndefined(status_code_js) && !JS_IsNull(status_code_js))
{
if (JS_VALUE_GET_TAG(status_code_js.val) != JS_TAG_INT)
{
endpoint_ctx.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::InternalError,
"Invalid endpoint function return value (status code type).");
return;
}
response_status_code = JS_VALUE_GET_INT(status_code_js.val);
}
endpoint_ctx.rpc_ctx->set_response_status(response_status_code);
}
return;
}
struct JSDynamicEndpoint : public ccf::endpoints::EndpointDefinition
{};
public:
JSHandlers(NetworkTables& network, AbstractNodeContext& context) :
UserEndpointRegistry(context),
network(network),
context(context)
{
metrics_tracker.install_endpoint(*this);
}
void instantiate_authn_policies(JSDynamicEndpoint& endpoint)
{
for (const auto& policy_name : endpoint.properties.authn_policies)
{
auto policy = get_policy_by_name(policy_name);
if (policy == nullptr)
{
throw std::logic_error(
fmt::format("Unknown auth policy: {}", policy_name));
}
endpoint.authn_policies.push_back(std::move(policy));
}
}
ccf::endpoints::EndpointDefinitionPtr find_endpoint(
kv::Tx& tx, enclave::RpcContext& rpc_ctx) override
{
const auto method = rpc_ctx.get_method();
const auto verb = rpc_ctx.get_request_verb();
auto endpoints =
tx.ro<ccf::endpoints::EndpointsMap>(ccf::Tables::ENDPOINTS);
const auto key = ccf::endpoints::EndpointKey{method, verb};
// Look for a direct match of the given path
const auto it = endpoints->get(key);
if (it.has_value())
{
auto endpoint_def = std::make_shared<JSDynamicEndpoint>();
endpoint_def->dispatch = key;
endpoint_def->properties = it.value();
instantiate_authn_policies(*endpoint_def);
return endpoint_def;
}
// If that doesn't exist, look through _all_ the endpoints to find
// templated matches. If there is one, that's a match. More is an error,
// none means delegate to the base class.
{
std::vector<ccf::endpoints::EndpointDefinitionPtr> matches;
endpoints->foreach_key(
[this, &endpoints, &matches, &key, &rpc_ctx](const auto& other_key) {
if (key.verb == other_key.verb)
{
const auto opt_spec =
ccf::endpoints::parse_path_template(other_key.uri_path);
if (opt_spec.has_value())
{
const auto& template_spec = opt_spec.value();
// This endpoint has templates in its path, and the correct verb
// - now check if template matches the current request's path
std::smatch match;
if (std::regex_match(
key.uri_path, match, template_spec.template_regex))
{
if (matches.empty())
{
// Populate the request_path_params while we have the match,
// though this will be discarded on error if we later find
// multiple matches
auto& path_params = rpc_ctx.get_request_path_params();
for (size_t i = 0;
i < template_spec.template_component_names.size();
++i)
{
const auto& template_name =
template_spec.template_component_names[i];
const auto& template_value = match[i + 1].str();
path_params[template_name] = template_value;
}
}
auto endpoint = std::make_shared<JSDynamicEndpoint>();
endpoint->dispatch = other_key;
endpoint->properties = endpoints->get(other_key).value();
instantiate_authn_policies(*endpoint);
matches.push_back(endpoint);
}
}
}
return true;
});
if (matches.size() > 1)
{
report_ambiguous_templated_path(key.uri_path, matches);
}
else if (matches.size() == 1)
{
return matches[0];
}
}
return ccf::endpoints::EndpointRegistry::find_endpoint(tx, rpc_ctx);
}
void execute_endpoint(
ccf::endpoints::EndpointDefinitionPtr e,
ccf::endpoints::EndpointContext& endpoint_ctx) override
{
auto endpoint = dynamic_cast<const JSDynamicEndpoint*>(e.get());
if (endpoint != nullptr)
{
execute_request(endpoint->properties, endpoint_ctx);
return;
}
ccf::endpoints::EndpointRegistry::execute_endpoint(e, endpoint_ctx);
}
// Since we do our own dispatch within the default handler, report the
// supported methods here
void build_api(nlohmann::json& document, kv::ReadOnlyTx& tx) override
{
UserEndpointRegistry::build_api(document, tx);
auto endpoints =
tx.ro<ccf::endpoints::EndpointsMap>(ccf::Tables::ENDPOINTS);
endpoints->foreach([&document](const auto& key, const auto& properties) {
const auto http_verb = key.verb.get_http_method();
if (!http_verb.has_value())
{
return true;
}
if (!properties.openapi_hidden)
{
auto& path_op = ds::openapi::path_operation(
ds::openapi::path(document, key.uri_path),
http_verb.value(),
false);
if (!properties.openapi.empty())
{
for (const auto& [k, v] : properties.openapi.items())
{
LOG_INFO_FMT("Inserting field {}", k);
}
path_op.insert(
properties.openapi.cbegin(), properties.openapi.cend());
}
}
return true;
});
}
void tick(std::chrono::milliseconds elapsed, size_t tx_count) override
{
metrics_tracker.tick(elapsed, tx_count);
ccf::UserEndpointRegistry::tick(elapsed, tx_count);
}
};
#pragma clang diagnostic pop
class JS : public ccf::RpcFrontend
{
private:
JSHandlers js_handlers;
public:
JS(NetworkTables& network, ccfapp::AbstractNodeContext& context) :
ccf::RpcFrontend(*network.tables, js_handlers),
js_handlers(network, context)
{}
};
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler_impl(
NetworkTables& network, ccfapp::AbstractNodeContext& context)
{
return make_shared<JS>(network, context);
}
} // namespace ccfapp

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "ccf/app_interface.h"
#include <memory>
namespace ccfapp
{
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler_impl(
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context);
}

Просмотреть файл

@ -7,6 +7,7 @@
#include "ds/oversized.h"
#include "enclave_time.h"
#include "interface.h"
#include "js/wrap.h"
#include "node/entities.h"
#include "node/historical_queries.h"
#include "node/network_state.h"
@ -126,6 +127,8 @@ namespace enclave
rpc_map->register_frontend<ccf::ActorsType::nodes>(
std::make_unique<ccf::NodeRpcFrontend>(network, *context));
ccf::js::register_ffi_plugins(ccfapp::get_js_plugins());
node->initialize(
consensus_config,
rpc_map,

Просмотреть файл

@ -2,7 +2,7 @@
// Licensed under the Apache 2.0 License.
#include "js/wrap.h"
namespace js
namespace ccf::js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"

Просмотреть файл

@ -9,7 +9,7 @@
#include <quickjs/quickjs.h>
namespace js
namespace ccf::js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
@ -485,4 +485,4 @@ namespace js
#pragma clang diagnostic pop
}
}

14
src/js/no_plugins.cpp Normal file
Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#include "ccf/app_interface.h"
#include "ccf/js_plugin.h"
#include <vector>
namespace ccfapp
{
std::vector<ccf::js::FFIPlugin> __attribute__((weak)) get_js_plugins()
{
return {};
}
}

Просмотреть файл

@ -13,10 +13,12 @@
#else
# include <openenclave/host_verify.h>
#endif
#include "ccf/js_openenclave_plugin.h"
#include "ccf/js_plugin.h"
#include "ccf/version.h"
#include "js/plugin.h"
#include "js/wrap.h"
namespace js
namespace ccf::js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"

Просмотреть файл

@ -8,7 +8,7 @@
#include "enclave/rpc_context.h"
#include "js/conv.cpp"
#include "js/crypto.cpp"
#include "js/oe.cpp"
#include "js/no_plugins.cpp"
#include "kv/untyped_map.h"
#include "node/jwt.h"
#include "node/rpc/call_types.h"
@ -19,7 +19,7 @@
#include <quickjs/quickjs-exports.h>
#include <quickjs/quickjs.h>
namespace js
namespace ccf::js
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
@ -59,9 +59,12 @@ namespace js
ffi_plugins.push_back(plugin);
}
void register_ffi_plugins()
void register_ffi_plugins(const std::vector<FFIPlugin>& plugins)
{
register_ffi_plugin(openenclave_plugin);
for (const auto& plugin : plugins)
{
register_ffi_plugin(plugin);
}
}
static JSValue js_kv_map_has(

Просмотреть файл

@ -3,10 +3,10 @@
#pragma once
#include "ccf/historical_queries_interface.h"
#include "ccf/js_plugin.h"
#include "ccf/tx.h"
#include "ds/logger.h"
#include "enclave/rpc_context.h"
#include "js/plugin.h"
#include "kv/kv_types.h"
#include "node/network_state.h"
#include "node/rpc/node_interface.h"
@ -15,7 +15,7 @@
#include <quickjs/quickjs-exports.h>
#include <quickjs/quickjs.h>
namespace js
namespace ccf::js
{
extern JSClassID kv_class_id;
extern JSClassID kv_map_handle_class_id;
@ -46,7 +46,7 @@ namespace js
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wc99-extensions"
void register_ffi_plugins();
void register_ffi_plugins(const std::vector<ccf::js::FFIPlugin>& plugins);
void register_class_ids();
void register_request_body_class(JSContext* ctx);
void populate_global(
@ -208,4 +208,4 @@ namespace js
#pragma clang diagnostic pop
}
}

Просмотреть файл

@ -333,7 +333,6 @@ namespace ccf
get_subject_alternative_names();
js::register_class_ids();
js::register_ffi_plugins();
self_signed_node_cert = create_self_signed_node_cert();
accept_node_tls_connections();
open_frontend(ActorsType::nodes);