blob: 51dd7b23285a7371902c5235dd51018d15e0ee25 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "fidl.h"
#include <array>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <memory>
#include <set>
#include <vector>
#include "lib/fidl/txn_header.h"
#include "src/developer/shell/josh/lib/object_converter.h"
#include "src/developer/shell/josh/lib/qjs_util.h"
#include "src/developer/shell/josh/lib/zx.h"
#include "src/lib/fidl_codec/encoder.h"
#include "src/lib/fidl_codec/library_loader.h"
#include "src/lib/fidl_codec/wire_parser.h"
#include "third_party/quickjs/quickjs.h"
#include "third_party/rapidjson/include/rapidjson/stringbuffer.h"
#include "third_party/rapidjson/include/rapidjson/writer.h"
namespace shell::fidl {
// TODO(jeremymanson): Dedup from fidl_codec.
namespace {
std::string DocumentToString(rapidjson::Document* document) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
document->Accept(writer);
return output.GetString();
}
JSClassID fidl_class_id_;
JSClassDef fidl_class_ = {
"FidlInternal",
.finalizer = nullptr,
};
// Loads a FIDL library.
// argv[0] A string name of the library (e.g., "fuchsia.io")
// Returns a boolean indicating success.
JSValue LoadLibrary(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
if (argc != 1) {
return JS_ThrowSyntaxError(ctx,
"Wrong number of arguments to fidl.loadLibrary(), "
"was %d, expected 1",
argc);
}
auto loader =
reinterpret_cast<fidl_codec::LibraryLoader*>(JS_GetOpaque(this_val, fidl_class_id_));
if (loader == nullptr) {
return JS_EXCEPTION;
}
CStringHolder val(ctx, argv[0]);
const char* path = val.get();
if (!path) {
return JS_EXCEPTION;
}
fidl_codec::LibraryReadError loader_err;
loader->AddPath(std::string(path), &loader_err);
return JS_NewBool(ctx, loader_err.value == fidl_codec::LibraryReadError::kOk);
}
// Loads a FIDL library from a string containing its JSON.
// argv[0] A string name of the library (e.g., "fuchsia.io")
// argv[1] A string containing the IR of the library.
// Returns a boolean indicating success.
JSValue LoadLibraryFromString(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
if (argc != 2) {
return JS_ThrowSyntaxError(ctx,
"Wrong number of arguments to fidl.loadLibraryFromString(), "
"was %d, expected 2",
argc);
}
auto loader =
reinterpret_cast<fidl_codec::LibraryLoader*>(JS_GetOpaque(this_val, fidl_class_id_));
if (loader == nullptr) {
return JS_EXCEPTION;
}
CStringHolder val(ctx, argv[0]);
const char* path = val.get();
if (!path) {
return JS_EXCEPTION;
}
fidl_codec::LibraryReadError loader_err;
CStringHolder contents(ctx, argv[1]);
loader->AddContent(std::string(contents.get()), &loader_err);
return JS_NewBool(ctx, loader_err.value == fidl_codec::LibraryReadError::kOk);
}
// Returns an object with a "bytes" and "handles" field containing the encoded version of a fidl
// request.
// argv[0] = Transaction ID.
// argv[1] = Ordinal.
// argv[2] = Object.
JSValue EncodeRequest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
const uint8_t kFidlMagic = 1;
const uint8_t kFlags[3] = {0, 0, 0};
if (argc != 3) {
return JS_ThrowSyntaxError(ctx,
"Wrong number of arguments to fidl.encodeRequest(), "
"was %d, expected 3",
argc);
}
auto loader =
reinterpret_cast<fidl_codec::LibraryLoader*>(JS_GetOpaque(this_val, fidl_class_id_));
if (loader == nullptr) {
return JS_EXCEPTION;
}
int32_t txn_id_signed;
if (JS_ToInt32(ctx, &txn_id_signed, argv[0]) == -1) {
return JS_EXCEPTION;
}
auto txn_id = static_cast<uint32_t>(txn_id_signed);
int64_t ordinal_signed;
if (JS_ToBigInt64(ctx, &ordinal_signed, argv[1]) == -1) {
return JS_EXCEPTION;
}
auto ordinal = static_cast<fidl_codec::Ordinal64>(ordinal_signed);
const std::vector<const fidl_codec::InterfaceMethod*>* methods = loader->GetByOrdinal(ordinal);
if (!methods || methods->empty()) {
return JS_ThrowInternalError(ctx, "Method not found for ordinal %zu", ordinal);
}
auto method = (*methods)[0];
auto request = method->request();
if (request == nullptr) {
return JS_ThrowInternalError(ctx, "Method missing request.");
}
auto ast = ObjectConverter::Convert(ctx, request, argv[2]);
if (!ast || !ast->AsStructValue()) {
return JS_EXCEPTION;
}
auto result = fidl_codec::Encoder::EncodeMessage(txn_id, ordinal, kFlags, kFidlMagic,
*ast->AsStructValue());
auto bytes = JS_NewArrayBufferCopy(ctx, result.bytes.data(), result.bytes.size());
auto handles = JS_NewArray(ctx);
JS_SetPropertyStr(ctx, handles, "length",
JS_NewInt32(ctx, static_cast<int32_t>(result.handles.size())));
for (uint32_t i = 0; i < result.handles.size(); i++) {
JSValue opaque_handle = zx::HandleCreate(ctx, result.handles[i].handle, result.handles[i].type);
JSValue user_handle = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, user_handle, "_handle", opaque_handle);
JS_SetPropertyUint32(ctx, handles, i, user_handle);
}
auto ret = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, ret, "bytes", bytes);
JS_SetPropertyStr(ctx, ret, "handles", handles);
return ret;
}
// Returns a string with the JSON representation of this FIDL message.
// argv[0] = bytes
// argv[1] = handles
JSValue DecodeResponse(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) {
if (argc != 2) {
return JS_ThrowSyntaxError(ctx,
"Wrong number of arguments to fidl.decodeResponse(), "
"was %d, expected 2",
argc);
}
auto loader =
reinterpret_cast<fidl_codec::LibraryLoader*>(JS_GetOpaque(this_val, fidl_class_id_));
if (loader == nullptr) {
return JS_EXCEPTION;
}
size_t message_len;
uint8_t* message_buf = JS_GetArrayBuffer(ctx, &message_len, argv[0]);
if (message_buf == nullptr || message_len < sizeof(fidl_message_header_t)) {
return JS_NewString(ctx, "");
}
if (!JS_IsArray(ctx, argv[1])) {
return JS_ThrowSyntaxError(ctx, "Expected array of handles");
}
int32_t handles_len;
// It's an array, so assume this works...
JS_ToInt32(ctx, &handles_len, JS_GetPropertyStr(ctx, argv[1], "length"));
std::array<zx_handle_disposition_t, ZX_CHANNEL_MAX_MSG_HANDLES> handle_buf;
// Check if handles returned anything useful.
for (int32_t i = 0; i < handles_len; i++) {
JSValue val = JS_GetPropertyUint32(ctx, argv[1], i);
handle_buf[i] = zx::HandleFromJsval(val);
}
auto header = reinterpret_cast<const fidl_message_header_t*>(message_buf);
const std::vector<const fidl_codec::InterfaceMethod*>* methods =
loader->GetByOrdinal(header->ordinal);
// Test method not found, but...
const fidl_codec::InterfaceMethod* method = (*methods)[0];
std::unique_ptr<fidl_codec::StructValue> object;
std::ostringstream errors;
if (!fidl_codec::DecodeResponse(method, message_buf, message_len, handle_buf.data(), handles_len,
&object, errors)) {
return JS_ThrowTypeError(ctx, "%s", errors.str().c_str());
}
rapidjson::Document actual_response;
if (object != nullptr) {
object->ExtractJson(actual_response.GetAllocator(), actual_response);
std::string val = DocumentToString(&actual_response);
return JS_NewString(ctx, val.c_str());
}
return JS_NewString(ctx, "");
}
// Returns a new library object, which hides a fidl_codec::LibraryLoader.
JSValue NewLibrary(JSContext* ctx, JSValueConst /*this_val*/, int argc, JSValueConst* /*argv*/) {
if (argc != 0) {
return JS_ThrowSyntaxError(ctx,
"Wrong number of arguments to fidl.decodeResponse(), "
"was %d, expected 0",
argc);
}
JSValue new_library = JS_NewObjectClass(ctx, fidl_class_id_);
if (JS_IsException(new_library)) {
return JS_EXCEPTION;
}
JS_SetOpaque(new_library, new fidl_codec::LibraryLoader());
return new_library;
}
// Closes the library passed in via this_val.
JSValue Close(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* /*argv*/) {
if (argc != 0) {
return JS_ThrowSyntaxError(ctx,
"Wrong number of arguments to fidl.decodeResponse(), "
"was %d, expected 0",
argc);
}
auto loader =
reinterpret_cast<fidl_codec::LibraryLoader*>(JS_GetOpaque(this_val, fidl_class_id_));
if (loader == nullptr) {
return JS_EXCEPTION;
}
delete loader;
JS_SetOpaque(this_val, nullptr);
return JS_UNDEFINED;
}
const JSCFunctionListEntry fidl_proto_funcs_[] = {
JS_CFUNC_DEF("loadLibrary", 1, LoadLibrary),
JS_CFUNC_DEF("loadLibraryFromString", 1, LoadLibraryFromString),
JS_CFUNC_DEF("encodeRequest", 1, EncodeRequest),
JS_CFUNC_DEF("decodeResponse", 1, DecodeResponse),
JS_CFUNC_DEF("close", 1, Close),
};
JSCFunctionListEntry module_funcs_[] = {JS_CFUNC_DEF("newLibrary", 1, NewLibrary),
JS_PROP_STRING_DEF("irPath", "", JS_PROP_CONFIGURABLE)};
int FidlRunOnInit(JSContext* ctx, JSModuleDef* m) {
JS_NewClassID(&fidl_class_id_);
JS_NewClass(JS_GetRuntime(ctx), fidl_class_id_, &fidl_class_);
JSValue proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, proto, fidl_proto_funcs_, std::size(fidl_proto_funcs_));
JS_SetClassProto(ctx, fidl_class_id_, proto);
JS_SetModuleExportList(ctx, m, module_funcs_, std::size(module_funcs_));
return 0;
};
} // namespace
JSModuleDef* FidlModuleInit(JSContext* ctx, const char* module_name, const std::string& fidl_path) {
JSModuleDef* m = JS_NewCModule(ctx, module_name, FidlRunOnInit);
if (!m) {
return nullptr;
}
module_funcs_[1].u.str = fidl_path.c_str();
JS_AddModuleExportList(ctx, m, module_funcs_, std::size(module_funcs_));
return m;
}
} // namespace shell::fidl