diff --git a/src/inspector/inspector_utils.cpp b/src/inspector/inspector_utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0003d83053625865a27b7eaff47967691066da5a --- /dev/null +++ b/src/inspector/inspector_utils.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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 "inspector_utils.h" + +#include "jsvm_dfx.h" +#include "unicode/unistr.h" + +#if HAVE_OPENSSL +#include "openssl/opensslv.h" +#endif + +#if OPENSSL_VERSION_MAJOR >= 3 +#include "openssl/provider.h" +#endif + +#include +#include + +namespace jsvm { +namespace inspector { +namespace { +inline icu::UnicodeString Utf8ToUtf16(const char* data, size_t len) +{ + icu::UnicodeString utf16Str = icu::UnicodeString::fromUTF8(icu::StringPiece(data, len)); + + return utf16Str; +} + +inline std::string Utf16ToUtf8(const char16_t* data, size_t length) +{ + icu::UnicodeString unicodeStr(data, length); + std::string utf8Str; + unicodeStr.toUTF8String(utf8Str); + + return utf8Str; +} +} // namespace + +std::unique_ptr Utf8ToStringView(const std::string_view message) +{ + icu::UnicodeString utf16Str = Utf8ToUtf16(message.data(), message.length()); + size_t utf16Len = utf16Str.length(); + + v8_inspector::StringView view(reinterpret_cast(utf16Str.getBuffer()), utf16Len); + return v8_inspector::StringBuffer::create(view); +} + +std::string StringViewToUtf8(v8_inspector::StringView view) +{ + if (view.length() == 0) { + return ""; + } + if (view.is8Bit()) { + return std::string(reinterpret_cast(view.characters8()), view.length()); + } + const char16_t* source = reinterpret_cast(view.characters16()); + + return Utf16ToUtf8(source, view.length()); +} + +static constexpr char BASE64_CHAR_SET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +// clang-format off +size_t Base64Encode(const char* inputString, size_t slen, char* outputBuffer, size_t dlen) +{ + // 1: caluate encode size and check + size_t strLen = slen; + size_t encodedStrLen = Base64EncodeSize(slen); + + CHECK_GE(dlen, encodedStrLen); + + // 2: the index do not exceed the range of outputBuffer and form a complete four-character block + for (size_t i = 0, j = 0; j < strLen - 2; i += TRANSFORMED_CHAR_NUM, j += TO_TRANSFORM_CHAR_NUM) { + // convert three 8bit into four 6bit; then add two 0 bit in each 6 bit + // former 00 + first 6 bits of the first char + outputBuffer[i] = BASE64_CHAR_SET[(static_cast(inputString[j]) & 0xff) >> ByteOffset::BIT_2]; + // 00 + the last 2 bits of the first char + the first 4 bits of the second char + outputBuffer[i + ByteOffset::BYTE_1] = + BASE64_CHAR_SET[(static_cast(inputString[j]) & 0x03) << ByteOffset::BIT_4 | + (static_cast(inputString[j + ByteOffset::BYTE_1]) & 0xf0) >> + ByteOffset::BIT_4]; + // 00 + last 4 bits of the second char + the first 2 bits of the third char + outputBuffer[i + ByteOffset::BYTE_2] = + BASE64_CHAR_SET[(static_cast(inputString[j + ByteOffset::BYTE_1]) & 0x0f) << + ByteOffset::BIT_2 | + (static_cast(inputString[j + ByteOffset::BYTE_2]) & 0xc0) >> + ByteOffset::BIT_6]; + // 00 + the last 6 bits of the third char + outputBuffer[i + ByteOffset::BYTE_3] = + BASE64_CHAR_SET[static_cast(inputString[j + ByteOffset::BYTE_2]) & 0x3f]; + } + switch (strLen % TO_TRANSFORM_CHAR_NUM) { + // the original string is less than three bytes, and the missing place is filled with '=' to patch four bytes + case ByteSize::SIZE_1_BYTES: + // 1,2: the original character is one, and two characters are missing after conversion + outputBuffer[encodedStrLen - ByteOffset::BYTE_4] = + BASE64_CHAR_SET[(static_cast(inputString[strLen - ByteOffset::BYTE_1]) & 0xff) >> + ByteOffset::BIT_2]; + outputBuffer[encodedStrLen - ByteOffset::BYTE_3] = + BASE64_CHAR_SET[(static_cast(inputString[strLen - ByteOffset::BYTE_1]) & 0x03) << + ByteOffset::BIT_4]; + outputBuffer[encodedStrLen - ByteOffset::BYTE_2] = '='; + outputBuffer[encodedStrLen - ByteOffset::BYTE_1] = '='; + break; + case ByteSize::SIZE_2_BYTES: + // 1: the original character is two, and a character are missing after conversion + outputBuffer[encodedStrLen - ByteOffset::BYTE_4] = + BASE64_CHAR_SET[(static_cast(inputString[strLen - ByteOffset::BYTE_2]) & 0xff) >> + ByteOffset::BIT_2]; + outputBuffer[encodedStrLen - ByteOffset::BYTE_3] = + BASE64_CHAR_SET[(static_cast(inputString[strLen - ByteOffset::BYTE_2]) & 0x03) << + ByteOffset::BIT_4 | + (static_cast(inputString[strLen - ByteOffset::BYTE_1]) & 0xf0) >> + ByteOffset::BIT_4]; + outputBuffer[encodedStrLen - ByteOffset::BYTE_2] = + BASE64_CHAR_SET[(static_cast(inputString[strLen - ByteOffset::BYTE_1]) & 0x0f) << + ByteOffset::BIT_2]; + outputBuffer[encodedStrLen - ByteOffset::BYTE_1] = '='; + break; + default: + break; + } + + return encodedStrLen; +} +// clang-format on + +std::string GetHumanReadableProcessName() +{ + return "JSVM[" + std::to_string(platform::OS::GetPid()) + "]"; +} + +MUST_USE_RESULT bool CSPRNG(void* buffer, size_t length) +{ + unsigned char* buf = static_cast(buffer); + do { + if (RAND_status() == 1) { +#if OPENSSL_VERSION_MAJOR >= 3 + if (RAND_bytes_ex(nullptr, buf, length, 0) == 1) { + return true; + } +#else + while (length > INT_MAX && RAND_bytes(buf, INT_MAX) == 1) { + buf += INT_MAX; + length -= INT_MAX; + } + if (length <= INT_MAX && RAND_bytes(buf, static_cast(length)) == 1) { + return true; + } +#endif + } +#if OPENSSL_VERSION_MAJOR >= 3 + const auto code = ERR_peek_last_error(); + // A misconfigured OpenSSL 3 installation may report 1 from RAND_poll() + // and RAND_status() but fail in RAND_bytes() if it cannot look up + // a matching algorithm for the CSPRNG. + if (ERR_GET_LIB(code) == ERR_LIB_RAND) { + const auto reason = ERR_GET_REASON(code); + if (reason == RAND_R_ERROR_INSTANTIATING_DRBG || reason == RAND_R_UNABLE_TO_FETCH_DRBG || + reason == RAND_R_UNABLE_TO_CREATE_DRBG) { + return false; + } + } +#endif + } while (RAND_poll() == 1); + + return false; +} + +void CheckedUvLoopClose(uv_loop_t* loop) +{ + if (uv_loop_close(loop) == 0) { + return; + } + + // Finally, abort. + UNREACHABLE("uv_loop_close() while having open handles"); +} + +using v8::Isolate; +using v8::Local; +using v8::String; +using v8::Value; + +TwoByteValue::TwoByteValue(Isolate* isolate, Local value) +{ + if (value.IsEmpty()) { + return; + } + + Local string; + if (!value->ToString(isolate->GetCurrentContext()).ToLocal(&string)) { + return; + } + + // Allocate enough space to include the null terminator + const size_t storage = string->Length() + 1; + AllocateSufficientStorage(storage); + + const int flags = String::NO_NULL_TERMINATION; + const int length = string->Write(isolate, Out(), 0, storage, flags); + SetLengthAndZeroTerminate(length); +} +} // namespace inspector +} // namespace jsvm \ No newline at end of file diff --git a/src/inspector/inspector_utils.h b/src/inspector/inspector_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..f82f619cac0c6f673ffcfe3a950cb3b382d9063d --- /dev/null +++ b/src/inspector/inspector_utils.h @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * 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. + */ + +#ifndef INSPECTOR_UTILS_H +#define INSPECTOR_UTILS_H +#include +#include +#include +#include + +#include "jsvm_dfx.h" +#include "jsvm_util.h" +#include "uv.h" +#include "v8-inspector.h" + +#ifdef __GNUC__ +#define MUST_USE_RESULT __attribute__((warn_unused_result)) +#else +#define MUST_USE_RESULT +#endif + +namespace jsvm { +namespace inspector { +template +class ContainerOfHelper { +public: + // The helper is for doing safe downcasts from base types to derived types. + inline ContainerOfHelper(Inner Outer::* field, Inner* pointer); + template + inline operator TypeName*() const; + +private: + Outer* const pointer; +}; + +inline char ToLower(char c) +{ + return std::tolower(c, std::locale::classic()); +} + +inline bool StringEqualNoCase(const char* a, const char* b) +{ + while (ToLower(*a) == ToLower(*b++)) { + if (*a++ == '\0') { + return true; + } + } + return false; +} + +inline bool StringEqualNoCaseN(const char* a, const char* b, size_t length) +{ + for (size_t i = 0; i < length; i++) { + if (ToLower(a[i]) != ToLower(b[i])) { + return false; + } + if (a[i] == '\0') { + return true; + } + } + return true; +} + +// Use this when a variable or parameter is unused in order to explicitly +// silence a compiler warning about that. +template +inline void USE(T&&) +{} + +template +struct FunctionDeleter { + void operator()(T* pointer) const + { + function(pointer); + } + typedef std::unique_ptr Pointer; +}; + +template +using DeleteFnPtr = typename FunctionDeleter::Pointer; + +template +constexpr uintptr_t OffsetOf(Inner Outer::* field) +{ + return reinterpret_cast(&(static_cast(nullptr)->*field)); +} + +template +ContainerOfHelper::ContainerOfHelper(Inner Outer::* field, Inner* pointer) + : pointer(reinterpret_cast(reinterpret_cast(pointer) - OffsetOf(field))) +{} + +template +template +ContainerOfHelper::operator TypeName*() const +{ + return static_cast(pointer); +} + +// Calculate the address of the outer (i.e. embedding) struct from +// the interior pointer to a data member. +template +constexpr ContainerOfHelper ContainerOf(Inner Outer::* field, Inner* pointer) +{ + return ContainerOfHelper(field, pointer); +} + +template +class MaybeStackBuffer { +public: + const T* Out() const + { + return buf; + } + + T* Out() + { + return buf; + } + + // operator* for compatibility with `v8::String::(Utf8)Value` + T* operator*() + { + return buf; + } + + const T* operator*() const + { + return buf; + } + + T& operator[](size_t index) + { + CHECK_LT(index, GetLength()); + return buf[index]; + } + + const T& operator[](size_t index) const + { + CHECK_LT(index, GetLength()); + return buf[index]; + } + + size_t GetLength() const + { + return length; + } + + // Current maximum capacity of the buffer with which SetLength() can be used + // without first calling AllocateSufficientStorage(). + size_t GetCapacity() const + { + return capacity; + } + + void AllocateSufficientStorage(size_t storage); + + void SetLength(size_t lengthParam) + { + // GetCapacity() returns how much memory is actually available. + CHECK_LE(lengthParam, GetCapacity()); + length = lengthParam; + } + + void SetLengthAndZeroTerminate(size_t len) + { + // GetCapacity() returns how much memory is actually available. + CHECK_LE(len + 1, GetCapacity()); + SetLength(len); + + // T() is 0 for integer types, nullptr for pointers, etc. + buf[len] = T(); + } + + void Invalidate() + { + CHECK(!IsAllocated()); + capacity = 0; + length = 0; + buf = nullptr; + } + + // If the buffer is stored in the heap rather than on the stack. + bool IsAllocated() const + { + return !IsInvalidated() && buf != bufSt; + } + + // If Invalidate() has been called. + bool IsInvalidated() const + { + return buf == nullptr; + } + + // Release ownership of the malloc'd buffer. + // Note: This does not free the buffer. + void Release() + { + CHECK(IsAllocated()); + buf = bufSt; + length = 0; + capacity = jsvm::ArraySize(bufSt); + } + + MaybeStackBuffer() : length(0), capacity(jsvm::ArraySize(bufSt)), buf(bufSt) + { + // Default to a zero-length, null-terminated buffer. + buf[0] = T(); + } + + explicit MaybeStackBuffer(size_t storage) : MaybeStackBuffer() + { + AllocateSufficientStorage(storage); + } + + ~MaybeStackBuffer() + { + if (IsAllocated()) { + free(buf); + } + } + + inline std::basic_string ToString() const + { + return { Out(), GetLength() }; + } + inline std::basic_string_view ToStringView() const + { + return { Out(), GetLength() }; + } + +private: + size_t length; + // capacity of the malloc'ed buf + size_t capacity; + T* buf; + T bufSt[kStackStorageSize]; +}; + +class TwoByteValue : public MaybeStackBuffer { +public: + explicit TwoByteValue(v8::Isolate* isolate, v8::Local value); +}; + +template +T* Realloc(T* pointer, size_t n, size_t oldN) +{ + CHECK(n > 0); + size_t newSize = sizeof(T) * n; + T* newPtr = static_cast(malloc(newSize)); + + CHECK_NOT_NULL(newPtr); + + if (!pointer) { + return newPtr; + } + + size_t oldSize = sizeof(T) * oldN; + CHECK(newSize > oldSize); + errno_t ret = memcpy_s(newPtr, newSize, pointer, oldSize); + CHECK(ret == EOK); + free(pointer); + + return newPtr; +} + +template +void MaybeStackBuffer::AllocateSufficientStorage(size_t storage) +{ + CHECK(!IsInvalidated()); + if (storage > GetCapacity()) { + bool wasAllocated = IsAllocated(); + T* allocatedPtr = wasAllocated ? buf : nullptr; + buf = Realloc(allocatedPtr, storage, length); + capacity = storage; + if (!wasAllocated && length > 0) { + int ret = memcpy_s(buf, length * sizeof(buf[0]), bufSt, length * sizeof(buf[0])); + CHECK(ret == EOK); + } + } + + length = storage; +} + +// Convertion between v8_inspector::StringView and std::string +std::string StringViewToUtf8(v8_inspector::StringView view); +std::unique_ptr Utf8ToStringView(const std::string_view message); + +constexpr size_t TO_TRANSFORM_CHAR_NUM = 3; +constexpr size_t TRANSFORMED_CHAR_NUM = 4; + +enum ByteOffset : uint8_t { + BYTE_0 = 0, + BYTE_1 = 1, + BYTE_2 = 2, + BYTE_3 = 3, + BYTE_4 = 4, + BYTE_5 = 5, + BYTE_6 = 6, + BYTE_7 = 7, + BIT_0 = 0, + BIT_1 = 1, + BIT_2 = 2, + BIT_3 = 3, + BIT_4 = 4, + BIT_5 = 5, + BIT_6 = 6, + BIT_7 = 7, + BIT_8 = 8, +}; + +// Encode base64 +inline constexpr size_t Base64EncodeSize(size_t size) +{ + return ((size + TO_TRANSFORM_CHAR_NUM - 1) / TO_TRANSFORM_CHAR_NUM * TRANSFORMED_CHAR_NUM); +} + +// Be careful: If dlen is less than expected encode size, it will crash. +size_t Base64Encode(const char* inputString, size_t slen, char* outputBuffer, size_t dlen); + +std::string GetHumanReadableProcessName(); + +// Either succeeds with exactly |length| bytes of cryptographically +// strong pseudo-random data, or fails. This function may block. +// Don't assume anything about the contents of |buffer| on error. +// As a special case, |length == 0| can be used to check if the CSPRNG +// is properly seeded without consuming entropy. +MUST_USE_RESULT bool CSPRNG(void* buffer, size_t length); + +void CheckedUvLoopClose(uv_loop_t* loop); +} // namespace inspector +} // namespace jsvm +#endif