346 строки
10 KiB
C++
346 строки
10 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
#include "pch.h"
|
|
|
|
#include "BaseScriptStoreImpl.h"
|
|
#include "Hasher.h"
|
|
#include "MemoryMappedBuffer.h"
|
|
|
|
#include <CppRuntimeOptions.h>
|
|
|
|
// C++/WinRT
|
|
#include <winrt/base.h>
|
|
|
|
// Standard Library
|
|
#include <fstream>
|
|
|
|
namespace facebook {
|
|
namespace react {
|
|
|
|
namespace {
|
|
|
|
class ByteArrayBuffer : public facebook::jsi::Buffer {
|
|
public:
|
|
size_t size() const override {
|
|
return size_;
|
|
}
|
|
|
|
const uint8_t *data() const override {
|
|
return byteArray_.get();
|
|
}
|
|
|
|
uint8_t *data() {
|
|
return byteArray_.get();
|
|
}
|
|
|
|
ByteArrayBuffer(size_t size) : size_(size), byteArray_(std::make_unique<uint8_t[]>(size)) {}
|
|
|
|
ByteArrayBuffer(ByteArrayBuffer &&) = default;
|
|
ByteArrayBuffer &operator=(ByteArrayBuffer &&) = default;
|
|
|
|
private:
|
|
ByteArrayBuffer(const ByteArrayBuffer &) = delete;
|
|
ByteArrayBuffer &operator=(const ByteArrayBuffer &) = delete;
|
|
|
|
size_t size_;
|
|
std::unique_ptr<uint8_t[]> byteArray_;
|
|
};
|
|
|
|
class BufferViewBuffer : public facebook::jsi::Buffer {
|
|
public:
|
|
size_t size() const override {
|
|
return size_;
|
|
}
|
|
|
|
const uint8_t *data() const override {
|
|
return buffer_->data() + offset_;
|
|
}
|
|
|
|
uint8_t *data() {
|
|
return const_cast<uint8_t *>(buffer_->data()) + offset_;
|
|
}
|
|
|
|
BufferViewBuffer(std::unique_ptr<const facebook::jsi::Buffer> buffer, size_t offset, size_t size)
|
|
: buffer_(std::move(buffer)), offset_(offset), size_(size) {
|
|
if (size_ > buffer_->size() - offset)
|
|
std::terminate();
|
|
}
|
|
|
|
BufferViewBuffer(BufferViewBuffer &&) = default;
|
|
BufferViewBuffer &operator=(BufferViewBuffer &&) = default;
|
|
|
|
private:
|
|
BufferViewBuffer(const BufferViewBuffer &) = delete;
|
|
BufferViewBuffer &operator=(const BufferViewBuffer &) = delete;
|
|
|
|
std::unique_ptr<const facebook::jsi::Buffer> buffer_;
|
|
size_t offset_;
|
|
size_t size_;
|
|
};
|
|
|
|
constexpr const char *PERSIST_MAGIC = "RNWPREP";
|
|
constexpr const char *PERSIST_EOF = "EOF";
|
|
|
|
int constexpr length__(const char *str) {
|
|
return *str ? 1 + length__(str + 1) : 0;
|
|
}
|
|
|
|
// TODO : Enforce any packing policy ?
|
|
struct PreparedScriptPrefix {
|
|
// TODO :: constexpr initialize the array.
|
|
char magic[length__(PERSIST_MAGIC)];
|
|
jsi::ScriptVersion_t scriptVersion;
|
|
jsi::JSRuntimeVersion_t runtimeVersion;
|
|
uint64_t sizeInBytes;
|
|
std::uint8_t hash[32];
|
|
};
|
|
|
|
struct PreparedScriptSuffix {
|
|
char eof[length__(PERSIST_EOF)];
|
|
};
|
|
|
|
} // namespace
|
|
|
|
jsi::VersionedBuffer BaseScriptStoreImpl::getVersionedScript(const std::string &url) noexcept {
|
|
std::ifstream file(url, std::ios::binary | std::ios::ate);
|
|
|
|
if (!file) {
|
|
return {nullptr, 0};
|
|
}
|
|
|
|
std::streamsize size = file.tellg();
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
auto buffer = std::make_unique<ByteArrayBuffer>(static_cast<size_t>(size));
|
|
if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
|
|
return {nullptr, 0};
|
|
}
|
|
|
|
file.close();
|
|
|
|
return {std::move(buffer), versionProvider_ ? versionProvider_->getVersion(url) : static_cast<uint64_t>(size)};
|
|
}
|
|
|
|
jsi::ScriptVersion_t BaseScriptStoreImpl::getScriptVersion(const std::string &url) noexcept {
|
|
if (versionProvider_) {
|
|
return versionProvider_->getVersion(url);
|
|
} else {
|
|
std::ifstream file(url, std::ios::binary | std::ios::ate);
|
|
|
|
if (!file) {
|
|
return 0;
|
|
}
|
|
|
|
std::streamsize size = file.tellg();
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
return static_cast<uint64_t>(size);
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<const jsi::Buffer> LocalFileSimpleBufferStore::getBuffer(const std::string &bufferId) noexcept {
|
|
// 1. Store path must be set
|
|
// 2. It must be a directory that exists. TODO :: Figure out a cross platform
|
|
// way to ensure this.
|
|
// 3. Directory must end with the path delimiter. TODO :: Verify this.
|
|
if (storeDirectory_.empty()) {
|
|
std::terminate();
|
|
}
|
|
|
|
if (Microsoft::React::GetRuntimeOptionBool("JSI.MemoryMappedScriptStore")) {
|
|
try {
|
|
return Microsoft::JSI::MakeMemoryMappedBuffer(winrt::to_hstring(storeDirectory_ + bufferId).c_str());
|
|
} catch (const facebook::jsi::JSINativeException &) {
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
// Treat buffer id as the relative path fragment.
|
|
std::ifstream file(storeDirectory_ + bufferId, std::ios::binary | std::ios::ate);
|
|
|
|
if (!file) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::streamsize size = file.tellg();
|
|
file.seekg(0, std::ios::beg);
|
|
|
|
auto buffer = std::make_unique<ByteArrayBuffer>(static_cast<size_t>(size));
|
|
if (!file.read(reinterpret_cast<char *>(buffer->data()), size)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
bool LocalFileSimpleBufferStore::persistBuffer(
|
|
const std::string &relativeUrl,
|
|
std::unique_ptr<const jsi::Buffer> buffer) noexcept {
|
|
// Assumptions on storeDirectory_ same as in getRawBuffer
|
|
if (storeDirectory_.empty())
|
|
std::terminate();
|
|
|
|
std::ofstream file;
|
|
file.open(storeDirectory_ + relativeUrl, std::ios::binary | std::ios::trunc);
|
|
if (!file)
|
|
return false;
|
|
|
|
file.write(reinterpret_cast<const char *>(buffer->data()), buffer->size());
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string BasePreparedScriptStoreImpl::getPreparedScriptFileName(
|
|
const jsi::ScriptSignature &scriptSignature,
|
|
const jsi::JSRuntimeSignature &runtimeSignature,
|
|
const char *prepareTag) {
|
|
// Essentially, we are trying to construct,
|
|
// prep_<source_url>_<runtime_id>_<preparation_tag>.cache
|
|
|
|
std::string preparedScriptFileName("prep_");
|
|
|
|
const std::string &scriptUrl = scriptSignature.url;
|
|
|
|
// As a crude heuristic we choose the last 64 characters of the source url.
|
|
constexpr int MAXLENGTH = 64;
|
|
preparedScriptFileName.append(
|
|
scriptUrl.begin() + ((scriptUrl.size() < MAXLENGTH) ? 0 : (scriptUrl.size() - MAXLENGTH)), scriptUrl.end());
|
|
|
|
// Make a valid file name.
|
|
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), '\\', '_');
|
|
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), '/', '_');
|
|
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), ':', '_');
|
|
std::replace(preparedScriptFileName.begin(), preparedScriptFileName.end(), '.', '_');
|
|
|
|
if (runtimeSignature.runtimeName.empty()) {
|
|
std::terminate();
|
|
}
|
|
|
|
preparedScriptFileName.append("_");
|
|
preparedScriptFileName.append(runtimeSignature.runtimeName);
|
|
|
|
if (prepareTag) {
|
|
preparedScriptFileName.append("_");
|
|
preparedScriptFileName.append(prepareTag);
|
|
}
|
|
|
|
// TODO :: Need to construct a hash. ref:
|
|
// https://en.wikipedia.org/wiki/Base64#Filenames
|
|
|
|
// extension
|
|
preparedScriptFileName.append(".cache");
|
|
|
|
return preparedScriptFileName;
|
|
}
|
|
|
|
std::shared_ptr<const jsi::Buffer> BasePreparedScriptStoreImpl::tryGetPreparedScript(
|
|
const jsi::ScriptSignature &scriptSignature,
|
|
const jsi::JSRuntimeSignature &runtimeSignature,
|
|
const char *prepareTag) noexcept {
|
|
std::string preparedScriptFilePath = getPreparedScriptFileName(scriptSignature, runtimeSignature, prepareTag);
|
|
|
|
auto buffer = bufferStore_->getBuffer(preparedScriptFilePath);
|
|
|
|
if (!buffer) {
|
|
return nullptr;
|
|
}
|
|
|
|
const PreparedScriptPrefix *prefix = reinterpret_cast<const PreparedScriptPrefix *>(buffer->data());
|
|
|
|
if (strncmp(prefix->magic, PERSIST_MAGIC, sizeof(prefix->magic)) != 0) {
|
|
// magic value doesn't match!! The store is very likely corrupted or belongs
|
|
// to old version.
|
|
return nullptr;
|
|
}
|
|
|
|
if (prefix->scriptVersion != scriptSignature.version) {
|
|
// script version don't match!! Need to regenerate cache.
|
|
return nullptr;
|
|
}
|
|
|
|
if (prefix->runtimeVersion != runtimeSignature.version) {
|
|
// Runtime changed after the cache generation.
|
|
return nullptr;
|
|
}
|
|
|
|
if (prefix->sizeInBytes != buffer->size() - sizeof(PreparedScriptPrefix) - sizeof(PreparedScriptSuffix)) {
|
|
// Size is not as expected. Store is possibly corrupted .. It is safer to
|
|
// bail out.
|
|
return nullptr;
|
|
}
|
|
|
|
std::optional<std::vector<std::uint8_t>> hashBuffer = Microsoft::ReactNative::GetSHA256Hash(
|
|
reinterpret_cast<const std::uint8_t *>(buffer->data()) + sizeof(PreparedScriptPrefix),
|
|
static_cast<size_t>(prefix->sizeInBytes));
|
|
if (!hashBuffer) {
|
|
// Hashing failed.
|
|
return nullptr;
|
|
}
|
|
|
|
if (hashBuffer.value().size() < sizeof(prefix->hash)) {
|
|
// Unexpected hash size.
|
|
return nullptr;
|
|
}
|
|
|
|
if (memcmp(hashBuffer.value().data(), prefix->hash, sizeof(prefix->hash)) != 0) {
|
|
// Hash doesn't match. Store is possibly corrupted. It is safer to bail out.
|
|
return nullptr;
|
|
}
|
|
|
|
const PreparedScriptSuffix *suffix = reinterpret_cast<const PreparedScriptSuffix *>(
|
|
buffer->data() + sizeof(PreparedScriptPrefix) + prefix->sizeInBytes);
|
|
if (strncmp(suffix->eof, PERSIST_EOF, sizeof(suffix->eof)) != 0) {
|
|
// magic value doesn't match!! The store is very likely corrupted or belongs
|
|
// to old version.
|
|
return nullptr;
|
|
}
|
|
|
|
return std::make_shared<BufferViewBuffer>(
|
|
std::move(buffer), sizeof(PreparedScriptPrefix), static_cast<size_t>(prefix->sizeInBytes));
|
|
}
|
|
|
|
void BasePreparedScriptStoreImpl::persistPreparedScript(
|
|
std::shared_ptr<const jsi::Buffer> preparedScript,
|
|
const jsi::ScriptSignature &scriptMetadata,
|
|
const jsi::JSRuntimeSignature &runtimeMetadata,
|
|
const char *prepareTag) noexcept {
|
|
// TODO :: Unfortunately, The current abstraction is forcing us to make a
|
|
// copy. Need to re-evaluate.
|
|
auto newBuffer = std::make_unique<ByteArrayBuffer>(
|
|
sizeof(PreparedScriptPrefix) + preparedScript->size() + sizeof(PreparedScriptSuffix));
|
|
|
|
PreparedScriptPrefix *prefix = reinterpret_cast<PreparedScriptPrefix *>(newBuffer->data());
|
|
memcpy_s(prefix->magic, sizeof(prefix->magic), PERSIST_MAGIC, sizeof(prefix->magic));
|
|
prefix->scriptVersion = scriptMetadata.version;
|
|
prefix->runtimeVersion = runtimeMetadata.version;
|
|
prefix->sizeInBytes = preparedScript->size();
|
|
|
|
std::optional<std::vector<std::uint8_t>> hashBuffer =
|
|
Microsoft::ReactNative::GetSHA256Hash(preparedScript->data(), preparedScript->size());
|
|
if (!hashBuffer) {
|
|
// Hashing failed.
|
|
std::terminate();
|
|
}
|
|
|
|
memcpy_s(prefix->hash, sizeof(prefix->hash), hashBuffer.value().data(), hashBuffer.value().size());
|
|
|
|
memcpy_s(
|
|
newBuffer->data() + sizeof(PreparedScriptPrefix),
|
|
newBuffer->size() - sizeof(PreparedScriptPrefix),
|
|
preparedScript->data(),
|
|
preparedScript->size());
|
|
|
|
PreparedScriptSuffix *suffix = reinterpret_cast<PreparedScriptSuffix *>(
|
|
newBuffer->data() + sizeof(PreparedScriptPrefix) + preparedScript->size());
|
|
memcpy_s(suffix->eof, sizeof(suffix->eof), PERSIST_EOF, sizeof(suffix->eof));
|
|
|
|
std::string preparedScriptFilePath = getPreparedScriptFileName(scriptMetadata, runtimeMetadata, prepareTag);
|
|
|
|
bufferStore_->persistBuffer(preparedScriptFilePath, std::move(newBuffer));
|
|
}
|
|
|
|
} // namespace react
|
|
} // namespace facebook
|